/* -*- Mode: C; c-basic-offset: 4 -*- */
/* Dia -- an diagram creation/manipulation program
 * Copyright (C) 1998 Alexander Larsson
 *
 * xfig-import.c: xfig import filter for dia
 * Copyright (C) 2001 Lars Clausen
 * based on the dxf-import code
 * Copyright (C) 2000 Steffen Macke
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */
 
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
/* Information used here is taken from the FIG Format 3.2 specification
   <URL:http://www.xfig.org/userman/fig-format.html>
   Some items left unspecified in the specifications are taken from the
   XFig source v. 3.2.3c
*/

#include <string.h>
#include <math.h>
#include <glib.h>
#include <stdlib.h>
#include <errno.h>
#include <locale.h>

#include "intl.h"
#include "message.h"
#include "geometry.h"
#include "filter.h"
#include "object.h"
#include "properties.h"
#include "propinternals.h"
#include "group.h"

#include "create.h"
#include "xfig.h"

#define BUFLEN 512

static Color fig_colors[FIG_MAX_USER_COLORS];

gboolean import_fig(const gchar *filename, DiagramData *dia, void* user_data);

/** Eats the rest of the line.
 */
static void
eat_line(FILE *file) {
    char buf[BUFLEN];

    do {
	if (fgets(buf, BUFLEN, file) == NULL) {
	    return;
	}
	if (buf[strlen(buf)-1] == '\n') return;
    } while (!feof(file));
}

/** Skip past FIG comments (lines starting with #) and empty lines.
 * Returns TRUE if there is more in the file to read.
 */
static int
skip_comments(FILE *file) {
    int ch;

    while (!feof(file)) {
	if ((ch = fgetc(file)) == EOF) {
	    return FALSE;
	}
    
	if (ch == '\n') continue;
	else if (ch == '#') {
	    eat_line(file);
	    continue;
	} else {
	    ungetc(ch, file);
	    return TRUE;
	}
    }
    return FALSE;
}

static Color
fig_color(int color_index) 
{
    if (color_index <= -1) 
        return color_black; /* Default color */
    else if (color_index < FIG_MAX_DEFAULT_COLORS) 
        return fig_default_colors[color_index];
    else if (color_index < FIG_MAX_USER_COLORS) 
	return fig_colors[color_index-FIG_MAX_DEFAULT_COLORS];
    else {
	message_error(_("Color index %d too high, only 512 colors allowed. Using black instead."),
		      color_index);
	return color_black;
    }
}

static Color
fig_area_fill_color(int area_fill, int color_index) {
    Color col;
    col = fig_color(color_index);
    if (area_fill == -1) return col;
    if (area_fill >= 0 && area_fill <= 20) {
	if (color_index == -1 || color_index == 0) {
	    col.red = 0xff*(20-area_fill)/20;
	    col.green = 0xff*(20-area_fill)/20;
	    col.blue = 0xff*(20-area_fill)/20;
	} else {
	    col.red = (col.red*area_fill)/20;
	    col.green = (col.green*area_fill)/20;
	    col.blue = (col.blue*area_fill)/20;
	}
    } else if (area_fill > 20 && area_fill <= 40) {
	/* White and black area illegal here */
	col.red += (0xff-col.red)*(area_fill-20)/20;
	col.green += (0xff-col.green)*(area_fill-20)/20;
	col.blue += (0xff-col.blue)*(area_fill-20)/20;
    } else {
	message_warning(_("Patterns are not supported by Dia"));
    }
    
    return col;
}



static PropDescription xfig_simple_prop_descs_line[] = {
    { "line_width", PROP_TYPE_REAL },
    { "line_colour", PROP_TYPE_COLOUR },
    PROP_DESC_END};

static LineStyle 
fig_line_style_to_dia(int line_style) 
{
    switch (line_style) {
    case 0:
        return LINESTYLE_SOLID;
    case 1:
        return LINESTYLE_DASHED;
    case 2:
	return LINESTYLE_DOTTED;
    case 3:
        return LINESTYLE_DASH_DOT;
    case 4:
        return LINESTYLE_DASH_DOT_DOT;
    case 5:
        message_warning(_("Triple-dotted lines are not supported by Dia, "
			  "using double-dotted"));
        return LINESTYLE_DASH_DOT_DOT;
    default:
        message_error(_("Line style %d should not appear\n"), line_style);
        return LINESTYLE_SOLID;
    }
}

static void
fig_simple_properties(DiaObject *obj,
		      int line_style,
		      float dash_length,
		      int thickness,
		      int pen_color,
		      int fill_color,
		      int area_fill) {
    GPtrArray *props = prop_list_from_descs(xfig_simple_prop_descs_line,
                                            pdtpp_true);
    RealProperty *rprop;
    ColorProperty *cprop;

    g_assert(props->len == 2);
    
    rprop = g_ptr_array_index(props,0);
    rprop->real_data = thickness/FIG_ALT_UNIT;
    
    cprop = g_ptr_array_index(props,1);
    cprop->color_data = fig_color(pen_color);


    if (line_style != -1) {
        LinestyleProperty *lsprop = 
            (LinestyleProperty *)make_new_prop("line_style", 
                                               PROP_TYPE_LINESTYLE,
                                               PROP_FLAG_DONT_SAVE);
        lsprop->dash = dash_length/FIG_ALT_UNIT;
        lsprop->style = fig_line_style_to_dia(line_style);

        g_ptr_array_add(props,lsprop);
    }

    if (area_fill == -1) {
        BoolProperty *bprop = 
            (BoolProperty *)make_new_prop("show_background",
                                          PROP_TYPE_BOOL,PROP_FLAG_DONT_SAVE);
        bprop->bool_data = FALSE;

        g_ptr_array_add(props,bprop);
    } else {
        ColorProperty *cprop = 
            (ColorProperty *)make_new_prop("fill_colour",
                                           PROP_TYPE_COLOUR,
                                           PROP_FLAG_DONT_SAVE);
        cprop->color_data = fig_area_fill_color(area_fill, fill_color);

        g_ptr_array_add(props,cprop);
    }

    obj->ops->set_props(obj, props);
    prop_list_free(props);
}

static int
fig_read_n_points(FILE *file, int n, Point **points) {
    int i;
    GArray *points_list = g_array_sized_new(FALSE, FALSE, sizeof(Point), n);

    for (i = 0; i < n; i++) {
	int x,y;
	Point p;
	if (fscanf(file, " %d %d ", &x, &y) != 2) {
	    message_error(_("Error while reading %dth of %d points: %s\n"),
			  i, n, strerror(errno));
	    g_array_free(points_list, TRUE);
	    return FALSE;
	}
	p.x = x/FIG_UNIT;
	p.y = y/FIG_UNIT;
	g_array_append_val(points_list, p);
    }
    fscanf(file, "\n");
    
    *points = (Point *)points_list->data;
    g_array_free(points_list, FALSE);
    return TRUE;
}

static Arrow *
fig_read_arrow(FILE *file) {
    int arrow_type, style;
    real thickness, width, height;
    Arrow *arrow;
    char* old_locale;

    old_locale = setlocale(LC_NUMERIC, "C");

    if (fscanf(file, "%d %d %lf %lf %lf\n",
	       &arrow_type, &style, &thickness,
	       &width, &height) != 5) {
	message_error(_("Error while reading arrowhead\n"));
	setlocale(LC_NUMERIC,old_locale);
	return NULL;
    }
    setlocale(LC_NUMERIC,old_locale);

    arrow = g_new(Arrow, 1);

    switch (arrow_type) {
    case 0:
	arrow->type = ARROW_LINES;
	break;
    case 1:
	arrow->type = (style?ARROW_FILLED_TRIANGLE:ARROW_HOLLOW_TRIANGLE);
	break;
    case 2:
	arrow->type = (style?ARROW_FILLED_CONCAVE:ARROW_BLANKED_CONCAVE);
	break;
    case 3:
	arrow->type = (style?ARROW_FILLED_DIAMOND:ARROW_HOLLOW_DIAMOND);
	break;
    default:
	message_error(_("Unknown arrow type %d\n"), arrow_type);
	g_free(arrow);
	return NULL;
    }
    arrow->width = width/FIG_UNIT;
    arrow->length = height/FIG_UNIT;

    return arrow;
}

static gchar *
fig_fix_text(gchar *text) {
    int i, j;
    int asciival;
    GError *err = NULL;
    gchar *converted;
    gboolean needs_conversion = FALSE;

    for (i = 0, j = 0; text[i] != 0; i++, j++) {
	if (text[i] == '\\') {
	    sscanf(text+i+1, "%3o", &asciival);
	    text[j] = asciival;
	    i+=3;
	    needs_conversion = TRUE;
	} else {
	    text[j] = text[i];
	}
    }
    /* Strip final newline */
    text[j-1] = 0;
    if (text[j-2] == '\001') {
	text[j-2] = 0;
    }
    if (needs_conversion) {
	/* Crudely assuming that fig uses Latin-1 */
	converted = g_convert(text, strlen(text), "UTF-8", "ISO-8859-1",
			      NULL, NULL, &err);
	if (err != NULL) {
	    printf("Error converting %s: %s\n", text, err->message);
	    return text;
	}
	if (!g_utf8_validate(converted, -1, NULL)) {
	    printf("Fails to validate %s\n", converted);
	    return text;
	}
	if (text != converted) g_free(text);
	return converted;
    } else return text;
}

static char *
fig_read_text_line(FILE *file) {
    char *text_buf;
    int text_alloc, text_len;

    getc(file);
    text_alloc = 80;
    text_buf = (char *)g_malloc(text_alloc*sizeof(char));
    text_len = 0;
    while (fgets(text_buf+text_len, text_alloc-text_len, file) != NULL) {
	if (strlen(text_buf) < text_alloc-1) break;
	text_len = text_alloc;
	text_alloc *= 2;
	text_buf = (char *)g_realloc(text_buf, text_alloc*sizeof(char));
    }

    text_buf = fig_fix_text(text_buf);

    return text_buf;
}

static GList *depths[FIG_MAX_DEPTHS];

/* If there's something in the compound stack, we ignore the depth field,
   as it will be determined by the group anyway */
static GSList *compound_stack = NULL;
/* When collection compounds, this is the *highest* depth an element is
   found at.  Since Dia groups are of one level, we put them all at that
   level.  Best we can do now. */
static int compound_depth;

/** Add an object at a given depth.  This function checks for depth limits
 * and updates the compound depth if needed.
 *
 * @param newobj An object to add.  If we're inside a compound, this
 * doesn't really add the object.
 * @param depth A depth as in the Fig format, max 999
 */
static void
add_at_depth(DiaObject *newobj, int depth) {
    if (depth < 0 || depth >= FIG_MAX_DEPTHS) {
	message_error(_("Depth %d of of range, only 0-%d allowed.\n"),
		      depth, FIG_MAX_DEPTHS-1);
	depth = FIG_MAX_DEPTHS - 1;
    }
    if (compound_stack == NULL) 
	depths[depth] = g_list_append(depths[depth], newobj);
    else 
	if (compound_depth > depth) compound_depth = depth;
}

static DiaObject *
fig_read_ellipse(FILE *file) {
    int sub_type;
    int line_style;
    int thickness;
    int pen_color;
    int fill_color;
    int depth;
    int pen_style;
    int area_fill;
    real style_val;
    int direction;
    real angle;
    int center_x, center_y;
    int radius_x, radius_y;
    int start_x, start_y;
    int end_x, end_y;
    DiaObject *newobj = NULL;
    char* old_locale;

    old_locale = setlocale(LC_NUMERIC, "C");
    if (fscanf(file, "%d %d %d %d %d %d %d %d %lf %d %lf %d %d %d %d %d %d %d %d\n",
	       &sub_type,
	       &line_style,
	       &thickness,
	       &pen_color,
	       &fill_color,
	       &depth,
	       &pen_style,
	       &area_fill,
	       &style_val,
	       &direction,
	       &angle,
	       &center_x, &center_y,
	       &radius_x, &radius_y,
	       &start_x, &start_y,
	       &end_x, &end_y) < 19) {
	message_error(_("Couldn't read ellipse info: %s\n"), strerror(errno));
	setlocale(LC_NUMERIC, old_locale);
	return NULL;
    }
    setlocale(LC_NUMERIC, old_locale);
    
    /* Curiously, the sub_type doesn't matter, as all info can be
       extracted this way */
    newobj = create_standard_ellipse((center_x-radius_x)/FIG_UNIT,
				     (center_y-radius_y)/FIG_UNIT,
				     (2*radius_x)/FIG_UNIT,
				     (2*radius_y)/FIG_UNIT);
    if (newobj == NULL) return NULL;
    fig_simple_properties(newobj, line_style, style_val, thickness,
			  pen_color, fill_color, area_fill);

    /* Pen style field (not used) */
    /* Style_val (size of dots and dashes) in 1/80 inch */
    /* Direction (not used) */
    /* Angle -- can't rotate yet */

    /* Depth field */
    add_at_depth(newobj, depth);

    return newobj;
}

static DiaObject *
fig_read_polyline(FILE *file) {
    int sub_type;
    int line_style;
    int thickness;
    int pen_color;
    int fill_color;
    int depth;
    int pen_style;
    int area_fill;
    real style_val;
    int join_style;
    int cap_style;
    int radius;
    int forward_arrow, backward_arrow;
    Arrow *forward_arrow_info = NULL, *backward_arrow_info = NULL;
    int npoints;
    Point *points;
    GPtrArray *props = g_ptr_array_new();
    DiaObject *newobj = NULL;
    int flipped = 0;
    char *image_file = NULL;
    char* old_locale;

    old_locale = setlocale(LC_NUMERIC, "C");
    if (fscanf(file, "%d %d %d %d %d %d %d %d %lf %d %d %d %d %d %d\n",
	       &sub_type,
	       &line_style,
	       &thickness,
	       &pen_color,
	       &fill_color,
	       &depth,
	       &pen_style,
	       &area_fill,
	       &style_val,
	       &join_style,
	       &cap_style,
	       &radius,
	       &forward_arrow,
	       &backward_arrow,
	       &npoints) != 15) {
	message_error(_("Couldn't read polyline info: %s\n"), strerror(errno));
	goto exit;
    }

    if (forward_arrow == 1) {
	forward_arrow_info = fig_read_arrow(file);
    }

    if (backward_arrow == 1) {
	backward_arrow_info = fig_read_arrow(file);
    }

    if (sub_type == 5) { /* image has image name before npoints */
	/* Despite what the specs say */
	if (fscanf(file, " %d", &flipped) != 1) {
	    message_error(_("Couldn't read flipped bit: %s\n"), strerror(errno));
	    goto exit;
	}

	image_file = fig_read_text_line(file);

    }

    if (!fig_read_n_points(file, npoints, &points)) {
	goto exit;
    }
     
    switch (sub_type) {
    case 4: {
	RealProperty *rprop = 
	    (RealProperty *)make_new_prop("corner_radius",
					  PROP_TYPE_REAL,PROP_FLAG_DONT_SAVE);
	if (radius < 0) {
	    message_warning(_("Negative corner radius, negating"));
	    rprop->real_data = -radius/FIG_ALT_UNIT;
	} else {
	    rprop->real_data = radius/FIG_ALT_UNIT;
	}
	g_ptr_array_add(props,rprop);
    }
	/* Notice fallthrough */
    case 2: /* box */
	if (points[0].x > points[2].x) {
	    real tmp = points[0].x;
	    points[0].x = points[2].x;
	    points[2].x = tmp;
	}
	if (points[0].y > points[2].y) {
	    real tmp = points[0].y;
	    points[0].y = points[2].y;
	    points[2].y = tmp;
	}
	newobj = create_standard_box(points[0].x, points[0].y,
				     points[2].x-points[0].x,
				     points[2].y-points[0].y);
	if (newobj == NULL) goto exit;
	newobj->ops->set_props(newobj, props);
	break;
    case 5: /* imported-picture bounding-box) */
	newobj = create_standard_image(points[0].x, points[0].y,
				       points[2].x-points[0].x,
				       points[2].y-points[0].y,
				       image_file);
	if (newobj == NULL) goto exit;
	break;
    case 1: /* polyline */
	newobj = create_standard_polyline(npoints, points, 
					  forward_arrow_info, 
					  backward_arrow_info);
	if (newobj == NULL) goto exit;
	break;
    case 3: /* polygon */
	newobj = create_standard_polygon(npoints, points);
	if (newobj == NULL) goto exit;
	break;
    default: 
	message_error(_("Unknown polyline subtype: %d\n"), sub_type);
	goto exit;
    }

    fig_simple_properties(newobj, line_style, style_val, thickness,
			  pen_color, fill_color, area_fill);
    /* Pen style field (not used) */
    /* Style_val (size of dots and dashes) in 1/80 inch*/
    /* Join style */
    /* Cap style */
     
    /* Depth field */
    add_at_depth(newobj, depth);
 exit:
    setlocale(LC_NUMERIC, old_locale);
    prop_list_free(props);
    g_free(forward_arrow_info);
    g_free(backward_arrow_info);
    g_free(image_file);
    return newobj;
}

#define TENSION 0.25

static BezPoint *transform_spline(int npoints, Point *points, gboolean closed) {
    BezPoint *bezpoints = g_new(BezPoint, npoints);
    int i;
    Point vector;

    for (i = 0; i < npoints; i++) {
	bezpoints[i].p3 = points[i];
	bezpoints[i].type = BEZ_CURVE_TO;
    }
    bezpoints[0].type = BEZ_MOVE_TO;
    bezpoints[0].p1 = points[0];
    for (i = 1; i < npoints-1; i++) {
	bezpoints[i].p2 = points[i];
	bezpoints[i+1].p1 = points[i];
	vector = points[i-1];
	point_sub(&vector, &points[i+1]);
	point_scale(&vector, -TENSION);
	point_sub(&bezpoints[i].p2, &vector);
	point_add(&bezpoints[i+1].p1, &vector);
    }
    if (closed) {
	bezpoints[npoints-1].p2 = points[i];
	bezpoints[1].p1 = points[i];
	vector = points[npoints-2];
	point_sub(&vector, &points[1]);
	point_scale(&vector, -TENSION);
	point_sub(&bezpoints[npoints-1].p2, &vector);
	point_add(&bezpoints[1].p1, &vector);
    } else {
	bezpoints[1].p1 = points[0];
	bezpoints[npoints-1].p2 = bezpoints[npoints-1].p3;
    }
    return bezpoints;
}

/* The XFig 'X-Splines' seem to be a generalization of cubic B-Splines
 * and Catmull-Rom splines.
 * Our Beziers, if Beziers they are, have the basis matrix M_bez
 * [-1  3 -3  1]
 * [ 3 -6  3  0]
 * [-3  3  0  0]
 * [ 1  0  0  0]
 *
 * The basis matrix for cubic B-Splines according to Hearn & Baker (M_b)
 * [-1  3 -3  1]
 * [ 3 -6  3  0]*1/6
 * [-3  0  3  0]
 * [ 1  4  1  0]
 * So the transformation matrix (M_bez^-1)*M_b should be
 * [ 1  4  1  0]
 * [ 0  4  2  0]*1/6
 * [ 0  2  4  0]
 * [ 0  1  4  1] 
 *
 * The basis matrix for Catmull-Rom splines (M_c) is:
 * [-s 2-s s-2 s]
 * [2s s-3 3-2s -s]
 * [-s  0  s  0]
 * [ 0  1  0  0] where s = (1-t)/2 and t = 0, so s = 1/2:
 * [ -.5  1.5 -1.5   .5] 
 * [   1 -2.5    2  -.5] 
 * [ -.5    0   .5    0] 
 * [   0    1    4    1] 
 * Thus, the transformation matrix for the interpolated splines should be
 * (M_bez^-1)*M_c
 * The conversion matrix should then be:
 * [0    1    4    1]
 * [-1/6 1 4+1/6   1]
 * [0   1/6   5  5/6]
 * [0    0    5    1]
 * or
 * [ 0  6  24  6]
 * [-1  6  25  6]*1/6
 * [ 0  1  30  5]
 * [ 0  0  30  5]
 */

static real matrix_bspline_to_bezier[4][4] =
    {{1/6.0, 4/6.0, 1/6.0, 0},
     {0,   4/6.0, 2/6.0, 0},
     {0,   2/6.0, 4/6.0, 0},
     {0,   1/6.0, 4/6.0, 1/6.0}};

static real matrix_catmull_to_bezier[4][4] =
    {{0,      1,   4,     1},
     {-1/6.0, 1, 25/26.0, 1},
     {0,      1,   5,   5/6.0},
     {0,      0,   5,   5/6.0}};

static DiaObject *
fig_read_spline(FILE *file) {
    int sub_type;
    int line_style;
    int thickness;
    int pen_color;
    int fill_color;
    int depth;
    int pen_style;
    int area_fill;
    real style_val;
    int cap_style;
    int forward_arrow, backward_arrow;
    Arrow *forward_arrow_info = NULL, *backward_arrow_info = NULL;
    int npoints;
    Point *points;
    GPtrArray *props = g_ptr_array_new();
    DiaObject *newobj = NULL;
    BezPoint *bezpoints;
    int i;
    char* old_locale;

    old_locale = setlocale(LC_NUMERIC, "C");
    if (fscanf(file, "%d %d %d %d %d %d %d %d %lf %d %d %d %d\n",
	       &sub_type,
	       &line_style,
	       &thickness,
	       &pen_color,
	       &fill_color,
	       &depth,
	       &pen_style,
	       &area_fill,
	       &style_val,
	       &cap_style,
	       &forward_arrow,
	       &backward_arrow,
	       &npoints) != 13) {
	message_error(_("Couldn't read spline info: %s\n"), strerror(errno));
	goto exit;
    }

    if (forward_arrow == 1) {
	forward_arrow_info = fig_read_arrow(file);
    }

    if (backward_arrow == 1) {
	backward_arrow_info = fig_read_arrow(file);
    }

    if (!fig_read_n_points(file, npoints, &points)) {
	goto exit;
    }
     
    switch (sub_type) {
    case 0: /* Open approximated spline */
    case 1: /* Closed approximated spline */
	message_warning(_("Cannot convert approximated spline yet."));
	goto exit;
    case 2: /* Open interpolated spline */
    case 3: /* Closed interpolated spline */
	/* Despite what the Fig description says, interpolated splines
	   now also have the line with spline info from the X-spline */
    case 4: /* Open X-spline */
    case 5: /* Closed X-spline */
	{
	    double f;
	    gboolean interpolated = TRUE;
	    for (i = 0; i < npoints; i++) {
		if (fscanf(file, " %lf ", &f) != 1) {
		    message_error(_("Couldn't read spline info: %s\n"),
				  strerror(errno));
		    goto exit;
		}
		if (f != -1.0 && f != 0.0) {
		    message_warning(_("Cannot convert approximated spline yet."));
		    interpolated = FALSE;
		}
	    }
	    if (!interpolated)
		goto exit;
	    /* Matrix-based conversion not ready yet. */
#if 0
	    if (sub_type%2 == 0) {
		bezpoints = fig_transform_spline(npoints, points, FALSE, f);
		newobj = create_standard_bezierline(npoints, bezpoints,
						    forward_arrow_info,
						    backward_arrow_info);
	    } else {
		points = g_renew(Point, points, npoints+1);
		points[npoints] = points[0];
		npoints++;
		bezpoints = fig_transform_spline(npoints, points, TRUE, f);
		newobj = create_standard_beziergon(npoints, bezpoints);
	    }
#else
	    if (sub_type%2 == 0) {
		bezpoints = transform_spline(npoints, points, FALSE);
		newobj = create_standard_bezierline(npoints, bezpoints,
						    forward_arrow_info,
						    backward_arrow_info);
	    } else {
		points = g_renew(Point, points, npoints+1);
		points[npoints] = points[0];
		npoints++;
		bezpoints = transform_spline(npoints, points, TRUE);
		newobj = create_standard_beziergon(npoints, bezpoints);
	    }
#endif
	}
	if (newobj == NULL) goto exit;
	break;
    default: 
	message_error(_("Unknown spline subtype: %d\n"), sub_type);
	goto exit;
    }

    fig_simple_properties(newobj, line_style, style_val, thickness,
			  pen_color, fill_color, area_fill);
    /* Pen style field (not used) */
    /* Style_val (size of dots and dashes) in 1/80 inch*/
    /* Cap style */
     
    /* Depth field */
    add_at_depth(newobj, depth);
 exit:
    setlocale(LC_NUMERIC, old_locale);
    prop_list_free(props);
    g_free(forward_arrow_info);
    g_free(backward_arrow_info);
    g_free(points);
    return newobj;
}

static DiaObject *
fig_read_arc(FILE *file) {
    int sub_type;
    int line_style;
    int thickness;
    int pen_color;
    int fill_color;
    int depth;
    int pen_style;
    int area_fill;
    real style_val;
    int cap_style;
    int direction;
    int forward_arrow, backward_arrow;
    Arrow *forward_arrow_info = NULL, *backward_arrow_info = NULL;
    DiaObject *newobj = NULL;
    real center_x, center_y;
    int x1, y1;
    int x2, y2;
    int x3, y3;
    real radius;
    char* old_locale;

    old_locale = setlocale(LC_NUMERIC, "C");
    if (fscanf(file, "%d %d %d %d %d %d %d %d %lf %d %d %d %d %lf %lf %d %d %d %d %d %d\n",
	       &sub_type,
	       &line_style,
	       &thickness,
	       &pen_color,
	       &fill_color,
	       &depth,
	       &pen_style,
	       &area_fill,
	       &style_val,
	       &cap_style,
	       &direction,
	       &forward_arrow,
	       &backward_arrow,
	       &center_x, &center_y,
	       &x1, &y1,
	       &x2, &y2,
	       &x3, &y3) != 21) {
	message_error(_("Couldn't read arc info: %s\n"), strerror(errno));
	goto exit;
    }

    if (forward_arrow == 1) {
	forward_arrow_info = fig_read_arrow(file);
    }

    if (backward_arrow == 1) {
	backward_arrow_info = fig_read_arrow(file);
    }

    radius = sqrt((x1-center_x)*(x1-center_x)+(y1-center_y)*(y1-center_y))/FIG_UNIT;

    switch (sub_type) {
    case 0: /* We can't do pie-wedge properly yet */
    case 1: 
	newobj = create_standard_arc(x1/FIG_UNIT, y1/FIG_UNIT,
				     x3/FIG_UNIT, y3/FIG_UNIT,
				     radius, 
				     forward_arrow_info,
				     backward_arrow_info);
	if (newobj == NULL) goto exit;
	break;
    default: 
	message_error(_("Unknown polyline subtype: %d\n"), sub_type);
	goto exit;
    }

    fig_simple_properties(newobj, line_style, style_val, thickness,
			  pen_color, fill_color, area_fill);

    /* Pen style field (not used) */
    /* Style_val (size of dots and dashes) in 1/80 inch*/
    /* Join style */
    /* Cap style */
     
    /* Depth field */
    add_at_depth(newobj, depth);

 exit:
    setlocale(LC_NUMERIC, old_locale);
    g_free(forward_arrow_info);
    g_free(backward_arrow_info);
    return newobj;
}

static PropDescription xfig_text_descs[] = {
    { "text", PROP_TYPE_TEXT },
    PROP_DESC_END
    /* Can't do the angle */
    /* Height and length are ignored */
    /* Flags */
};

static DiaObject *
fig_read_text(FILE *file) {
    GPtrArray *props = NULL;
    TextProperty *tprop;

    DiaObject *newobj = NULL;
    int sub_type;
    int color;
    int depth;
    int pen_style;
    int font;
    real font_size;
    real angle;
    int font_flags;
    real height;
    real length;
    int x, y;
    char *text_buf = NULL;
    char* old_locale;

    old_locale = setlocale(LC_NUMERIC, "C");
    if (fscanf(file, " %d %d %d %d %d %lf %lf %d %lf %lf %d %d",
	       &sub_type,
	       &color,
	       &depth,
	       &pen_style,
	       &font,
	       &font_size,
	       &angle,
	       &font_flags,
	       &height,
	       &length,
	       &x,
	       &y) != 12) {
	message_error(_("Couldn't read text info: %s\n"), strerror(errno));
	setlocale(LC_NUMERIC, old_locale);
	return NULL;
    }
    /* Skip one space exactly */
    text_buf = fig_read_text_line(file);

    newobj = create_standard_text(x/FIG_UNIT, y/FIG_UNIT);
    if (newobj == NULL) goto exit;

    props = prop_list_from_descs(xfig_text_descs,pdtpp_true);

    tprop = g_ptr_array_index(props,0);
    tprop->text_data = g_strdup(text_buf);
    /*g_free(text_buf); */
    tprop->attr.alignment = sub_type;
    tprop->attr.position.x = x/FIG_UNIT;
    tprop->attr.position.y = y/FIG_UNIT;

    if (font_flags & 4) {
	switch (font) {
	case 0: tprop->attr.font = dia_font_new_from_legacy_name("Times-Roman"); break;
	case 1: tprop->attr.font = dia_font_new_from_legacy_name("Times-Roman"); break;
	case 2: tprop->attr.font = dia_font_new_from_legacy_name("Times-Bold"); break;
	case 3: tprop->attr.font = dia_font_new_from_legacy_name("Times-Italic"); break;
	case 4: tprop->attr.font = dia_font_new_from_legacy_name("Helvetica"); break;
	case 5: tprop->attr.font = dia_font_new_from_legacy_name("Courier"); break;
	default: message_warning("Can't find LaTeX font nr. %d, using sans\n", font);
	    tprop->attr.font = dia_font_new_from_legacy_name("Helvetica");
	}
    } else {
	if (font == -1) {
	    /* "Default font" - wazzat? */
	    tprop->attr.font = dia_font_new_from_legacy_name("Times Roman");
	} else if (font < 0 || font > 34) {
	    message_warning("Can't find Postscript font nr. %d, using sans\n", font);
	    tprop->attr.font = dia_font_new_from_legacy_name("Helvetica");
	} else {
	    tprop->attr.font = dia_font_new_from_legacy_name(fig_fonts[font]);
	}
    }
    tprop->attr.height = font_size*3.54/72.0;
    tprop->attr.color = fig_color(color);
    newobj->ops->set_props(newobj, props);
    
    /* Depth field */
    add_at_depth(newobj, depth);

 exit:
    setlocale(LC_NUMERIC, old_locale);
    if (text_buf != NULL) g_free(text_buf);
    if (props != NULL) prop_list_free(props);
    return newobj;
}

static gboolean
fig_read_object(FILE *file) {
    int objecttype;
    DiaObject *item = NULL;

    if (fscanf(file, "%d ", &objecttype) != 1) {
	if (!feof(file)) {
	    message_error(_("Couldn't identify FIG object: %s\n"), strerror(errno));
	}
	return FALSE;
    }

    switch (objecttype) {
    case -6: { /* End of compound */
	if (compound_stack == NULL) {
	    message_error(_("Compound end outside compound\n"));
	    return FALSE;
	}

	/* Make group item with these items */
	item = create_standard_group((GList*)compound_stack->data);
	compound_stack = g_slist_remove(compound_stack, compound_stack->data);
	if (compound_stack == NULL) {
	    depths[compound_depth] = g_list_append(depths[compound_depth],
						    item);
	}
	break;
    }
    case 0: { /* Color pseudo-object. */
	int colornumber;
	int colorvalues;
	Color color;

	if (fscanf(file, " %d #%xd", &colornumber, &colorvalues) != 2) {
	    message_error(_("Couldn't read color: %s\n"), strerror(errno));
	    return FALSE;
	}

	if (colornumber < 32 || colornumber > FIG_MAX_USER_COLORS) {
	    message_error(_("Color number %d out of range 0..%d.  Discarding color.\n"),
			  colornumber, FIG_MAX_USER_COLORS);
	    return FALSE;
	}

	color.red = ((colorvalues & 0x00ff0000)>>16) / 255.0;
	color.green = ((colorvalues & 0x0000ff00)>>8) / 255.0;
	color.blue = (colorvalues & 0x000000ff) / 255.0;

	fig_colors[colornumber-32] = color;
	break;
    }
    case 1: { /* Ellipse which is a generalization of circle. */
	item = fig_read_ellipse(file);
	if (item == NULL) {
	    return FALSE;
	}
	break;
    }
    case 2: /* Polyline which includes polygon and box. */
	item = fig_read_polyline(file);
	if (item == NULL) {
	    return FALSE;
	}
	break;
    case 3: /* Spline which includes closed/open control/interpolated spline. */
	item = fig_read_spline(file);
	if (item == NULL) {
	    return FALSE;
	}
	break;
    case 4: /* Text. */
	item = fig_read_text(file);
	if (item == NULL) {
	    return FALSE;
	}
	break;
    case 5: /* Arc. */
	item = fig_read_arc(file);
	if (item == NULL) {
	    return FALSE;
	}
	break;
    case 6: {/* Compound object which is composed of one or more objects. */
	int dummy;
	if (fscanf(file, " %d %d %d %d\n", &dummy, &dummy, &dummy, &dummy) != 4) {
	    message_error(_("Couldn't read group extend: %s\n"), strerror(errno));
	    return FALSE;
	}
	/* Group extends don't really matter */
	if (compound_stack == NULL)
	    compound_depth = FIG_MAX_DEPTHS - 1;
	compound_stack = g_slist_append(compound_stack, NULL);
	return TRUE;
	break;
    }
    default:
	message_error(_("Unknown object type %d\n"), objecttype);
	return FALSE;
	break;
    }
    if (compound_stack != NULL && item != NULL) { /* We're building a compound */
	GList *compound = (GList *)compound_stack->data;
	compound = g_list_append(compound, item);
	compound_stack->data = compound;
    }
    return TRUE;
}

static int
fig_read_line_choice(FILE *file, char *choice1, char *choice2) {
    char buf[BUFLEN];

    if (!fgets(buf, BUFLEN, file)) {
	return -1;
    }

    buf[strlen(buf)-1] = 0; /* Remove trailing newline */
    g_strstrip(buf); /* And any other whitespace */
    if (!g_strcasecmp(buf, choice1)) return 0;
    if (!g_strcasecmp(buf, choice2)) return 1;
    message_warning(_("`%s' is not one of `%s' or `%s'\n"), buf, choice1, choice2);
    return 0;
}

static int
fig_read_paper_size(FILE *file, DiagramData *dia) {
    char buf[BUFLEN];
    int paper;

    if (!fgets(buf, BUFLEN, file)) {
	message_error(_("Error reading paper size: %s\n"), strerror(errno));
	return FALSE;
    }

    buf[strlen(buf)-1] = 0; /* Remove trailing newline */
    g_strstrip(buf); /* And any other whitespace */
    if ((paper = find_paper(buf)) != -1) {
	get_paper_info(&dia->paper, paper, NULL);
	return TRUE;
    }

    message_warning(_("Unknown paper size `%s', using default\n"), buf);
    return TRUE;
}

int figversion;

static int
fig_read_meta_data(FILE *file, DiagramData *dia) {
    if (figversion >= 300) { /* Might exist earlier */
	int portrait;

	if ((portrait = fig_read_line_choice(file, "Portrait", "Landscape")) == -1) {
	    message_error(_("Error reading paper orientation: %s\n"), strerror(errno));
	    return FALSE;
	}
	dia->paper.is_portrait = portrait;
    }

    if (figversion >= 300) { /* Might exist earlier */
	int justify;

	if ((justify = fig_read_line_choice(file, "Center", "Flush Left")) == -1) {
	    message_error(_("Error reading justification: %s\n"), strerror(errno));
	    return FALSE;
	}
	/* Don't know what to do with this */
    }

    if (figversion >= 300) { /* Might exist earlier */
	int units;

	if ((units = fig_read_line_choice(file, "Metric", "Inches")) == -1) {
	    message_error(_("Error reading units: %s\n"), strerror(errno));
	    return FALSE;
	}
	/* Don't know what to do with this */
    }

    if (figversion >= 302) {
	if (!fig_read_paper_size(file, dia)) return FALSE;
    }

    {
	real mag;
	char* old_locale;

	old_locale = setlocale(LC_NUMERIC, "C");
	if (fscanf(file, "%lf\n", &mag) != 1) {
	    message_error(_("Error reading magnification: %s\n"), strerror(errno));
	    setlocale(LC_NUMERIC, old_locale);
	    return FALSE;
	}
        setlocale(LC_NUMERIC, old_locale);

	dia->paper.scaling = mag/100;
    }

    if (figversion >= 302) {
	int multiple;

	if ((multiple = fig_read_line_choice(file, "Single", "Multiple")) == -1) {
	    message_error(_("Error reading multipage indicator: %s\n"), strerror(errno));
	    return FALSE;
	}

	/* Don't know what to do with this */
    }

    {
	int transparent;

	if (fscanf(file, "%d\n", &transparent) != 1) {
	    message_error(_("Error reading transparent color: %s\n"), strerror(errno));
	    return FALSE;
	}
    
	/* Don't know what to do with this */
    }

    if (!skip_comments(file)) {
	if (!feof(file)) {
	    message_error(_("Error reading FIG file: %s\n"), strerror(errno));
	} else {
	    message_error(_("Premature end of FIG file\n"));
	}
	return FALSE;
    }

    {
	int resolution, coord_system;

	if (fscanf(file, "%d %d\n", &resolution, &coord_system) != 2) {
	    message_error(_("Error reading resolution: %s\n"), strerror(errno));
	    return FALSE;
	}
    
	/* Don't know what to do with this */
    }
    return TRUE;
}

/* imports the given fig-file, returns TRUE if successful */
gboolean 
import_fig(const gchar *filename, DiagramData *dia, void* user_data) {
    FILE *figfile;
    int figmajor, figminor;	
    int i;

    for (i = 0; i < FIG_MAX_USER_COLORS; i++) {
	fig_colors[i] = color_black;
    }
    for (i = 0; i < FIG_MAX_DEPTHS; i++) {
	depths[i] = NULL;
    }

    figfile = fopen(filename,"r");
    if(figfile == NULL){
	message_error(_("Couldn't open: '%s' for reading.\n"), 
		      dia_message_filename(filename));
	return FALSE;
    }
  
    /* First check magic bytes */
    if (fscanf(figfile, "#FIG %d.%d\n", &figmajor, &figminor) != 2) {
	message_error(_("Doesn't look like a Fig file: %s\n"), strerror(errno));
	fclose(figfile);
	return FALSE;
    }
	
    if (figmajor != 3 || figminor != 2) {
	message_warning(_("This is a FIG version %d.%d file, I may not understand it\n"), figmajor, figminor);
    }

    figversion = figmajor*100+figminor;

    if (!skip_comments(figfile)) {
	if (!feof(figfile)) {
	    message_error(_("Error reading FIG file: %s\n"), strerror(errno));
	} else {
	    message_error(_("Premature end of FIG file\n"));
	}
	fclose(figfile);
	return FALSE;
    }

    if (!fig_read_meta_data(figfile, dia)) {
	fclose(figfile);
	return FALSE;
    }
  
    compound_stack = NULL;

    do {
	if (!skip_comments(figfile)) {
	    if (!feof(figfile)) {
		message_error(_("Error reading FIG file: %s\n"), strerror(errno));
	    } else {
		break;
	    }
	}
	if (! fig_read_object(figfile)) {
	    fclose(figfile);
	    break;
	}
    } while (TRUE);

    /* Now we can reorder for the depth fields */
    for (i = 0; i < FIG_MAX_DEPTHS; i++) {
	if (depths[i] != NULL)
	    layer_add_objects_first(dia->active_layer, depths[i]);
    }
    return TRUE;
}

/* interface from filter.h */

static const gchar *extensions[] = {"fig", NULL };
DiaImportFilter xfig_import_filter = {
    N_("XFig File Format"),
    extensions,
    import_fig
};
