/*
 *  This is the main part
 */

#include <intuition/screens.h>
#include <proto/dos.h>
#include <proto/exec.h>

#include "video.h"
#include "proto.h"
#include <sys/types.h>
#include <signal.h>

#include <proto/timer.h>
#include <devices/timer.h>

#define ASIO_REGARGS
#include <clib/asyncio_protos.h>

#include "util.h"

#define HELP_TEXT "NOB\t- ignore and not display any B frames.\n\
NOP\t- ignore and not display any P frames.\n\
LOOP\t- makes the player loop back to the beginning after reaching the end.\n\
EACHSTAT- causes statistics to be displayed after each frame.\n\
\t  Only valid when compiled with -DANALYSIS\n\
NODISPLAY- dithers, but does not display, usually used for\n\
\t  testing and timing purposes.\n\
QUIET\t- supresses printing of frame numbers, timing information,\n\
\t  and most error messages.\n\
DITHER\t- selects from a variety of dither options.\n\
\t  The possible values are:\n\
\t\tcolor\n\
\t\thiresham\n\
\t\tham6\n\
\t\tcybergfx\n\
\t\tcybergfxgray\n\
\t\tgray\n\
\t\tnone\n\
PUBSCREEN- open window on selected \"deep\" public screen. Only valid\n\
\t  with DITHER cybergfx or cybergfxgray. Use with \"DEFAULT\" to open\n\
\t  on current default public screen.\n\
MODEID\t- select modeid for screen to open and play on.\n\
FRAMERATE- speed in frames per second. (0 - as fast as possible).\n\
BUFFER\t- size of the io buffer (default - 65536).\n\
FILE\t- filename of MPEG stream.\n"

#define TEMPLATE "NOB/S,NOP/S,LOOP/S,EACHSTAT/S,ND=NODISPLAY/S,QUIET/S,DITHER/K,PS=PUBSCREEN/K,MODEID/K,FRAMERATE=FPS/K,BUFFER=BUF/K,FILE/A"

#define OPT_NOB				0
#define OPT_NOP				1
#define OPT_LOOP			2
#define OPT_EACHSTAT	3
#define OPT_NODISPLAY	4
#define OPT_QUIET			5
#define OPT_DITHER		6
#define	OPT_PUBSCREEN	7
#define OPT_MODEID		8
#define OPT_FPS				9
#define OPT_BUFFER		10
#define OPT_FILE			11
#define OPT_COUNT			12


const char *dither_names = "ham6\1color\1hiresham\1ham6\1cybergfx\1cybergfxgray\1gray\1none";
/*                          01234 56789  1113151719 2123 2527293133 353739414345 474951 53
   got it? :)                          10 12141618 202224 26283032 34363840424446 4850 525456
*/

char animname[1024];
char pubscreen_name[MAXPUBSCREENNAME + 1];
char *pubname_ptr;

/* Declaration of global variable to hold dither info. */

int ditherType;

/* size of the io buffer */

int buf_length = 65536;

/* Global file pointer to incoming data. */
struct AsyncFile *input;

/* End of File flag. */
static int EOF_flag = 0;

/* Loop flag. */
int loopFlag = 0;

/* Quiet flag. */
int quietFlag = 0;

/* Display image on screen? */
int noDisplayFlag = 0;

/* Setjmp/Longjmp env. */
jmp_buf env;

/* HAM6 rendering / HAM hires flag */
extern int ham6, lores;

/* modeid of CyberGraphX screen */
unsigned long modeid = 0xffffffff;

/* are we going to play on the Public Screen? */
int cyber_pub = 0;

/* Framerate, -1: specified in stream (default)
               0: as fast as possible
               N (N>0): N frames/sec  
               */
int framerate = -1;

/* these are for timer.device */
struct Library *TimerBase;
struct MsgPort *TimerMP;   		/* Message port pointer */
struct timerequest *TimerIO;  /* I/O structure pointer */

/* Method of picture conversion */
void (*DoDitherImage)(unsigned char *l, unsigned char *Cr, unsigned char *Cb,
		      unsigned char *disp, int h, int w);

static char version[]="$VER: aMiPEG 0.7 (compiled on " __DATE__ ", " __TIME__ ")\n";

/*
 *--------------------------------------------------------------
 *
 * get_more_data --
 *
 *	Called by correct_underflow in bit parsing utilities to
 *      read in more data.
 *
 * Results:
 *	Input buffer updated, buffer length updated.
 *      Returns 1 if data read, 0 if EOF, -1 if error.
 *
 * Side effects:
 *      None.
 *
 *--------------------------------------------------------------
 */

int get_more_data(unsigned int *buf_start, int max_length, int *length_ptr, unsigned int **buf_ptr)
{
  
  int length, num_read, request;
  unsigned char *buffer, *mark;

  if (EOF_flag) return 0;

  length = *length_ptr;
  buffer = (unsigned char *) *buf_ptr;

  if (length > 0) {
    memcpy((unsigned char *) buf_start, buffer, (length*4));
    mark = ((unsigned char *) (buf_start + length));
  }
  else {
    mark = (unsigned char *) buf_start;
    length = 0;
  }

  request = (max_length-length)*4;
  
//  num_read = fread( mark, 1, request, input);
	num_read = ReadAsync(input, mark, request);

  /* Paulo Villegas - 26/1/1993: Correction for 4-byte alignment */
  {
    int num_read_rounded;
    unsigned char *index;
 
    num_read_rounded = num_read & 0xfffffffc;
 
    /* this can happen only if num_read<request; i.e. end of file reached */
    if( num_read_rounded < num_read )
      { 
 	num_read_rounded+=4;
 	/* fill in with zeros */
 	for( index=mark+num_read; index<mark+num_read_rounded; *(index++)=0 );
 	/* advance to the next 4-byte boundary */
 	num_read = num_read_rounded;
      }
  }
  
  if (num_read < 0) {
    return -1;
  }
  else if (num_read == 0) {
    *buf_ptr = buf_start;
    
    /* Make 32 bits after end equal to 0 and 32
       bits after that equal to seq end code
       in order to prevent messy data from infinite
       recursion.
    */

    *(buf_start + length) = 0x0;
    *(buf_start + length+1) = SEQ_END_CODE;

    EOF_flag = 1;
    return 0;
  }

  num_read >>= 2;

  *buf_ptr = buf_start;
  *length_ptr = length + num_read;
 
  return 1;
}

/*
 *--------------------------------------------------------------
 *
 * int_handler --
 *
 *	Handles Cntl-C interupts..
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *--------------------------------------------------------------
 */
void int_handler(int dummy)
{
	if (!quietFlag) fprintf(stderr, "Interrupted!\n");
	if (curVidStream) DestroyVidStream(curVidStream);

	if(input)
		CloseAsync(input);

	exit(1);
}


/*
 *--------------------------------------------------------------
 *
 * init_timer --
 *
 *	Prepare timer.device and IORequest for later use
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *--------------------------------------------------------------
 */

void init_timer(void)
{
	/* Create port for timer device communications */
	if(TimerMP = CreatePort(NULL, 0))
		/* Create message block for device IO */
		if(TimerIO = (struct timerequest *)CreateExtIO(TimerMP, sizeof(struct timerequest)))

			/* Open the timer device with UNIT_MICROHZ */
			if(!OpenDevice(TIMERNAME, UNIT_MICROHZ, (struct IORequest *)TimerIO, 0))
			{
				TimerBase = (struct Library *)TimerIO->tr_node.io_Device;
				TimerIO->tr_node.io_Command = TR_ADDREQUEST;
				return;
			}
			else
				fprintf(stderr, "Error: Can't open Timer.device\n");
		else
			fprintf(stderr, "Error: Can't create IO request\n");
	else
		fprintf(stderr, "Error: Can't create port\n");

	exit(1);
}
/*
 *--------------------------------------------------------------
 *
 * close_timer --
 *
 *	Close timer.device, remove IORequest and message port
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *--------------------------------------------------------------
 */

void close_timer(void)
{
	if(TimerBase)
		CloseDevice((struct IORequest *)TimerIO);  /* Close Timer device */

	if(TimerIO)
		DeleteExtIO((struct IORequest *)TimerIO);

	if(TimerMP)
		DeletePort(TimerMP);
}

/*
 *--------------------------------------------------------------
 *
 * main --
 *
 *	Parses command line, starts decoding and displaying.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *--------------------------------------------------------------
 */

void main(int argc, char **argv)
{
  static VidStream *theStream;
  extern int lores;
  LONG opts[OPT_COUNT];
  struct RDArgs *rdargs;

  ditherType = FULL_COLOR_DITHER;
  noDisplayFlag = 0;

	memset(opts, 0, sizeof(opts));

	if(rdargs = (struct RDArgs *)AllocDosObject(DOS_RDARGS, NULL))
	{
		rdargs->RDA_ExtHelp = HELP_TEXT;

		if(rdargs = ReadArgs(TEMPLATE, opts, rdargs))
		{
			if(opts[OPT_NOP])
	      TogglePFlag();

			if(opts[OPT_NOB])
	      ToggleBFlag();

			if(opts[OPT_DITHER])
			{
				switch(strstr(dither_names, strlwr((char *)opts[OPT_DITHER])) - dither_names)
				{
					case 5:		/* color */
						ditherType = FULL_COLOR_DITHER;
						break;

					case 11:	/* hiresham */
						ditherType = FULL_COLOR_DITHER;
						lores = FALSE;
						break;

					case 0:	/* ham6 */
						ditherType = FULL_COLOR_DITHER;
						ham6 = TRUE;
						break;

					case 25:	/* cybergfx */
						ditherType = CYBERGFX_DITHER;
						break;

					case 34:	/* cybergfxgray */
						ditherType = CYBERGFXGRAY_DITHER;
						break;

					case 42:	/* gray */
						ditherType = GRAY_DITHER;
						break;

					case 52:	/* none */
						ditherType = NO_DITHER;
						break;

					default:
						fprintf(stderr, "You should specify legal dither name after DITHER keyword.\n");
						FreeArgs(rdargs);
						exit(10);
				}
			}

			if(opts[OPT_EACHSTAT])
#ifdef ANALYSIS
				showEachFlag = 1;
#else
			{
				fprintf(stderr, "To use EACHSTAT, recompile with DEFINE ANALYSIS in CFLAGS\n");
				exit(1);
			}
#endif

			if(opts[OPT_QUIET])
	    	quietFlag = 1;

			if(opts[OPT_LOOP])
				loopFlag = 1;

			if(opts[OPT_NODISPLAY])
				noDisplayFlag = 1;

			if(opts[OPT_PUBSCREEN])
			{
				if(!stricmp("default", (char *)opts[OPT_PUBSCREEN]))
					pubname_ptr = NULL;
				else
				{
					strcpy(pubscreen_name, (char *)opts[OPT_PUBSCREEN]);
					pubname_ptr = pubscreen_name;
				}

				cyber_pub = 1;
			}

			if(opts[OPT_MODEID])
				if(sscanf((char *)opts[OPT_MODEID], "%x", &modeid) != 1)
				{
					fprintf(stderr, "ModeID should be an hexadecimal value.\n");
					FreeArgs(rdargs);
					exit(10);
				}

			if(opts[OPT_FPS])
				if(sscanf((char *)opts[OPT_FPS], "%d", &framerate) != 1)
				{
					fprintf(stderr, "You should specify a number after FRAMERATE keyword.\n");
					FreeArgs(rdargs);
					exit(10);
				}

			if(opts[OPT_BUFFER])
				if(sscanf((char *)opts[OPT_BUFFER], "%d", &buf_length) != 1)
				{
					fprintf(stderr, "You should specify a number after BUFFER keyword.\n");
					FreeArgs(rdargs);
					exit(10);
				}

			if(opts[OPT_FILE])
			{
//				input = fopen((char *)opts[OPT_FILE], "r");

				input = OpenAsync((char *)opts[OPT_FILE], MODE_READ, buf_length);

				if(input == NULL)
				{
					fprintf(stderr, "Could not open file %s\n", (char *)opts[OPT_FILE]);
					FreeArgs(rdargs);
					exit(10);
	      }

				strcpy(animname, (char *)opts[OPT_FILE]);

			  signal(SIGINT, int_handler);

			  init_tables();
  
			  switch (ditherType)
				{
					case CYBERGFX_DITHER:
						InitColorDither();
						modeid = InitCyberGfxDisplay(modeid);
						HAM8_draw = (void (*)(void *, int, int)) DrawCyberGfxImage;
						DoDitherImage = ColorDitherImage_RGB;
						break;

					case CYBERGFXGRAY_DITHER:
						InitColorDither();
						modeid = InitCyberGfxDisplay(modeid);
						HAM8_draw = (void (*)(void *, int, int)) DrawCyberGfxImage;
						DoDitherImage = NoDitherImage;
						break;

					case GRAY_DITHER:
						InitColorDither();
						modeid = InitGrayDisplay(modeid);
						HAM8_draw = (void (*)(void *, int, int)) DrawGrayImage;
						DoDitherImage = NoDitherImage;
						break;

					case FULL_COLOR_DITHER:
						InitColorDither();
						InitColorDisplay();
						break;

					case NO_DITHER:
						HAM8_draw = (void (*)(void *, int, int)) NoDitherImage;	// method casting ... argh!
						DoDitherImage = NoDitherImage;
						break;
				}

				init_timer();

/*
 *  The new restart handling has not been checked out very closely with changing
 *  (non)intra scale matrices!
 */

				theStream = NewVidStream(buf_length);

				if (setjmp(env) != 0)
				{
					mpegInitVidRsrc(); /* fix bug in static first in mpegVidRsrc */

//					rewind(input);
					SeekAsync(input, 0, MODE_START);

					EOF_flag = 0;
					theStream->bit_offset = 0;
					theStream->buf_length = 0;
					theStream->buffer = NULL;
					totNumFrames = 0;
#ifdef ANALYSIS 
					init_stats();
#endif
				}

				realTimeStart = ReadSysClock();
				while (mpegVidRsrc(0, theStream));

				CloseAsync(input);
			}

			FreeArgs(rdargs);
		}
		else
		{
			PrintFault(IoErr(), argv[0]);
			exit(10);
		}

		FreeDosObject(DOS_RDARGS, rdargs);
	}
	else
	{
		PrintFault(IoErr(), argv[0]);
		exit(10);
	}
}
	

/*
 *  Dummy display method
 *
 */
void NoDitherImage(unsigned char *l, unsigned char *Cr, unsigned char *Cb,
		   unsigned char *disp, int h, int w)
{}

