/*
 *	Euler - a numerical lab
 *
 *	platform : neutral
 *
 *	file : meta.c -- portable independant graphic system
 */


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <math.h>
#include <float.h>
#include <time.h>

#include "sysdep.h"
#include "stack.h"
#include "output.h"
#include "meta.h"


static double delay=0;


double getcolor (int i, int j);

extern int fillcolor1,fillcolor2,usecolors;

double pswidth=15,psheight=15;

typedef struct metastruct {
	char		*start, *end;
	size_t		size;
	int			full, active, recording, playingframe;
	long		nframes;
	int			width, height;
	int			wchar, hchar;
	int			lines;
	short		colors[3][MAX_COLORS];
	metadevice	*device;
} metastruct;

static struct metastruct *m = NULL;

static void commandwrite (int i);
static void longwrite (double n);
static void shortwrite (short n);
static void stringwrite (char *s);
static int nextcommand (char **p);
static double nextlong (char **p);
static int nextint (char **p);
//static void showcommand(int cmd);

/****************************************************************************
 *	public metafile api
 *
 ****************************************************************************/
 
int openmeta(int size, metadevice *device)
{
	m = (metastruct*)malloc(sizeof(metastruct));
	
	if (m) {
		m->size = size*1024L;
		m->full = 0;
		m->active = 1;
		m->recording = 0;
		m->playingframe = 0;
		m->device = device;
		m->nframes = 0;
		m->lines = 40;
		m->wchar = m->hchar = 1;
		m->height = m->width = 0;
		m->start = m->end = (char*)malloc(m->size);
		if (m->start)
			return 1;
	}
	return 0;
}

void closemeta()
{
	if (m) {
		if (m->start)
			free(m->start);
		free(m);
	}
}

metadevice * setmetadevice(metadevice *d)
{
	metadevice *old = m->device;
	
	if (d)
		m->device = d;
	
	return old;
}

metadevice * getmetadevice()
{
	return m->device;
}

void setmetawidth(int width)
{
	m->width = width;
}

void setmetaheight(int height)
{
	m->height = height;
}

int getmetawidth()
{
	return m->width;
}

int getmetaheight()
{
	return m->height;
}

void setmetacharwidth(int w)
{
	m->wchar = w;
}

void setmetacharheight(int h)
{
	m->hchar = h;
}

int getmetacharwidth()
{
	return m->wchar;
}

int getmetacharheight()
{
	return m->hchar;
}

void setmetalines(int lines)
{
	m->lines = lines;
}

int getmetalines()
{
	return m->lines;
}

void setmetacolors(short colors[3][MAX_COLORS])
{
	int i, j;
	
	for (i=0 ; i<3 ; i++)
		for (j=0 ; j<MAX_COLORS ; j++)
			m->colors[i][j] = colors[i][j];
}

short getmetacolor(int rgb, int index)
{
	return m->colors[rgb][index];
}

void playmeta()
{
	char *p=m->start;
	double c,r,c1,r1,cc[16],hue;
	int col,st,width,n,i,co[16],again=-1;
	
	if (m->recording || !m->device) return;
	m->active = 0;
	
	
	if (!m->playingframe) again = 1;
	
	while (p<m->end)
	{   int command=nextcommand(&p);
    	switch(command)
		{   case 1 :
				if (!m->playingframe && !again) {
					m->active = 1;
					return;
				} else again--;
				
				if (m->playingframe) {
					sys_wait(delay,&n);
					if (n==escape) {
						m->active = 1;
						return;
					}
//					else if (n) {
//						m->active=1;
//						return;
//					}
				}
        		m->device->clear(m->device->data);
                break;
        	case 2 :
				c=nextlong(&p);
				r=nextlong(&p);
				c1=nextlong(&p);
				r1=nextlong(&p);
				m->device->clip(m->device->data,c,r,c1,r1);
				break;
        	case 10 :
				c=nextlong(&p);
				r=nextlong(&p);
				c1=nextlong(&p);
				r1=nextlong(&p);
				col=nextint(&p);
				st=nextint(&p);
				width=nextint(&p);
				m->device->line(m->device->data,c,r,c1,r1,col,st,width);
				break;
			case 20 :
				c=nextlong(&p);
				r=nextlong(&p);
				col=nextint(&p);
				st=nextint(&p);
				m->device->marker(m->device->data,c,r,col,st);
				break;
			case 30 :
				n=nextint(&p);
				for (i=0; i<n; i++)
				{	cc[2*i]=nextlong(&p);
					cc[2*i+1]=nextlong(&p);
					co[i]=nextint(&p);
				}
				st=nextint(&p);
				m->device->fill(m->device->data,cc,st,n,co);
				break;
			case 31 :
				for (i=0; i<8; i++) cc[i]=nextlong(&p);
				hue=nextlong(&p);
				col=nextint(&p);
				st=nextint(&p);
				m->device->fillh(m->device->data,cc,hue,col,st);
				break;
			case 32 :
				c=nextlong(&p);
				r=nextlong(&p);
				c1=nextlong(&p);
				r1=nextlong(&p);
				hue=nextlong(&p);
				col=nextint(&p);
				st=nextint(&p);
				m->device->bar(m->device->data,c,r,c1,r1,hue,col,st);
				break;
			case 33 :
				c=nextlong(&p);
				r=nextlong(&p);
				c1=nextlong(&p);
				r1=nextlong(&p);
				col=nextint(&p);
				st=nextint(&p);
				m->device->bar1(m->device->data,c,r,c1,r1,col,st);
				break;
			case 40 :
				c=nextlong(&p);
				r=nextlong(&p);
				col=nextint(&p);
				st=nextint(&p);
				m->device->text(m->device->data,c,r,p,col,st);
				p+=strlen(p)+1;
				break;
			case 41 :
				c=nextlong(&p);
				r=nextlong(&p);
				col=nextint(&p);
				st=nextint(&p);
				m->device->vtext(m->device->data,c,r,p,col,st);
				p+=strlen(p)+1;
				break;
			case 42 :
				c=nextlong(&p);
				r=nextlong(&p);
				col=nextint(&p);
				st=nextint(&p);
				m->device->vutext(m->device->data,c,r,p,col,st);
				p+=strlen(p)+1;
				break;
			case 50 :
				nextlong(&p);
				break;
			default :
            	break;
		}
	}
	m->active = 1;
}

/****************************************************************************
 *	frame support
 *
 ****************************************************************************/

void mbeginframes(header *hd)
{
//	gclear();
	m->recording = 1;
	m->end = m->start;
	m->full = 0;
	m->nframes = 0;
	new_real(m->nframes,"");
}

void mendframes(header *h)
{
//	gclear();
	m->recording = 0;
	new_real(m->nframes,"");
}

void mframes (header *hd)
{
	new_real(m->nframes,"");
}

void mplayframes(header *hd)
{
	header *st=hd,*result;
	hd=getvalue(hd);
	if (error) return;
	if (hd->type!=s_real) wrong_arg_in("play");
	graphic_mode();
	delay = *realof(hd);
	m->playingframe = 1;
	m->device->begin(m->device->data);
	playmeta();
	m->device->end(m->device->data);
	m->playingframe = 0;
	delay=0.0;
	result=new_real(m->nframes,"");
	moveresult(st,result);
}

/****************************************************************************
 *	basic drawing commands
 *
 ****************************************************************************/

void gclear()
{
	if (!m->active) return;

	if (m->recording) {
		m->nframes++;
	} else {
		m->end = m->start;
		m->full = 0;
		m->nframes = 0;
	}
	commandwrite(1);
	if (!m->recording && m->device && m->device->clear)
		m->device->clear(m->device->data);
}

void gclip(double c, double r, double c1, double r1)
{
	if (!m->active) return;
	if (fabs(r)>10000.0) return;
	if (fabs(c)>10000.0) return;
	if (fabs(r1)>10000.0) return;
	if (fabs(c1)>10000.0) return;
	commandwrite(2);
	longwrite(c);
	longwrite(r);
	longwrite(c1);
	longwrite(r1);
	
	if (!m->recording && m->device && m->device->clip)
		m->device->clip(m->device->data,c,r,c1,r1);
}

/***** gline
	draw a line.
	col is the color, where 0 should be white and 1 black.
	st is a style from linetyp.
	width is the linewidth, where 0 or 1 are equal defaults.
*****/
void gline(double c, double r, double c1, double r1, int color, int st, int width)
{
	if (!m->active) return;
	if (fabs(r)>10000.0) return;
	if (fabs(c)>10000.0) return;
	if (fabs(r1)>10000.0) return;
	if (fabs(c1)>10000.0) return;
	commandwrite(10);
	longwrite(c);
	longwrite(r);
	longwrite(c1);
	longwrite(r1);
	shortwrite(color);
	shortwrite(st);
	shortwrite(width);
	if (!m->recording && m->device && m->device->line)
		m->device->line(m->device->data,c,r,c1,r1,color,st,width);
}

/***** gmarker
	plot a single marker on screen.
	col is the color.
	type is a type from markertyp.
*****/
void gmarker(double c, double r, int color, int type)
{
	if (!m->active) return;
	commandwrite(20);
	longwrite(c);
	longwrite(r);
	shortwrite(color);
	shortwrite(type);
	if (!m->recording && m->device && m->device->marker)
		m->device->marker(m->device->data,c,r,color,type);
}

void gfill(double c[], int st, int n, int connect[])
{
	int i;
	if (!m->active) return;
	for (i=0; i<2*n; i++) if (fabs(c[i])>10000.0) return;
	commandwrite(30);
	shortwrite(n);
	for (i=0; i<n; i++) {
		longwrite(c[2*i]);
		longwrite(c[2*i+1]);
		shortwrite(connect[i]);
	}
	shortwrite(st);
	if (!m->recording && m->device && m->device->fill)
		m->device->fill(m->device->data,c,st,n,connect);
}

/***** Draw a filled polygon.
	Works like gfill, but uses hue.
*****/
void gfillh(double c[8], double hue, int color, int connect)
{
	int i;
	if (!m->active) return;
	for (i=0; i<8; i++) if (fabs(c[i])>10000.0) return;
	hue-=floor(hue);
	commandwrite(31);
	for (i=0; i<8; i+=2) {
		longwrite(c[i]);
		longwrite(c[i+1]);
	}
	longwrite(hue);
	shortwrite(color);
	shortwrite(connect);
	if (!m->recording && m->device && m->device->fillh)
		m->device->fillh(m->device->data,c,hue,color,connect);
}

/***** Draw a rectangle.
	hue is a hue intensity from 0 to 1.
	style determines, if a black boundary should be drawn.
******/
void gbar(double c, double r, double c1, double r1, double hue, int color, int style)
{
	if (!m->active) return;
	commandwrite(32);
	longwrite(c);
	longwrite(r);
	longwrite(c1);
	longwrite(r1);
	longwrite(hue);
	shortwrite(color);
    shortwrite(style);
    if (!m->recording && m->device && m->device->bar)
    	m->device->bar(m->device->data,c,r,c1,r1,hue,color,style);
}

/***** Draw a rectangle.
	hue is a hue intensity from 0 to 1.
	style determines, if a black boundary should be drawn.
******/
void gbar1(double c, double r, double c1, double r1, int color, int style)
{
	if (!m->active) return;
	commandwrite(33);
	longwrite(c);
	longwrite(r);
	longwrite(c1);
	longwrite(r1);
	shortwrite(color);
	shortwrite(style);
    if (!m->recording && m->device && m->device->bar1)
    	m->device->bar1(m->device->data,c,r,c1,r1,color,style);
}

/***** gtext
	output a graphic text on screen.
	alignment is left=0, centered=1, right=2.
*****/
void gtext(double c, double r, char *text, int color, int alignment)
{
	if (!m->active) return;
	commandwrite(40);
	longwrite(c);
	longwrite(r);
	shortwrite(color);
	shortwrite(alignment);
	stringwrite(text);
	if (!m->recording && m->device && m->device->text)
		m->device->text(m->device->data,c,r,text,color,alignment);
}

/***** gvtext
	like gtext downwards
*****/
void gvtext(double c, double r, char *text, int color, int alignment)
{
	if (!m->active) return;
	commandwrite(41);
	longwrite(c);
	longwrite(r);
	shortwrite(color);
	shortwrite(alignment);
	stringwrite(text);
	if (!m->recording && m->device && m->device->vtext)
		m->device->vtext(m->device->data,c,r,text,color,alignment);
}

/***** gvutext
	like gtext upwards.
*****/
void gvutext(double c, double r, char *text, int color, int alignment)
{
	if (!m->active) return;
	commandwrite(42);
	longwrite(c);
	longwrite(r);
	shortwrite(color);
	shortwrite(alignment);
	stringwrite(text);
	if (!m->recording && m->device && m->device->vutext)
		m->device->vutext(m->device->data,c,r,text,color,alignment);
}

void gscale (double s)
{
	if (!m->active) return;
	commandwrite(50);
	longwrite(s*1000);
	if (!m->recording && m->device && m->device->scale)
		m->device->scale(m->device->data,s);
}

int dump_meta (char *filename)
{
	FILE *out=fopen(filename,"w");
	if (!out) return 0;
	
	fwrite(m->start,m->end-m->start,1,out);
	
	fclose(out);
	return 1;
}

void pswindow (double w, double h)
{	pswidth=w;
	psheight=h;
}

/****************************************************************************
 *	private metafile
 *
 ****************************************************************************/

static void write (void *l, int n)
{   if (!m->active || m->full) return;
	memmove(m->end,l,n);
	m->end+=n;
}

static void commandwrite (int i)
{
   if (!m->active) return;
	if ((int)(m->end-m->start)>(int)(m->size-512)) { m->full=1; return; }
	*m->end++=i;
}

/*****
	write a double to the metafile as long
*****/
static void longwrite (double n)
{
	long k=(long)(n*1000.0);
	write(&k,sizeof(long));
}

/*****
	write an int to the metafile
*****/
static void shortwrite (short n)
{	write(&n,sizeof(short));
}

/*****
	write a string to the metafile
*****/
static void stringwrite (char *s)
{	write(s,strlen(s)+1);
}

static int nextcommand (char **p)
{	int k=**p; (*p)++; return k;
}

static double nextlong (char **p)
{	long x;
	memmove(&x,*p,sizeof(long));
	(*p)+=sizeof(long);
	return x/1000.0;
}

static int nextint (char **p)
{   short n;
	memmove(&n,*p,sizeof(short));
	(*p)+=sizeof(short);
	return n;
}

#if 0
static void showcommand(int cmd)
{
	switch (cmd) {
		case 1 :
			fprintf(stderr,"%2d : clear\n",cmd);
			break;
		case 2 :
			fprintf(stderr,"%2d : clip\n",cmd);
			break;
		case 10 :
			fprintf(stderr,"%2d : line\n",cmd);
			break;
		case 20 :
			fprintf(stderr,"%2d : marker\n",cmd);
			break;
		case 30 :
			fprintf(stderr,"%2d : fill\n",cmd);
			break;
		case 31 :
			fprintf(stderr,"%2d : fillh\n",cmd);
			break;
		case 32 :
			fprintf(stderr,"%2d : bar\n",cmd);
			break;
		case 33 :
			fprintf(stderr,"%2d : bar1\n",cmd);
			break;
		case 40 :
			fprintf(stderr,"%2d : text\n",cmd);
			break;
		case 41 :
			fprintf(stderr,"%2d : vtext\n",cmd);
			break;
		case 42 :
			fprintf(stderr,"%2d : vutext\n",cmd);
			break;
		case 50 :
			fprintf(stderr,"%2d : scale\n",cmd);
			break;
		default :
			fprintf(stderr,"%2d : unknown\n",cmd);
			break;

	}
}
#endif
