#ifdef __LINUX__

#define __USE_GL__

#include <math.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <vga.h>
#include <vgagl.h>
#include <signal.h>

#include "linuxgl.h"

GraphicsContext backscreen;
GraphicsContext physicalscreen;
static float FHWIDTH,NFHHEIGHT,HHEIGHT,HWIDTH;

static struct {
  Matrix viewing,projection;
  int    viewident,projident;
  int    matmode;
} GL;

void swapbuffers ( void )
{
  gl_copyscreen(&physicalscreen);
}

void getsize ( long *x, long *y )
{
  if(x) *x = WIDTH;
  if(y) *y = HEIGHT;
}

void signal_handler(int sig)
{
  fprintf(stderr,"signal handler: Turning off rawmode\n");
  rawmode_exit();
  exit(-1);
}

int winopen(const char *name)
{
  int vgamode = -1;
  int virtual;

  signal(SIGSEGV,signal_handler);
  signal(SIGFPE,signal_handler);
  signal(SIGILL,signal_handler);
  signal(SIGINT,signal_handler);

  vga_init();
/*   vgamode = vga_getdefaultmode(); */

  if (vgamode == -1) {
    int high,i;

    for (i = 1; i <= GLASTMODE; i++) {
      if (vga_hasmode(i)) {
	vga_modeinfo *info;
	char expl[100];
	char *cols = NULL;

	*expl = '\0';
	info = vga_getmodeinfo(i);

	if ( info->colors != 256 ) continue;
	     if (i == G320x200x256) strcpy(expl, "linear");
	else if (i == G320x240x256 || i == G320x400x256 || i == G360x480x256)
	     strcpy(expl,"Mode X");
	else strcpy(expl,"linear, banked");

	if (info->flags&IS_INTERLACED) {
	  if (*expl != '\0') strcat(expl, ", ");
	  strcat(expl, "interlaced");
	}
	if (info->flags&IS_DYNAMICMODE) {
	  if (*expl != '\0') strcat(expl, ", ");
	  strcat(expl, "dynamically generated");
	}
	high = i;
	printf("%5d: %dx%d, ",
		i, info->width, info->height);
	if (cols == NULL)
		printf("%d", info->colors);
	else
		printf("%s", cols);
	printf(" colors ");
	if (*expl != '\0')
		printf("(%s)", expl);
	printf("\n");
      }
    }
    printf("Enter mode number (1-%d): ", high);
    scanf("%d", &vgamode);
    getchar();
    printf("\n");

    if (vgamode < 1 || vgamode > GLASTMODE) {
      printf("Error: Mode number out of range \n");
      exit(-1);
    }
    else {

      vga_modeinfo *info;

      info = vga_getmodeinfo(vgamode);

      if(info->colors != 256) {
	printf("Error: rawks will only run in 256 color mode\n");
	exit(-1);
      }
    }
  }

  if (!vga_hasmode(vgamode)) {
	  printf("Mode not available.\n");
	  exit(-1);
  }

  vga_setmode(vgamode);
  rawmode_init();
  gl_setcontextvga(vgamode);	/* Physical screen context. */
  gl_getcontext(&physicalscreen);

  gl_setcontextvgavirtual(vgamode);
  gl_getcontext(&backscreen);

  gl_setcontext(&backscreen);

  if (COLORS == 256) gl_setrgbpalette();

  gl_clearscreen(0);
  gl_setclippingwindow(0,0,WIDTH-1,HEIGHT-1);

/*   vga_screenon(); */

  HHEIGHT  = HEIGHT>>1;
  HWIDTH   = WIDTH>>1;
  FHWIDTH  = (float)(HWIDTH);
  NFHHEIGHT = (float)(-HHEIGHT);

  mmode(MPROJECTION); loadmatrix(Identity);
  mmode(MVIEWING);    loadmatrix(Identity);
}

static struct {
  char state[256];
  short listen[256];
  char numl;
} dq;

static lgl_rawmode;

void unqdevice( int device ) {
  int i;
  if(device == RAWKEYBD) lgl_rawmode = FALSE;
  else if((device & 0xff) == device)
    for(i=0;i<dq.numl;i++)
      if(dq.listen[i] == device) {
	dq.listen[i] == dq.listen[--dq.numl];
	break;
      }
}

void qdevice( int device ) {
  if(device == RAWKEYBD) lgl_rawmode = TRUE;
  else if((device & 0xff) == device) {
    dq.state[device] = is_key_pressed(device);
    dq.listen[dq.numl++] = device;
  }
}

int qtest( void )
{
  int i;

  scan_keyboard();

  if(lgl_rawmode) {
    for(i=0;i<255;i++) if(dq.state[i] != is_key_pressed(i)) return 1;
  }
  else for(i=0;i<dq.numl;i++) {
    int key = dq.listen[i];
    if(dq.state[key] != is_key_pressed(key)) return 1;
  }

  return 0;
}

int qread(short *val)
{
  if(!dq.numl) return 0;

  while(1) {
    int i;

    if(lgl_rawmode) for(i=0;i<255;i++) {
      short t;
      if(dq.state[i] != (t = is_key_pressed(i))) {
	dq.state[i] = *val = t;
	return i;
      }
    }
    else for(i=0;i<dq.numl;i++) {
      short t;
      int key = dq.listen[i];

      if(dq.state[key] != (t = is_key_pressed(key))) {
        dq.state[key] = *val = t;
        return key;
      }
    }
    scan_keyboard();
  }
  return 0;
}

int getvaluator(int which)
{
  return is_key_pressed(which);
}

void clear(void)
{
  gl_clearscreen(0);
}

#ifdef __USE_GL__
#define vga_drawline( x1, y1, x2, y2 ) gl_line(x1,y1,x2,y2,ln_cur_color);
#endif

static int ln_ds,ln_sx,ln_sy,ln_x1,ln_y1,ln_cur_color,ln_cur_cpack;

#define LN_SCALEX(f) (((int)((f)*(FHWIDTH  ))) + (WIDTH >>1))
#define LN_SCALEY(f) (((int)((f)*(NFHHEIGHT))) + (HEIGHT>>1))

void gl_myfillbox( float fx1, float fy1, float fw, float fh )
{
  int x1 = LN_SCALEX(fx1);
  int y1 = LN_SCALEY(fy1);
  int w  = fw * HWIDTH;
  int h  = fh * HHEIGHT;

  gl_fillbox( x1,y1,w,h+1,ln_cur_color);
}

void sbox(int x1,int y1,int x2,int y2) {

  vga_drawline(x1,y1,x1,y2);
  vga_drawline(x1,y2,x2,y2);
  vga_drawline(x2,y2,x1,y2);
  vga_drawline(x1,y2,x1,y1);
}

void bgnpolygon(void) { ln_ds = 1; }
void bgnclosedline( void ) { ln_ds = 1; }
void endpolygon(void) { vga_drawline(ln_x1,ln_y1,ln_sx,ln_sy); }
void endclosedline( void ) { vga_drawline(ln_x1,ln_y1,ln_sx,ln_sy); }
void bgnline(void) { ln_ds = 2; }
void endline(void) { }

lnXFormPt3( float dst[3], Matrix xform, float vec[3] )
{
  float tmp[3];

  tmp[0] = vec[0] * xform[0][0] +
           vec[1] * xform[1][0] +
           vec[2] * xform[2][0] +
                    xform[3][0];

  tmp[1] = vec[0] * xform[0][1] +
           vec[1] * xform[1][1] +
           vec[2] * xform[2][1] +
                    xform[3][1];

  tmp[2] = vec[0] * xform[0][2] +
           vec[1] * xform[1][2] +
           vec[2] * xform[2][2] +
                    xform[3][2];

  dst[0] = tmp[0];
  dst[1] = tmp[1];
  dst[2] = tmp[2];
}

void v2f(float *f)
{
  int xyz[2];

  xyz[0] = LN_SCALEX(*(f++));
  xyz[1] = LN_SCALEY(*f);

  if(ln_ds) ln_sx=xyz[0],ln_sy=xyz[1],ln_ds=0;
  else vga_drawline(ln_x1,ln_y1,xyz[0],xyz[1]);

  ln_x1 = xyz[0]; ln_y1 = xyz[1];
}

void v3f(float *f)
{
  float f_xyz[3];
  int xyz[3];

  f_xyz[0] = f[0];
  f_xyz[1] = f[1];
  f_xyz[2] = f[2];

  if( ! GL.viewident ) lnXFormPt3( f_xyz, GL.viewing   , f_xyz );
/*   if( ! GL.projident ) lnXFormPt3( f_xyz, GL.projection, f_xyz ); */

  xyz[0] = LN_SCALEX(f_xyz[0]);
  xyz[1] = LN_SCALEY(f_xyz[1]);
  xyz[2] = (int) f_xyz[2];

  if(ln_ds) ln_sx=xyz[0],ln_sy=xyz[1],ln_ds=0;
  else vga_drawline(ln_x1,ln_y1,xyz[0],xyz[1]);

  ln_x1 = xyz[0]; ln_y1 = xyz[1];
}

unsigned int getcpack(unsigned int cpc)
{
  return ln_cur_cpack;
}

void cpack(unsigned int cpc)
{
  ln_cur_cpack = cpc;
  ln_cur_color = gl_rgbcolor( cpc&0xff, (cpc&0xff00)>>8, (cpc&0xff0000)>>16);
#ifndef __USE_GL__
  vga_setcolor(ln_cur_color);
#endif
}

int getdrawmode( void ) {
  return NORMALDRAW;
}

long getgdesc(int what) {
  switch(what) {
    case GD_XPMAX:
      return WIDTH;
    case GD_YPMAX:
      return HEIGHT;
  }
  return 0;
}

void winclose( int window )
{
  vga_setmode(TEXT);
  rawmode_exit();
}

int lnEqualMat( Matrix m1, Matrix m2 )
{
  if( m1[0][0] != m2[0][0] ) return 0;
  if( m1[0][1] != m2[0][1] ) return 0;
  if( m1[0][2] != m2[0][2] ) return 0;
  if( m1[0][3] != m2[0][3] ) return 0;

  if( m1[1][0] != m2[1][0] ) return 0;
  if( m1[1][1] != m2[1][1] ) return 0;
  if( m1[1][2] != m2[1][2] ) return 0;
  if( m1[1][3] != m2[1][3] ) return 0;

  if( m1[2][0] != m2[2][0] ) return 0;
  if( m1[2][1] != m2[2][1] ) return 0;
  if( m1[2][2] != m2[2][2] ) return 0;
  if( m1[2][3] != m2[2][3] ) return 0;

  if( m1[3][0] != m2[3][0] ) return 0;
  if( m1[3][1] != m2[3][1] ) return 0;
  if( m1[3][2] != m2[3][2] ) return 0;
  if( m1[3][3] != m2[3][3] ) return 0;

  return 1;
}

void lnCopyMat ( Matrix dst, Matrix mat )
{
  dst[0][0] = mat[0][0];
  dst[0][1] = mat[0][1];
  dst[0][2] = mat[0][2];
  dst[0][3] = mat[0][3];

  dst[1][0] = mat[1][0];
  dst[1][1] = mat[1][1];
  dst[1][2] = mat[1][2];
  dst[1][3] = mat[1][3];

  dst[2][0] = mat[2][0];
  dst[2][1] = mat[2][1];
  dst[2][2] = mat[2][2];
  dst[2][3] = mat[2][3];

  dst[3][0] = mat[3][0];
  dst[3][1] = mat[3][1];
  dst[3][2] = mat[3][2];
  dst[3][3] = mat[3][3];
}

/*

1         a b c d
  1     . e f g h
    1     i j k l
x y z 1   m n o p

*/

void lnPreTransMat( Matrix dst, float x, float y, float z )
{
  dst[3][0] = dst[0][0] * x +
              dst[1][0] * y +
              dst[2][0] * z +
              dst[3][0];

  dst[3][1] = dst[0][1] * x +
              dst[1][1] * y +
              dst[2][1] * z +
              dst[3][1];

  dst[3][2] = dst[0][2] * x +
              dst[1][2] * y +
              dst[2][2] * z +
              dst[3][2];
}

void translate ( float x, float y, float z )
{
  switch( GL.matmode ) {

    case MPROJECTION:
      lnPreTransMat( GL.projection, x,y,z );
      if(0) {
	int i,j;
	printf("proj = ");
	for(i=0;i<4;i++) for(j=0;j<4;j++)
	  printf("%1.1f ",GL.projection[i][j]);
	printf("\n");
      }
      GL.projident = 0;
      break;

    case MVIEWING:
      lnPreTransMat( GL.viewing, x,y,z );
      if(0) {
	int i,j;
	printf("view = ");
	for(i=0;i<4;i++) for(j=0;j<4;j++)
	  printf("%1.1f ",GL.viewing[i][j]);
	printf("\n");
	printf("xyz = %1.1f %1.1f %1.1f\n",x,y,z);
      }
      GL.viewident = 0;
      break;
  }
}

void
lnPreMultMat( Matrix dst, Matrix m )
{
  Matrix tmp;

  tmp[0][0] = dst[0][0] * m[0][0] +
              dst[0][1] * m[1][0] +
              dst[0][2] * m[2][0] +
              dst[0][3] * m[3][0];

  tmp[0][1] = dst[0][0] * m[0][1] +
              dst[0][1] * m[1][1] +
              dst[0][2] * m[2][1] +
              dst[0][3] * m[3][1];

  tmp[0][2] = dst[0][0] * m[0][2] +
              dst[0][1] * m[1][2] +
              dst[0][2] * m[2][2] +
              dst[0][3] * m[3][2];

  tmp[0][3] = dst[0][0] * m[0][3] +
              dst[0][1] * m[1][3] +
              dst[0][2] * m[2][3] +
              dst[0][3] * m[3][3];

  tmp[1][0] = dst[1][0] * m[0][0] +
              dst[1][1] * m[1][0] +
              dst[1][2] * m[2][0] +
              dst[1][3] * m[3][0];

  tmp[1][1] = dst[1][0] * m[0][1] +
              dst[1][1] * m[1][1] +
              dst[1][2] * m[2][1] +
              dst[1][3] * m[3][1];

  tmp[1][2] = dst[1][0] * m[0][2] +
              dst[1][1] * m[1][2] +
              dst[1][2] * m[2][2] +
              dst[1][3] * m[3][2];

  tmp[1][3] = dst[1][0] * m[0][3] +
              dst[1][1] * m[1][3] +
              dst[1][2] * m[2][3] +
              dst[1][3] * m[3][3];

  tmp[2][0] = dst[2][0] * m[0][0] +
              dst[2][1] * m[1][0] +
              dst[2][2] * m[2][0] +
              dst[2][3] * m[3][0];

  tmp[2][1] = dst[2][0] * m[0][1] +
              dst[2][1] * m[1][1] +
              dst[2][2] * m[2][1] +
              dst[2][3] * m[3][1];

  tmp[2][2] = dst[2][0] * m[0][2] +
              dst[2][1] * m[1][2] +
              dst[2][2] * m[2][2] +
              dst[2][3] * m[3][2];

  tmp[2][3] = dst[2][0] * m[0][3] +
              dst[2][1] * m[1][3] +
              dst[2][2] * m[2][3] +
              dst[2][3] * m[3][3];

  tmp[3][0] = dst[3][0] * m[0][0] +
              dst[3][1] * m[1][0] +
              dst[3][2] * m[2][0] +
              dst[3][3] * m[3][0];

  tmp[3][1] = dst[3][0] * m[0][1] +
              dst[3][1] * m[1][1] +
              dst[3][2] * m[2][1] +
              dst[3][3] * m[3][1];

  tmp[3][2] = dst[3][0] * m[0][2] +
              dst[3][1] * m[1][2] +
              dst[3][2] * m[2][2] +
              dst[3][3] * m[3][2];

  tmp[3][3] = dst[3][0] * m[0][3] +
              dst[3][1] * m[1][3] +
              dst[3][2] * m[2][3] +
              dst[3][3] * m[3][3];

  lnCopyMat( dst, tmp );
}

void
lnPreRotAxis( Matrix dst, float angle, float x, float y, float z )
{
  Matrix m;
  float c = (float) cos(angle);
  float s = (float) sin(angle);
  float t = 1 - c;
  float xx = (x==1.0f)?1.0f:x*x;
  float yy = (y==1.0f)?1.0f:y*y;
  float zz = (z==1.0f)?1.0f:z*z;

#if 0
  m[0][0] = t*xx + c;
  m[0][1] = t*x*y + s*z;
  m[0][2] = t*x*z - s*y;
  m[0][3] = 0.0f;

  m[1][0] = t*x*y - s*z;
  m[1][1] = t*yy + c;
  m[1][2] = t*y*z + s*x;
  m[1][3] = 0.0f;

  m[2][0] = t*x*z + s*y;
  m[2][1] = t*y*z - s*x;
  m[2][2] = t*zz + c;
  m[2][3] = 0.0f;

  m[3][0] = 0.0f;
  m[3][1] = 0.0f;
  m[3][2] = 0.0f;
  m[3][3] = 1.0f;
#else
  m[0][0] = t*xx + c;
  m[1][0] = t*x*y + s*z;
  m[2][0] = t*x*z - s*y;
  m[3][0] = 0.0f;

  m[0][1] = t*x*y - s*z;
  m[1][1] = t*yy + c;
  m[2][1] = t*y*z + s*x;
  m[3][1] = 0.0f;

  m[0][2] = t*x*z + s*y;
  m[1][2] = t*y*z - s*x;
  m[2][2] = t*zz + c;
  m[3][2] = 0.0f;

  m[0][3] = 0.0f;
  m[1][3] = 0.0f;
  m[2][3] = 0.0f;
  m[3][3] = 1.0f;
#endif

  lnPreMultMat( dst, m );
}

void rotate ( int angle, char axis )
{
  const float fang = - LN_DTR ( ( (float) angle ) / 10.0f );
  Matrix *dst =
    ( ( GL.matmode == MPROJECTION ) ? &GL.projection : &GL.viewing );

       if ( GL.matmode == MPROJECTION ) GL.projident = 0;
  else if ( GL.matmode == MVIEWING    ) GL.viewident = 0;

  switch( axis ) {
    case 'x':
      lnPreRotAxis( *dst, fang, 1.0f, 0.0f, 0.0f  );
      break;

    case 'y':
      lnPreRotAxis( *dst, fang, 0.0f, 1.0f, 0.0f  );
      break;

    case 'z':
      lnPreRotAxis( *dst, fang, 0.0f, 0.0f, 1.0f  );
      break;

    default:
      return;
  }
}

void loadmatrix ( Matrix mat )
{
  if ( GL.matmode == MVIEWING    ) {
    lnCopyMat( GL.viewing, mat );
    GL.viewident = lnEqualMat( GL.viewing, Identity );
  }
  else if ( GL.matmode == MPROJECTION ) {
    lnCopyMat( GL.projection, mat );
    GL.projident = lnEqualMat( GL.projection, Identity );
  }
}

void mmode ( int matmode )
{
  GL.matmode = matmode;
}

static int cmap[256];

void color ( int index )
{
  cpack(cmap[index]);
}

void mapcolor ( int index, float _r, float _g, float _b )
{
  int r = (int)(_r*255.0f);
  int g = (int)(_g*255.0f);
  int b = (int)(_b*255.0f);

  if(r < 0  ) r =   0;
  if(r > 255) r = 255;
  if(g < 0  ) g =   0;
  if(g > 255) g = 255;
  if(b < 0  ) b =   0;
  if(b > 255) b = 255;

  cmap[index] = ( b<<16 ) + ( g<<8 ) + r;
}

void qenter         ( void ) {}
void qreset         ( void ) {}
void ortho          ( int x1,int x2,int y1,int y2,int z1,int z2 )\
                    { (x1,y1,x2,y2,z1,z2); }
void ortho2         ( int x1,int x2,int y1,int y2 ) { (x1,y1,x2,y2); }
void viewport       ( int x1,int x2,int y1,int y2 ) { (x1,x2,y1,y2); }
void prefposition   ( int x1,int x2,int y1,int y2 ) { (x1,y1,x2,y2); }
void drawmode       ( int mode )                    { (mode); }
void frontbuffer    ( int onoff ) { (onoff); }
void backbuffer     ( int onoff ) { (onoff); }

void foreground     ( void ) {}
void sginap         ( void ) {}
void curson         ( void ) {}
void winconstraints ( void ) {}
void noborder       ( void ) {}
void gflush         ( void ) {}
void cursoff        ( void ) {}
void doublebuffer   ( void ) {}
void singlebuffer   ( void ) {}
void RGBmode        ( void ) {}
void gconfig        ( void ) {}
void reshapeviewport( void ) {}

#endif /* !__LINUX__ */
