/* GNUPLOT -- mif.trm */
/*
 * Copyright (C) 1992, 1993
 *
 * Permission to use, copy, and distribute this software and its
 * documentation for any purpose with or without fee is hereby granted, 
 * provided that the above copyright notice appear in all copies and 
 * that both that copyright notice and this permission notice appear 
 * in supporting documentation.
 *
 * Permission to modify the software is granted, but not the right to
 * distribute the modified code.  Modifications are to be distributed 
 * as patches to released version.
 *  
 * This software  is provided "as is" without express or implied warranty.
 * 
 * This file is included by ../term.c.
 *
 * This terminal driver was developed for
 *      gnuplot for unix version 3.0 (patchlevel 1)
 *      gnuplot for unix version 3.2 (patchlevel 2)
 *
 * This terminal driver supports:
 *      Frame Maker MIF format version 3.00
 *
 * Options for this terminal driver (set terminal mif [options]):
 *      colour /        Draw primitives with line types >= 0 in colour (sep. 2-7)
 *      monochrome      Draw primitives in black (sep. 0)
 *
 *      polyline /      Draw lines as continous curves
 *      vectors         Draw lines as collections of vectors
 *
 *      help / ?        Print short usage description on stderr
 *
 * Properties for this terminal driver:
 *     -Gnuplot size of worksheet:              MIF_XMAX * MIF_YMAX
 *     -Unit in MIF output:                     cm
 *     -Plot primitives with the same pen will
 *      be grouped in the same MIF group.
 *     -Plot primitives with line types >= 0
 *      will as default be drawn in colour.
 *     -Lines are plotted as collections of
 *      vectors, or as continous lines (default)
 *     -Plot primitives in a plot will be in a
 *      Frame in MIF. Several plot Frames will
 *      be collected in one large Frame.
 *     -Point size of MIF output characters:    MIF_PSIZE
 *     -Used font for MIF output characters:    Times
 *     -Supports vertical text
 *
 * AUTHORS:
 *      Olof Franksson, Physics IV, KTH, S-100 44 Stockholm, Sweden
 * 
 * COMMENTS:
 *      Send comments and/or suggestions to olof@fysik4.kth.se
 * 
 * CHANGES:
 *      Changed order of routine declarations.          olof@fysik4.kth.se
 *      Changed mechanism for pen pattern selection.    kssingvo@immd4.informatik.uni-erlangen.de
 *      Support for vertical text.                      kssingvo@immd4.informatik.uni-erlangen.de
 *      Fixed plot bug for "set size XS,YS", XS/YS > 1. olof@fysik4.kth.se
 *
 */
#if !defined(cfree)
#define cfree free
#endif

/** Coordinates **/
#define GNP_TO_MIF(P)   (((float) (P)) / 1000.0)        /* Converts gnuplot units to MIF units */
#define MIF_XMAX 15000                  /* Basic unit: 0.01 mm (15cm -> 15*10*100=15000) */
#define MIF_YMAX 10000                  /* Basic unit: 0.01 mm (10cm -> 10*10*100=10000) */

#define MIF_XLAST (MIF_XMAX - 1)
#define MIF_YLAST (MIF_YMAX - 1)

static struct mif_line { /* Line point structure specification */
	float fpos_x;           /* Line point X coordinate */
	float fpos_y;           /*            Y coordinate */
	struct mif_line *next;  /* Pointer to next line point */
	struct mif_line *prev;  /* Pointer to previous line point */
} mif_line = { /* Current position structure. Adjust for orign. Local for this file. */
	GNP_TO_MIF(0),
	GNP_TO_MIF(MIF_YLAST),
	&mif_line,
	&mif_line
};

/** Characters **/
#define MIF_PSIZE 9                     /* Point size of used characters */

#define MIF_VCHAR (MIF_YMAX/31)         /* Distance between rows (a guess) */
#define MIF_HCHAR (MIF_XMAX/95)         /* Distance between characters (a guess) */

/** Scale marks **/
#define MIF_VTIC  (MIF_YMAX/150)        /* Size of scale mark (vert) */
#define MIF_HTIC  (MIF_XMAX/225)        /* Size of scale mark (hor) */

/** Drawing properties **/
static char mif_justify[64];                    /* How to justify the used text */
static char mif_pen[64], mif_pen_width[64], mif_separation[64];         /* How to plot */

#define MIF_TEXT_HOR  0
#define MIF_TEXT_VER  1
static int mif_text_ang = MIF_TEXT_HOR;         /* Rotation angle of text */

#define MIF_NPENS 16                    /* Number of MIF pen types */
static int mif_pentype = 0;             /* Pen type to use. Also used to create groups for graphics */
#define MIF_PEN_TO_GROUP(P)     ( 1 + (P) )     /* Map pen type to group number. Must be >= 1 */

static int mif_pattern_table[MIF_NPENS] = {     /* Table, which pattern should be used for drawing */
    0,                          /* border  */
    1,                          /* not used */
    2, 3, 4, 8, 12, 13,         /* other lines: functions, data, ... (5 is used for grid; 6,7 is (nearly) invisible) */
    5,                          /* grid */
    9, 10, 11, 12, 13, 14, 15   /* not used */
};

/** MIF groups administration **/
#define MIF_NGROUP_ID           20
static struct mif_group_id {
	int group_existance;
#define MIF_GROUP_EXISTS        1       /* This group id should generate a MIF group */
#define MIF_GROUP_NOT_EXISTS    0       /* This group id should not generate a MIF group */

	int group_id;
#define MIF_INVALID_GROUP_ID    0       /* An invalid MIF group ID */

} mif_group_id[MIF_NGROUP_ID];            /* List of used group ID:s and corresponding MIF groups existance */

/** Semaphores **/
static int mif_initialized = 0;		/* != 0 when output is active */
static int mif_in_frame = 0;            /* != 0 when inside a plot frame */
static int mif_frameno = -1;            /* Current frame number */
static int mif_colour = TRUE;           /* == TRUE when colour should be used */
static int mif_polyline = TRUE;         /* == TRUE when lines are drawn as continous curves */

/** Declaration of routine/s for internal use **/
static int insert_mif_line(), proc_group_id();

/** Routine/s **/

/* Called when this terminal type is set in order to parse options */
MIF_options()
{
	extern char term_options[];     /* Declared in ../setshow.c */

	if (!END_OF_COMMAND) {
		/* Colour options */
		if (!END_OF_COMMAND && almost_equals(c_token, "m$onochrome")) {    /* Compare up to $ */
			mif_colour = FALSE;
			c_token++;
		}
		if (!END_OF_COMMAND && almost_equals(c_token, "c$olour")) {   /* Compare up to $ */
			mif_colour = TRUE;
			c_token++;
		}

		/* Curve options */
		if (!END_OF_COMMAND && almost_equals(c_token, "v$ectors")) {   /* Compare up to $ */
			mif_polyline = FALSE;
			c_token++;
		}
		if (!END_OF_COMMAND && almost_equals(c_token, "p$olyline")) {   /* Compare up to $ */
			mif_polyline = TRUE;
			c_token++;
		}

		/* Short help */
		if ( !END_OF_COMMAND &&
		     (almost_equals(c_token, "h$elp") ||
		      almost_equals(c_token, "?$")) ) { /* Compare up to $ */
			fprintf(stderr, "Usage: set terminal mif [options]\n");
			fprintf(stderr, "\toptions:\n");
			fprintf(stderr, "\t\tcolour /        Draw primitives with line types >= 0 in colour (sep. 2-7)\n");
			fprintf(stderr, "\t\tmonochrome      Draw primitives in black (sep. 0)                        \n");
			fprintf(stderr, "\n");
			fprintf(stderr, "\t\tpolyline /      Draw lines as continous curves                           \n");
			fprintf(stderr, "\t\tvectors         Draw lines as collections of vectors                     \n");
			fprintf(stderr, "\n");
			fprintf(stderr, "\t\thelp / ?        Print short usage description on stderr                  \n");

			c_token++;
		}
	}

	sprintf(term_options, "%s %s", (mif_colour == TRUE)? "colour": "monochrome",
				       (mif_polyline == TRUE)? "polyline": "vectors");
}

/* Deallocate the used line structure elements */
static int free_mif_line()
{
	struct mif_line *tline;

	while (mif_line.prev != &mif_line) {
		/* Unlink */
		tline = mif_line.prev;
		mif_line.prev = mif_line.prev->prev;
		mif_line.prev->next = &mif_line;

		/* Deallocate */
		free(tline);
	}

	/* Make sure that the list will be empty */
	mif_line.prev = &mif_line;
	mif_line.next = &mif_line;
}

/* Draw the pending line. Change current position. */
static int put_mif_line()
{
	int np, i;
	struct mif_line *tline;

	/* Process if inside a Frame */
	if (mif_initialized != 0 && mif_in_frame != 0) {

		/* Count the number of available points */
		for (tline = mif_line.next, np = 1; tline != &mif_line; tline = tline->next, np++)
			;

		/* Draw line (at least two points) */
		if (np >= 2) {
			/* Line preamble */
			fprintf(outfile, "\t<PolyLine <GroupID %d> %s %s %s\n",
					 MIF_PEN_TO_GROUP(mif_pentype), mif_pen, mif_pen_width, mif_separation);

			/* Draw the line elements */
			fprintf(outfile, "\t\t<NumPoints %d> ", np);
			for (i = 0, tline = &mif_line; i < np; i++, tline = tline->next) {
				if (i%4 == 0)
					fprintf(outfile, "\n\t\t");
				fprintf(outfile, "<Point  %.3f %.3f> ", tline->fpos_x, tline->fpos_y);
			}

			/* Line post amble */
			fprintf(outfile, "\n\t>\n");

			/* Register the used group ID */
			proc_group_id(MIF_PEN_TO_GROUP(mif_pentype));

			/* Avoid to redraw this. The MIF system should remember it. */
			mif_pen[0] = '\0';
			mif_pen_width[0] = '\0';
			mif_separation[0] = '\0';

			/* Move current position to end of line */
			mif_line.fpos_x = mif_line.prev->fpos_x;
			mif_line.fpos_y = mif_line.prev->fpos_y;

			/* Restore the line */
			free_mif_line();
		}

	} /* Line processed */
}

/* Set up a MIF output file */
MIF_init()
{
	int i;

	extern char version[];		/* Declared in ../version.c */
	extern char patchlevel[];

	/* Process if not inside a MIF file and Frame */
	if (mif_initialized == 0 && mif_in_frame == 0) {
		/* Tell this terminal driver that the output is initialized and no current frames are processed */
		mif_initialized = 1;
		mif_in_frame = 0;

		/* Reset internal position */
		free_mif_line();
		mif_line.fpos_x = GNP_TO_MIF(0);
		mif_line.fpos_y = GNP_TO_MIF(MIF_YLAST);

		/* Reset drawing properties strings */
		mif_pen[0] = '\0';
		mif_pen_width[0] = '\0';
		mif_separation[0] = '\0';
		sprintf(mif_justify, " <TLAlignment Left> ");

		/* Reset group ID generator */
		for (i = 0; i < MIF_NGROUP_ID; i++) {
			mif_group_id[i].group_id = MIF_INVALID_GROUP_ID;
			mif_group_id[i].group_existance = MIF_GROUP_NOT_EXISTS;
		}

		/* Identify ourselves */
		fprintf(outfile, "<MIFFile 3.00> # Generated by gnuplot version %s patchlevel %s; identifies this as a MIF file\n", version, patchlevel);
		fprintf(outfile, "#\n");

		/* Setup a default environment to use */
		fprintf(outfile, "# Set a default pen pattern, pen width, unit and font for subsequent objects\n");
		fprintf(outfile, "<Pen 0>\n");
		fprintf(outfile, "<Fill 15>\n");
		fprintf(outfile, "<PenWidth 0.5 pt>\n");
		fprintf(outfile, "<Separation 0>\n");
		fprintf(outfile, "<Units Ucm>\n");
		fprintf(outfile, "<Font <FFamily `Times'> <FSize %d> <FPlain Yes>>\n", MIF_PSIZE);
		fprintf(outfile, "#\n");
	} /* MIF file created */
}

/* Finish of a MIF output file */
MIF_reset()
{
	/* Process if inside a MIF file and not inside a Frame */
	if (mif_initialized != 0 && mif_in_frame == 0) {
		/* Finish off the MIF file */
		fprintf(outfile, "#\n");
		fprintf(outfile, "# End of MIFFile\n");

		/* Tell this terminal driver that the output is finished */
		mif_initialized = 0;
	} /* MIF file finished */
}

/* Start plotting a Frame (-> graphics mode) */
MIF_graphics()
{
	int i;

	/* Process if not inside a Frame */
	if (mif_initialized != 0 && mif_in_frame == 0) {
		/* Tell that this terminal driver is working with a plot frame */
		mif_in_frame = 1;

		/* Update frame number */
		mif_frameno++;

		/* Set current position */
		free_mif_line();
		mif_line.fpos_x = GNP_TO_MIF(0);
		mif_line.fpos_y = GNP_TO_MIF(MIF_YLAST);

		/* Set drawing properties */
		mif_pen[0] = '\0';
		mif_pen_width[0] = '\0';
		mif_separation[0] = '\0';
		sprintf(mif_justify, " <TLAlignment Left> ");

		/* Reset group ID generator */
		for (i = 0; i < MIF_NGROUP_ID; i++) {
			mif_group_id[i].group_id = MIF_INVALID_GROUP_ID;
			mif_group_id[i].group_existance = MIF_GROUP_NOT_EXISTS;
		}

		/* Frame preamble */
		fprintf(outfile, "#\n");
		fprintf(outfile, "# Frame number %d with plot of graphics\n", mif_frameno);
		fprintf(outfile, "<Frame\n");
		fprintf(outfile, "\t<Pen 15>\n");
		fprintf(outfile, "\t<Fill 15>\n");
		fprintf(outfile, "\t<PenWidth  0.5 pt>\n");
		fprintf(outfile, "\t<Separation 0>\n");
		fprintf(outfile, "\t<BRect 0.000 %.3f %.3f %.3f>\n",
				 ((float) mif_frameno)*GNP_TO_MIF(MIF_YMAX+100), GNP_TO_MIF(MIF_XMAX), GNP_TO_MIF(MIF_YMAX));
		fprintf(outfile, "\t<NSOffset  0.000>\n");
		fprintf(outfile, "\t<BLOffset  0.000>\n");
	} /* Frame created */
}

/* Stop plotting a Frame (-> text mode) */
MIF_text()
{
	int i;

	/* Process if inside a Frame */
	if (mif_initialized != 0 && mif_in_frame != 0) {

		/* Draw pending line */
		if (mif_polyline == TRUE)
			put_mif_line();

		/* Group the used plot primitives */
		fprintf(outfile, "\t#\n");
		fprintf(outfile, "\t# Group the the objects in groups to make the chart easier to manipulate\n");
		fprintf(outfile, "\t# after it's imported into FrameMaker.\n");

		for (i = 0; i < MIF_NGROUP_ID; i++) {
			if (mif_group_id[i].group_id != MIF_INVALID_GROUP_ID &&
			    mif_group_id[i].group_existance == MIF_GROUP_EXISTS) {
				fprintf(outfile, "\t<Group\n");
				fprintf(outfile, "\t\t<ID %d>\n", mif_group_id[i].group_id);
				fprintf(outfile, "\t>\n");
			}
		}

		/* Frame post amble */
		fprintf(outfile, ">\n");
		fprintf(outfile, "# End of Frame number %d\n", mif_frameno);
		fprintf(outfile, "#\n");

		/* Tell that this terminal driver is not working with a plot frame */
		mif_in_frame = 0;
	} /* Frame finshed */
}

/* Select type of line in grapics */
/* NOTE: actually written to output the first time a primitive is drawn AFTER this call */
MIF_linetype(linetype)
int linetype;           /* -2=border, -1=X/Y-axis, 0-13=lines, and 14-=mapped back */
{
	/* Process if inside a Frame */
	if (mif_initialized != 0 && mif_in_frame != 0) {

		/* Draw pending line */
		if (mif_polyline == TRUE)
			put_mif_line();

		/* Translate gnuplot pen types to MIF pen types */
		if (linetype < 0) {     /* Special lines */
			if (linetype == -1) {
				mif_pentype = 8+MIF_NPENS;      /* -1 */
				if (mif_colour == TRUE)
					sprintf(mif_separation, " <Separation 0> ");
			}
			else {
				mif_pentype = 0+MIF_NPENS;      /* -2 or less */
				if (mif_colour == TRUE)
					sprintf(mif_separation, " <Separation 0> ");
			}
			sprintf(mif_pen_width, " <PenWidth 1.0 pt> ");
		}
		else {                  /* Normal lines */
			mif_pentype = (linetype)%MIF_NPENS;     /* 0-(MIF_NPENS-1) */
			sprintf(mif_pen_width, " <PenWidth 0.5 pt> ");
			if (mif_colour == TRUE)
				sprintf(mif_separation, " <Separation %d> ", 2+(mif_pentype%6));        /* 2-7 */
		}

		/* Set pen type */
		sprintf(mif_pen, " <Pen %d> ", mif_pattern_table[mif_pentype%MIF_NPENS]);

	} /* Primitive processed */
}

/* Draw the text horisontally or vertically (90 degrees counterclockwise) */
int MIF_text_angle(ang)
int ang;
{
    if (ang != 0)
	mif_text_ang = MIF_TEXT_VER;
    else
	mif_text_ang = MIF_TEXT_HOR;

    return(TRUE);
}

/* Justify following text lines (MIF_put_text()) relative to the insertion point */
MIF_justify_text(mode)
/* NOTE: actually written to output in text primitives which are drawn AFTER this call */
enum JUSTIFY mode;
{
	int rval = TRUE;

	/* Process if inside a Frame */
	if (mif_initialized != 0 && mif_in_frame != 0) {
		switch (mode) {
		case LEFT:
			sprintf(mif_justify, " <TLAlignment Left> ");
			break;
		case CENTRE:
			sprintf(mif_justify, " <TLAlignment Center> ");
			break;
		case RIGHT:
			sprintf(mif_justify, " <TLAlignment Right> ");
			break;
		default:
			rval = FALSE;
			break;
		}

	} /* Primitive processed */
	else {
		rval = FALSE;
	}

	return(rval);
}

/* Draw a vector from current position to (x, y) and change current position. */
/* NOTE: actually written to output the first time another primitive is called AFTER this call */
MIF_vector(x, y)
unsigned int x, y;
{
	/* Process if inside a Frame */
	if (mif_initialized != 0 && mif_in_frame != 0) {

		/* Setup the vector as a part of the line */
		insert_mif_line(GNP_TO_MIF(x), GNP_TO_MIF(MIF_YLAST-(int)y));

		/* Draw pending line -> vector */
		if (mif_polyline == FALSE)
			put_mif_line();

	} /* Vector processed */
}

/* Move current position */
MIF_move(x, y)
unsigned int x, y;
{
	/* Process if inside a Frame */
	if (mif_initialized != 0 && mif_in_frame != 0) {

		/* Draw pending line */
		if (mif_polyline == TRUE)
			put_mif_line();

		mif_line.fpos_x = GNP_TO_MIF(x);
		mif_line.fpos_y = GNP_TO_MIF(MIF_YLAST-(int)y);
	}
}

/* Draw the text string str at (x, y). Adjust according to MIF_justify_text(). Change current position. */
MIF_put_text(x, y, str)
unsigned int x, y;
char str[];
{
	/* Process if inside a Frame */
	if (mif_initialized != 0 && mif_in_frame != 0) {

		/* Draw pending line */
		if (mif_polyline == TRUE)
			put_mif_line();

		/* Adjust current position for text-graphics alignment */
		MIF_move(x, y-MIF_VCHAR/5);

		if (strlen(str) > 0) {

			/* Draw the text */
			fprintf(outfile, "\t<TextLine <GroupID %d> %s %s %s\n",
					 MIF_PEN_TO_GROUP(mif_pentype), mif_pen, mif_pen_width, mif_separation);
			fprintf(outfile, "\t\t<TLOrigin  %.3f %.3f> %s %s <String `%s'>\n",
					 mif_line.fpos_x, mif_line.fpos_y, mif_justify,
					 (mif_text_ang == MIF_TEXT_VER)? "<Angle 90>": "",
					 str);
			fprintf(outfile, "\t>\n");

			/* Register the used group ID */
			proc_group_id(MIF_PEN_TO_GROUP(mif_pentype));

			/* Avoid to redraw this. The MIF system should remember it. */
			mif_pen[0] = '\0';
			mif_pen_width[0] = '\0';
			mif_separation[0] = '\0';

			mif_justify[0] = '\0';  /* Independent of linetype */
		}
	} /* Text processed */
}


/* Insert one point in the line */
static int insert_mif_line(fx, fy)
float fx, fy;
{
	int rval = TRUE;

	if ((mif_line.prev->next = (struct mif_line *) alloc(sizeof(struct mif_line),"MIF driver")) != (struct mif_line *) NULL) {
		/* Link */
		mif_line.prev->next->next = &mif_line;
		mif_line.prev->next->prev = mif_line.prev;
		mif_line.prev = mif_line.prev->next;

		/* Fill */
		mif_line.prev->fpos_x = fx;
		mif_line.prev->fpos_y = fy;

		rval = TRUE;
	}
	else { /* Failed to allocate */
		/* Relink */
		mif_line.prev->next = &mif_line;

		rval = FALSE;
	}

	return(rval);
}


/* Register group ID. Update group ID existance. */
/* Returns:     1       group_id belongs to a MIF group
		0       group_id does not belong to a MIF group
	       -1       not inside a Frame
	       -2       group ID list is full
 */
static int proc_group_id(group_id)
int group_id;
{
	int i, rval = 0;

	/* Process if inside a Frame */
	if (mif_initialized != 0 && mif_in_frame != 0) {

		/* Find out the group ID, or a free group ID slot index. */
		for (i = 0; i < MIF_NGROUP_ID &&
			    mif_group_id[i].group_id != MIF_INVALID_GROUP_ID &&
			    mif_group_id[i].group_id != group_id;
										i++) {
			/* Don't check the group_existance variable */
		}

		if (i < MIF_NGROUP_ID) {
			if (mif_group_id[i].group_id == MIF_INVALID_GROUP_ID) {
				/* Register as new group ID for eventual use as MIF group */
				mif_group_id[i].group_id = group_id;
				mif_group_id[i].group_existance = MIF_GROUP_NOT_EXISTS;
			}
			else {
				/* If second use of this group ID -> create a new MIF group */
				if (mif_group_id[i].group_id == group_id) {
					mif_group_id[i].group_existance = MIF_GROUP_EXISTS;
					/* NOTE: a group MUST have at least two members. */
					rval = 1;
				}
			}
		}
		else {
			rval = -2;      /* No place for this group ID in the list */
		}

	} /* Group ID processed */
	else {
		rval = -1;      /* Not inside a Frame */
	}

	/* Return MIF group status */
	return(rval);
}

