/* This file is part of `mogul'
 * Copyright (c) 1995 David Deaven
 *
 * In the following, "you" and "You" refer to any entity other than 
 * the Microsoft corporation and its subsidiaries.  All rights are
 * reserved by the author in connection with Microsoft and its
 * subsidiaries.  The Microsoft Network is explicity denied the right
 * to copy, reproduce, or transmit this software.
 *
 *   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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Author: deaven $
 * $Id: device.c,v 2.1 1996/07/11 18:41:37 deaven Exp deaven $
 */

#include <stdio.h>
#include <math.h>
#include <string.h>
#include <errno.h>
#include "dmd/memory.h"
#include "dmd/dmdtimer.h"
#include "mogul.h"

int MogulErrno = 0;

#define DEFINE_DEVICE(xxx) \
	{ extern MogulDevice xxx; InstallDevice(xxx); }

static MogulDevice *Devices = NULL;
static int ndevice = 0;

static char *msgtab[MoErrMax] = {
	"normal operation",
	"invalid device instance",
	"bad script",
	"<system error>"
};

static void InstallDevice(MogulDevice new)
{
  Devices = myrealloc(Devices, (ndevice + 1) * sizeof(MogulDevice *));
  Devices[ndevice++] = new;
}

void MogulDeviceInit(void)
{
  static int done = 0;
  if(!done) {
#   include "devices.def"
    done = 1;
  }
}

char **MogulListDevices(void)
{
	int i;
	char **devices = NULL;
	char buf[128];

	if(ndevice > 0) {
	  devices = ResizePointerArray(devices, ndevice + 1);
	  for(i=0; i<ndevice; i++) {
	    sprintf(buf, "%s: %s",
	      Devices[i]->devicename, Devices[i]->fullname);
	    devices[i] = mystrdup(buf);
	  }
	  devices[ndevice] = NULL;
	}

	return devices;
}

void MogulPrintDevices(FILE *f)
{
	int i;
	char **devices = MogulListDevices();
	if(devices)
	{
	  fprintf(f, "Devices available:\n");
	  for(i=0; devices[i]; i++) {
	    fprintf(f, "%s\n", devices[i]);
	    myfree(devices[i]);
	  }
	  myfree(devices);
	}
	else
	  fprintf(f, "<No devices available>\n");
}

MogulDevice MogulGetDevice(char *devicename, char *deviceinfo)
{
	int i;
	MogulDevice d;

	for(i=0; i<ndevice; ++i)
	{
	  if(strcmp(devicename, Devices[i]->devicename) == 0)
	  {
	    d = NewPointer(d);
	    *d = *(Devices[i]);
	    d->instance = d->Pinit(deviceinfo);
	    return d;
	  }
	}
	return NULL;
}

void MogulFreeDevice(MogulDevice d)
{
	if(d) {
	  d->Pstop(d->instance);
	  myfree(d);
	}
}

char *MogulStrerror(int code)
{
	int m = code & (~MoErrFatalMask);

	if(m < 0 || m >= MoErrMax) {
	  fprintf(stderr, "mogul: invalid message code %d\n", code);
	  exit(1);
	}

	if(m == MoErrSystem) return strerror(errno);
	else if(m == MoErrParserError) return MogulParserMsg;

	return msgtab[m];
}

static void (*error_message)(char *s) = NULL;
static void (*warning_message)(char *s) = NULL;
void MogulPerror(char *s)
{
	char *buf, *msg = MogulStrerror(MogulErrno);

	buf = NewPointerArray(buf, strlen(msg) + strlen(s) + 16);
	sprintf(buf, "%s: %s", s, msg);

	if(MogulErrno & MoErrFatalMask)
	{
	  if(error_message)
	    error_message(buf);
	  else
	    fprintf(stderr, "mogul fatal error: %s\n", buf);
	  exit(1);
	}
	else 
	{
	  if(warning_message)
	    warning_message(buf);
	  else
	    fprintf(stderr, "mogul warning: %s\n", buf);
	}

	myfree(buf);

	MogulErrno = MoErrNormalOperation; /* reset it for next time */
}

void MogulSetMsgHandler(void (*emsg)(char *), void (*wmsg)(char *))
{
	error_message = emsg;
	warning_message = wmsg;
}

/* Apply a perspective transformation to the point x.
 * Most devices should use this, unless there's a built-in
 * perspective operation.
 */
double perscl(double x[3], double px[3], double vdist)
{
	double s;

	if(x[2] != 0.0)
	{
	  s = vdist - x[2];
	  if(vdist > 0.0 && s > 0.01)
	  {
	    s = vdist / s;
	    px[0] = x[0] * s;
	    px[1] = x[1] * s;
	    px[2] = x[2];
	    return s;
	  }
	}

	px[0] = x[0];
	px[1] = x[1];
	px[2] = x[2];

	return 1.0;
}

#define ZERO 0.0
#define TWOPI (2.0 * M_PI)

#include "trig_table.h"

/* table of sin(t), cos(t) for t = 0, 2pi stepping by n steps
 */
static double *trig_table[] = {
  trig_table_2,
  trig_table_4,
  trig_table_8,
  trig_table_16,
  trig_table_32,
  trig_table_64,
  trig_table_128
};

/* Add an arc segment to the current "path", in the {u,v} plane.
 * This can be used by devices which don't have a better way.
 */
int Parc_from_lines(double x[3], double r, double u[3], double v[3],
	double t0, double t1, double res,
	int (*pline)(double *, MogulDeviceInstance),
	MogulDeviceInstance mdi)
{
	int i, m, n;
	double s, c, theta, dtheta;
	double p[3];
	char t0_is_zero, t0_is_pi, t1_is_zero, t1_is_pi, t1_is_twopi;

	ENTER("Parc_from_lines");

	/* determine the least number of segments to represent
	 * the curve without missing the real curve by more than
	 * res (pixels).
	 */
	c = 1.0 - res / r;
	if(c <= -1.0) n = 2;
	else {
	  s = 0.5 * fabs(t1 - t0) / acos(c);
	  n = s + 2;
	}

	t0_is_zero  = (t0 == ZERO ? 1 : 0);
	t0_is_pi    = (t0 == M_PI ? 1 : 0);
	t1_is_zero  = (t1 == ZERO ? 1 : 0);
	t1_is_pi    = (t1 == M_PI ? 1 : 0);
	t1_is_twopi = (t1 == TWOPI ? 1 : 0);

	if((t0_is_zero  || t0_is_pi) &&
	   (t1_is_zero || t1_is_pi || t1_is_twopi)) {
	  int tab;
	  if(n < 3) {
	    n = 2;
	    tab = 0;
	  } else if(n < 6) {
	    n = 4;
	    tab = 1;
	  } else if(n < 12) {
	    n = 8;
	    tab = 2;
	  } else if(n < 24) {
	    n = 16;
	    tab = 3;
	  } else if(n < 48) {
	    n = 32;
	    tab = 4;
	  } else if(n < 96) {
	    n = 64;
	    tab = 5;
	  } else {
	    n = 128;
	    tab = 6;
	  }
	  if(t0_is_zero) {
	    if(t1_is_pi) { /* else it is TWOPI */
	      if(++tab > 6) {
		tab = 6;
		n /= 2;
	      }
	    }
	    for(i=0; i<=n; ++i) {
	      c = trig_table[tab][i*2];
	      s = trig_table[tab][i*2+1];
	      for (m=0; m<3; ++m) p[m] = x[m] + r * (c*u[m] + s*v[m]);
	      if(pline(p, mdi)) return -1;
	    }
	  } else {
	    /* t0 must be PI */
	    if(++tab > 6) {
	      tab = 6;
	      n /= 2;
	    }
	    if(t1_is_zero) {
	      for(i=n-1; i>=0; --i) {
		c = trig_table[tab][i*2];
		s = trig_table[tab][i*2+1];
		for (m=0; m<3; ++m) p[m] = x[m] + r * (c*u[m] + s*v[m]);
		if(pline(p, mdi)) return -1;
	      }
	    } else { /* t1 must be TWOPI */
	      for(i=n-1; i<(n*2); ++i) {
		c = trig_table[tab][i*2];
		s = trig_table[tab][i*2+1];
		for (m=0; m<3; ++m) p[m] = x[m] + r * (c*u[m] + s*v[m]);
		if(pline(p, mdi)) return -1;
	      }
	    }
	  }

	}
	else
	{
	  /* General form, slower because the trigonometric functions
	   * cannot be looked up.
	   */
	  dtheta = (t1 - t0) / (n > 1 ? n - 1 : 1);

	  for(i=0; i<n; ++i) {
	    theta = t0 + i * dtheta;
	    c = cos(theta);
	    s = sin(theta);
	    for (m=0; m<3; ++m) p[m] = x[m] + r * (c*u[m] + s*v[m]);
	    if(pline(p, mdi)) return -1;
	  }
	}

	LEAVE();

	return 0;
}

