/*
 * Copyright (c) 1992 The Regents of the University of California.
 * All rights reserved.
 * 
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose, without fee, and without written agreement is
 * hereby granted, provided that the above copyright notice and the following
 * two paragraphs appear in all copies of this software.
 * 
 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
 * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
 * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
 * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 */
#include <math.h>
#include <vga.h>
#include <vgagl.h>
#include "video.h"
#include "proto.h"
#include "dither.h"

/* Range values for lum, cr, cb. */
int LUM_RANGE;
int CR_RANGE;
int CB_RANGE;

/* Array that remaps color numbers to actual pixel values used by X server. */

unsigned char pixel[256];

/* Arrays holding quantized value ranged for lum, cr, and cb. */

int *lum_values;
int *cr_values;
int *cb_values;

/* Declaration of global variable containing dither type. */

extern int ditherType;

int vgamode = -1;



/*
 *--------------------------------------------------------------
 *
 * InitColor --
 *
 *	Initialized lum, cr, and cb quantized range value arrays.
 *
 * Results: 
 *      None.
 *
 * Side effects:
 *      None.
 *
 *--------------------------------------------------------------
 */

void
InitColor()
{
  int i;

  for (i=0; i<LUM_RANGE; i++) {
    lum_values[i] = ((i * 256) / (LUM_RANGE)) + (256/(LUM_RANGE*2));
  }

  for (i=0; i<CR_RANGE; i++) {
    cr_values[i] = ((i * 256) / (CR_RANGE)) + (256/(CR_RANGE*2));
  }

  for (i=0; i<CB_RANGE; i++) {
    cb_values[i] = ((i * 256) / (CB_RANGE)) + (256/(CB_RANGE*2));
  }

}


/*
 *--------------------------------------------------------------
 *
 * ConvertColor --
 *
 *	Given a l, cr, cb tuple, converts it to r,g,b.
 *
 * Results:
 *	r,g,b values returned in pointers passed as parameters.
 *
 * Side effects:
 *      None.
 *
 *--------------------------------------------------------------
 */

static void
ConvertColor(l, cr, cb, r, g, b)
     unsigned char l, cr, cb;
     unsigned char *r, *g, *b;
{
  double fl, fcr, fcb, fr, fg, fb;

  fl = (double) l;
  fcr =  ((double) cr) - 128.0;
  fcb =  ((double) cb) - 128.0;


  fr = fl + (1.40200 * fcb);
  fg = fl - (0.71414 * fcb) - (0.34414 * fcr);
  fb = fl + (1.77200 * fcr);

  if (fr < 0.0) fr = 0.0;
  else if (fr > 255.0) fr = 255.0;

  if (fg < 0.0) fg = 0.0;
  else if (fg > 255.0) fg = 255.0;

  if (fb < 0.0) fb = 0.0;
  else if (fb > 255.0) fb = 255.0;

  *r = (unsigned char) fr;
  *g = (unsigned char) fg;
  *b = (unsigned char) fb;

}


void ResizeDisplay(w, h)
     int w, h;
{
  vga_modeinfo *modeinfo;
  /* Check if current VGA mode is big enough. */
  modeinfo = vga_getmodeinfo(vgamode);
  if (w > modeinfo->width || h > modeinfo->height) {
    int fail = 0;
    if (w > 640 || h > 480)
      fail = 1;
    else
      if (modeinfo->width == 320) {
        fail = 1;
        switch (modeinfo->bytesperpixel) {
        case 1 :
  	  if (vga_hasmode(G640x480x256)) {
  	    Palette pal;
  	    gl_getpalette(&pal);
  	    vgamode = G640x480x256;
	    vga_setmode(vgamode);
	    gl_setpalette(&pal);
	    gl_setcontextvga(vgamode);
	    if (ditherType != GRAY_DITHER)
		    gl_clearscreen(255);
	    gl_enableclipping();
	    fail = 0;
	  }
	  break;
	case 2 :
  	  if (vga_hasmode(G640x480x32K)) {
  	    vgamode = G640x480x32K;
	    vga_setmode(vgamode);
	    gl_setcontextvga(vgamode);
	    gl_enableclipping();
	    fail = 0;
	  }
	  break;
	case 3 :
  	  if (vga_hasmode(G640x480x16M)) {
  	    vgamode = G640x480x16M;
	    vga_setmode(vgamode);
	    gl_setcontextvga(vgamode);
	    gl_enableclipping();
	    fail = 0;
	  }
	  break;
        }
      }
      else
        fail = 1;
    if (fail) {
    	printf("MPEG screen size too big.\n");
    	vga_setmode(TEXT);
    	exit(-1);
    }
  }
}


static void
restoretextmode() {
  vga_setmode(TEXT);
}

static void 
MakeWindow(name) 
char *name;
{
  
  if (ditherType == NO_DITHER) return;

  /* Make the window */

  vga_disabledriverreport();
  vga_init();

  #define FIRSTRES(c) G320x200x##c
  #define SECONDRES(c) G640x480x##c
  
  if (ditherType == FULL_COLOR_DITHER) {
    /* Look for hicolor/truecolor mode. */
    /* 640x480 resolution makes most animations very small. */
    /* 320x200 is more full-screen, but the distortions are very visible. */
    /* Check default svgalib mode. */
    vgamode = vga_getdefaultmode();
    if (vgamode != -1) {
       if (vga_getmodeinfo(vgamode)->bytesperpixel == 3)
         /* Default mode is a truecolor mode. */
         goto gotvgamode;
       if (vga_getmodeinfo(vgamode)->colors == 32768)
         goto gotvgamode;
    }
    if (vga_hasmode(FIRSTRES(16M)))
      vgamode = FIRSTRES(16M);
    else if (vga_hasmode(SECONDRES(16M)))
      vgamode = SECONDRES(16M);
    else if (vga_hasmode(FIRSTRES(32K)))
      vgamode = FIRSTRES(32K);
    else if (vga_hasmode(SECONDRES(32K)))
      vgamode = SECONDRES(32K);
  } else if (ditherType == MONO_DITHER || ditherType == MONO_THRESHOLD) {
    /* set mode suitable for mono display if available */
    if (vga_hasmode(G640x480x2))
      vgamode = G640x480x2;
  } else {
    /* set 256-color mode */
    /* Check default svgalib mode. */
    vgamode = vga_getdefaultmode();
    if (vgamode != -1)
       if (vga_getmodeinfo(vgamode)->bytesperpixel == 1)
         /* Default mode is a 256 color mode. */
         goto gotvgamode;
    if (vga_hasmode(FIRSTRES(256)))
      vgamode = FIRSTRES(256);
    else if (vga_hasmode(SECONDRES(256)))
      vgamode = SECONDRES(256);
  }
  if (vgamode == -1) {
  	printf("Cannot find suitable SVGA graphics mode for selected dithering.\n");
  	exit(-1);
  }
gotvgamode:
  printf("Using mode %s.\n", vga_getmodename(vgamode));
  vga_setmode(vgamode);
  gl_setcontextvga(vgamode);
  gl_enableclipping();
}


/*
 *--------------------------------------------------------------
 *
 * InitDisplay --
 *
 *	Initialized display, sets up colormap, etc.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      None.
 *
 *--------------------------------------------------------------
 */

void InitDisplay(name)
char *name;
{
/* For 256 color modes. */

  int ncolors = LUM_RANGE*CB_RANGE*CR_RANGE;
  int i, lum_num, cr_num, cb_num;
  int r, g, b;

  if (ditherType == NO_DITHER) return;

  MakeWindow(name);

  gl_setpalettecolor(255, 0, 0, 0);	/* black */
  gl_clearscreen(255);

  for (i=0; i<ncolors; i++) {

    lum_num = (i / (CR_RANGE*CB_RANGE))%LUM_RANGE;
    cr_num = (i / CB_RANGE)%CR_RANGE;
    cb_num = i % CB_RANGE;

    r = g = b = 0;
    ConvertColor(lum_values[lum_num], cr_values[cr_num], cb_values[cb_num], &r, &g, &b);

    if (i > 256) {
      printf("mpeg_vga: not enough colors.\n");
      vga_setmode(TEXT);
      exit(-1);
    }
    gl_setpalettecolor(i, r >> 2, g >> 2, b >> 2);
    pixel[i] = i;
  }
}


/*
 *--------------------------------------------------------------
 *
 * InitGrayDisplay --
 *
 *	Initialized display for gray scale dither.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      None.
 *
 *--------------------------------------------------------------
 */

#define NUM_COLORS 256

void InitGrayDisplay(name)
char *name;
{
  int ncolors = NUM_COLORS;
  int i;

  MakeWindow(name);

  for (i=0; i<ncolors; i++) {
    int r, g, b;
    r = i;
    g = i;
    b = i;
    
    gl_setpalettecolor(i, r / 4, g / 4, b / 4);
    pixel[i] = i;
  }
}


/*
 *--------------------------------------------------------------
 *
 * InitMonoDisplay --
 *
 *	Initialized display for monochrome dither.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      None.
 *
 *--------------------------------------------------------------
 */

void InitMonoDisplay(name)
char *name;
{

  MakeWindow(name);

}


/*
 *--------------------------------------------------------------
 *
 * InitColorDisplay --
 *
 *	Initialized display for full color output.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      None.
 *
 *--------------------------------------------------------------
 */

void InitColorDisplay(name)
char *name;
{

  MakeWindow(name);

}


/*
 *--------------------------------------------------------------
 *
 * ExecuteDisplay --
 *
 *	Actually displays display plane in previously created window.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *--------------------------------------------------------------
 */

void
ExecuteDisplay(vid_stream)
     VidStream *vid_stream;
{

  totNumFrames++;
  if (!quietFlag) {
    fprintf (stderr, "%d\r", totNumFrames);
  }

  if (ditherType == NO_DITHER) return;

  if (!noDisplayFlag)
      {
	void *data = (char *) vid_stream->current->display; 
	
	if (ditherType == Twox2_DITHER) {
	  /* Twice the size; 256-color mode */
	  gl_putbox(0, 0, vid_stream->h_size * 2,
	    vid_stream->v_size * 2, data);
	} else if (ditherType == FULL_COLOR_DITHER && BYTESPERPIXEL == 3) {
	  /* Tricky conversion. */
	  /* The data is padded to 32 bits per pixel, we need 24 bits. */
	  int i, w;
	  unsigned int *datap;
	  void *box;
	  unsigned char *boxp;
	  datap = data;
	  w = vid_stream->h_size;
	  box = alloca(vid_stream->v_size * w * 3 + 3);
	  boxp = box;
	  for (i = 0; i < vid_stream->v_size; i++) {
	    int j = 0;
	    /* First byte is blue. */
	    /* Nasty overlapping memory writes, but it is fast. */
	    /* Note that boxp points to bytes, datap to words. */
            while (j + 7 < w) {
         	*(unsigned *)boxp = *datap;
            	*(unsigned *)(boxp + 3) = *(datap + 1);
            	*(unsigned *)(boxp + 6) = *(datap + 2);
            	*(unsigned *)(boxp + 9) = *(datap + 3);
            	*(unsigned *)(boxp + 12) = *(datap + 4);
            	*(unsigned *)(boxp + 15) = *(datap + 5);
            	*(unsigned *)(boxp + 18) = *(datap + 6);
            	*(unsigned *)(boxp + 21) = *(datap + 7);
            	j += 8;
            	boxp += 24;
            	datap += 8;
            }
            while (j < w) {
              *(unsigned *)boxp = *datap;
              j++;
              boxp += 3;
              datap++;
            }
	  }
	  gl_putbox(0, 0, vid_stream->h_size, vid_stream->v_size, box);
	} else if (ditherType == FULL_COLOR_DITHER && BYTESPERPIXEL == 2) {
	  /* The data is 8-8-8 truecolor padded to 32 bits, we need */
	  /* 15-bit 5-5-5 truecolor. Pretty slow conversion. */
	  int i, w;
	  unsigned int *datap;
	  void *box;
	  unsigned char *boxp;
	  datap = data;
	  w = vid_stream->h_size;
	  box = alloca(vid_stream->v_size * w * 2 + 3);
	  boxp = box;
	  for (i = 0; i < vid_stream->v_size; i++) {
	    int j = 0;
	    /* First byte is blue. */
	    /* Note that boxp points to bytes, datap to words. */
            while (j + 1 < w) {
            	unsigned r, g, b;
        	b = *((unsigned char *)datap);
        	g = *((unsigned char *)datap + 1);
        	r = *((unsigned char *)datap + 2);
            	*(unsigned short *)boxp =
            		((r & 0xf8) << 7) + ((g & 0xf8) << 2) + (b >> 3);
        	b = *((unsigned char *)datap + 4);
        	g = *((unsigned char *)datap + 5);
        	r = *((unsigned char *)datap + 6);
            	*(unsigned short *)(boxp + 2) =
            		((r & 0xf8) << 7) + ((g & 0xf8) << 2) + (b >> 3);
            	j += 2;
            	boxp += 4;
            	datap += 2;
            }
            while (j < w) {
            	unsigned r, g, b;
        	r = *((unsigned char *)datap);
        	g = *((unsigned char *)datap + 1);
        	g = *((unsigned char *)datap + 2);
            	*(unsigned short *)boxp =
            		((r & 0xf8) << 7) + ((g & 0xf8) << 2) + (b >> 3);
            	j++;
            	boxp += 2;
            	datap++;
            }
	  }
	  gl_putbox(0, 0, vid_stream->h_size, vid_stream->v_size, box);
        } else if (ditherType == MONO_DITHER || ditherType == MONO_THRESHOLD) {
          /* It's MSBFirst, which is what we need. */
          int i;
          for (i = 0; i < vid_stream->v_size; i++)
            vga_drawscansegment(data + i * vid_stream->h_size / 8,
              0, i, vid_stream->h_size / 8);
        } else {
          /* default 256-color dithering */
	  gl_putbox(0, 0, vid_stream->h_size, vid_stream->v_size, data);
	}
      }
}
