/* mpeg2dec.c, main(), initialization, option processing                    */

/*
 * All modifications (mpeg2decode -> mpeg2play) are
 * Copyright (C) 1994, Stefan Eckart. All Rights Reserved.
 */

/* Copyright (C) 1994, MPEG Software Simulation Group. All Rights Reserved. */

/*
 * Disclaimer of Warranty
 *
 * These software programs are available to the user without any license fee or
 * royalty on an "as is" basis.  The MPEG Software Simulation Group disclaims
 * any and all warranties, whether express, implied, or statuary, including any
 * implied warranties or merchantability or of fitness for a particular
 * purpose.  In no event shall the copyright-holder be liable for any
 * incidental, punitive, or consequential damages of any kind whatsoever
 * arising from the use of these programs.
 *
 * This disclaimer of warranty extends to the user of these programs and user's
 * customers, employees, agents, transferees, successors, and assigns.
 *
 * The MPEG Software Simulation Group does not represent or warrant that the
 * programs furnished hereunder are free of infringement of any third-party
 * patents.
 *
 * Commercial implementations of MPEG-1 and MPEG-2 video, including shareware,
 * are subject to royalty fees to patent holders.  Many of these patents are
 * general enough such that they are unavoidable regardless of implementation
 * design.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

#ifdef USE_TIME
#include <sys/time.h>
#endif

#define GLOBAL
#include "config.h"
#include "global.h"

extern int errno;

/* private prototypes */
static void initdecoder _ANSI_ARGS_((void));
static void options _ANSI_ARGS_((int *argcp, char **argvp[]));
static int getval _ANSI_ARGS_((char *argv[]));

/* private data */
static int loopflag;

#ifdef USE_TIME
static void doframerate();
static int framerate = 0;
static struct timeval tftarget;
#endif

int first, framenum;
#ifdef USE_TIME
int runtime;
struct timeval tstart,tstop;
#endif

#include <stdlib.h>
#include <X11/Intrinsic.h>
#include <Xm/Xm.h>
#include <Xm/Form.h>
#include <Xm/Frame.h>
#include <Xm/DrawingA.h>
#include <Xm/PushB.h>
#include <Xm/ArrowB.h>
#include <Xm/TextF.h>
#include <Xm/FileSB.h>

#include "loop.xbm"
#include "play.xbm"
#include "rewind.xbm"
#include "step.xbm"
#include "stop.xbm"
#include "fallbacks.h"

XtAppContext  app;

Widget toplevel, mainW, drawingW, picW, formW, rewindW, stopW, stepW, playW,
       loopW, frformW, larrowW, frnumW, rarrowW, numW, saveW, exitW, saveFile;

Boolean PLAY, LOOP;

static  XtWorkProcId dovid = (XtWorkProcId) NULL;

#include <unistd.h>

Boolean
Draw(data)
XtPointer data;
{
  char s[6];
  

  if (!PLAY)
    return (True);

  if ((framenum == 1) && (!LOOP))
    PLAY = False;

  if (framenum < 0) {
    /* (re)set to begin of file */
    ld = &base;
    if (base.infile!=0)
      lseek(base.infile,0l,0);
    initbits();

    if (twostreams)
    {
      ld = &enhan;
      lseek(enhan.infile,0l,0);
      initbits();
      ld = &base;
    }

    framenum = 0;
  }
  
  if (framenum == 1) {
#ifdef USE_TIME
    gettimeofday(&tstart,(struct timezone *)NULL);
    if (framerate > 0)
      gettimeofday(&tftarget,(struct timezone *)NULL);
#endif /* USE_TIME */
  }
  
  if (getheader()) {  
    if (first)
    {
/*      initdecoder(); */
      if (outtype==T_X11)
      {

#ifdef USE_TIME
	if (framerate == 0) {
	  char *s;

	  s = strdup((char *)XmTextFieldGetString(frnumW));
	  if (strcmp(s, "max") == 0)
	    framerate = 0;
	  else
	    framerate = atoi(s);
	  free(s);
	  if (framerate < 0) framerate = 0;
	  if (framerate > 99) framerate = 99; 
	}
	else {
	  char s[3];
  
	  sprintf(s, "%d", framerate);
	  XmTextFieldSetString(frnumW, s);
	}
#else
	XtUnmanageChild(frformW);
#endif /* USE_TIME */
	
/*        init_display("");*/
        init_dither();
	
	{
	  Dimension w, h;

	  XtVaGetValues(toplevel, XmNwidth, &w, XmNheight, &h, NULL);
	  XtVaSetValues(toplevel, XmNminWidth, w, XmNminHeight, h, NULL);
	  XtVaSetValues(toplevel, XmNmaxWidth, w, XmNmaxHeight, h, NULL);
	}
      }

      first = 0;
    }

    getpicture(framenum);

    if (framenum != 0) {
      sprintf(s, "%d", framenum);
    }
    else {
      sprintf(s, "max");
    }
    XmTextFieldSetString(numW, s);

    if (!secondfield) {
#ifdef USE_TIME
      if (framerate > 0)
      doframerate();
#endif	
      framenum++;
    }
  }
  else {
    if (framenum!=0)
    {
      /* put last frame */
      putlast(framenum);
    }
#ifdef USE_TIME
    gettimeofday(&tstop,(struct timezone *)NULL);
    runtime = 100*(tstop.tv_sec-tstart.tv_sec)
              + (tstop.tv_usec-tstart.tv_usec)/10000;
    if (!quiet && runtime!=0)
      printf("%d.%02d seconds, %d frames, %d.%02d fps\n",runtime/100,
	     runtime%100, framenum, ((10000*framenum+runtime/2)/runtime)/100,
	     ((10000*framenum+runtime/2)/runtime)%100);
#endif

    framenum = -1;
    if (!LOOP)
      PLAY = False;
  }
  
  return (False);
}


void
CountDown(Widget wid, XtPointer client_data, XtPointer call_data) {
#ifdef USE_TIME
  char s[3];
  
  if (framerate > 0) {
    gettimeofday(&tftarget,(struct timezone *)NULL);
    tftarget.tv_usec += 1000000 / framerate;
    
    /* this is where we should be */
    if (tftarget.tv_usec >= 1000000) {
      tftarget.tv_usec -= 1000000;
      tftarget.tv_sec++;
    }
    framerate--;
    if (framerate > 0)
      sprintf(s, "%d", framerate);
    else
      sprintf(s, "max");
    XmTextFieldSetString(frnumW, s);
  }
#endif /* USE_TIME */
}

void
CountUp(Widget wid, XtPointer client_data, XtPointer call_data) {
#ifdef USE_TIME
  char s[3];
  
  if (framerate > 0) {
    gettimeofday(&tftarget,(struct timezone *)NULL);
    tftarget.tv_usec += 1000000 / framerate;
    
    /* this is where we should be */
    if (tftarget.tv_usec >= 1000000) {
      tftarget.tv_usec -= 1000000;
      tftarget.tv_sec++;
    }
  }

  if (framerate < 99) {
    sprintf(s, "%d", ++framerate);
    XmTextFieldSetString(frnumW, s);
  }
#endif /* USE_TIME */
}

void
PlayVid(Widget wid, XtPointer client_data, XtPointer call_data) {
  if (!PLAY) {
    PLAY = True;
    dovid = XtAppAddWorkProc(app, Draw, NULL);
  }
  LOOP = False;
}

void
StepVid(Widget wid, XtPointer client_data, XtPointer call_data) {
  if (!PLAY) {
    PLAY = True;
    dovid = XtAppAddWorkProc(app, Draw, NULL);
  }
  if (framenum > 1) {
    LOOP = False;
    Draw(NULL);
    PLAY = False;
  }
  
}

void
StopVid(Widget wid, XtPointer client_data, XtPointer call_data) {
  PLAY = False;
}

void
RewindVid(Widget wid, XtPointer client_data, XtPointer call_data) {
  framenum = -1;
  StepVid(NULL, NULL, NULL);
  LOOP = False;
}

void
LoopVid(Widget wid, XtPointer client_data, XtPointer call_data) {
  if (!PLAY) {
    PLAY = True;
    dovid = XtAppAddWorkProc(app, Draw, NULL);
  }
  LOOP = True;
}

void
manageSave(Widget wid, XtPointer client_data, XtPointer call_data) {
  XtManageChild(saveFile);
}
void
unmanageSave(Widget wid, XtPointer client_data, XtPointer call_data) {
  XtUnmanageChild(saveFile);
}

void
SaveFile(Widget wid, XtPointer client_data, XtPointer call_data) {
  char *filename = (char *)malloc(256 * sizeof(char));
  char buf[2048];
  int  outfile, l;
  long position;

  XmStringGetLtoR(((XmFileSelectionBoxCallbackStruct *)call_data)->value,
		  XmSTRING_DEFAULT_CHARSET, &filename);

  position = tell(base.infile);
  fprintf(stderr, "pos: %d\n", position);
  fprintf(stderr, "output: %s\n", filename);

  lseek(base.infile, 0L, SEEK_SET);

  if ((outfile = open(filename, O_CREAT| O_WRONLY, 
		      S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH))
      != -1) {
    while ((l = read(base.infile, buf, sizeof(buf))) > 0)
      write(outfile, buf, l);
    
    close(outfile);
  }
  else {
    fprintf(stderr, "Couldn't open '%s' for writing.\nReason: %s\n",
	    filename, strerror(errno));
  }
  free(filename);
  
  lseek(base.infile, position, SEEK_SET);
}


void Exit();

int main(argc,argv)
int argc;
char *argv[];
{
  Display      *display;
  unsigned int black, fg, bg;
  Pixmap       pixmap;
    
  options(&argc,&argv);

  ld = &base; /* select base layer context */

  /* open MPEG input file(s) */
  if (argv[1][0]=='-' || argv[1][1]=='\0')
  {
    base.infile = 0; /* stdin */
    loopflag = 0; /* can't rewind stdin */
  }
  else if ((base.infile=open(argv[1],O_RDONLY|O_BINARY))<0)
  {
    sprintf(errortext,"Input file %s not found\n",argv[1]);
    error(errortext);
  }

  if (base.infile != 0)
  {
    initbits();
    startcode();
    switch(showbits(32))
    {
    case SEQ_START_CODE:
      break;
    case PACK_START_CODE:
      sysstream = 1;
      break;
    default:
      error("This not a valid MPEG system or video stream\n");
      break;
    }
    lseek(base.infile,0l,0);
    initbits();
  }

  if (argc==4)
  {
    ld = &enhan; /* select enhancement layer context */
    
    if ((enhan.infile = open(argv[2],O_RDONLY|O_BINARY))<0)
    {
      sprintf(errortext,"Input file %s not found\n",argv[2]);
      error(errortext);
    }

    twostreams = 1;
    ld = &base;
  }

  first = 1;

  toplevel = XtVaAppInitialize(&app, "Mpeg2play", NULL, 0, &argc, argv,
			       fallbacks, NULL);

  if (getheader())
    initdecoder();
  
  init_display("");

  mainW = XmCreateForm(toplevel, "main", NULL, 0);

  display = XtDisplay(mainW);
  black = BlackPixel (display, DefaultScreen(display));

  formW = XmCreateForm(mainW, "form", NULL, 0);

  rewindW = XmCreatePushButton(formW, "rewind", NULL, 0);
  XtAddCallback(rewindW, XmNactivateCallback, RewindVid, NULL);
  XtVaGetValues(rewindW, XmNforeground, &fg, XmNbackground, &bg, NULL);
  pixmap = XCreatePixmapFromBitmapData(display,
	 RootWindowOfScreen(XtScreen(toplevel)),
	 rewind_bits, rewind_width, rewind_height,
	 fg, bg, DefaultDepthOfScreen(XtScreen(toplevel)));
  XtVaSetValues(rewindW, XmNlabelType, XmPIXMAP, XmNlabelPixmap, pixmap, NULL);

  stopW = XmCreatePushButton(formW, "stop", NULL, 0);
  XtAddCallback(stopW, XmNactivateCallback, StopVid, NULL);
  XtVaGetValues(stopW, XmNforeground, &fg, XmNbackground, &bg, NULL);
  pixmap = XCreatePixmapFromBitmapData(display,
	 RootWindowOfScreen(XtScreen(toplevel)),
	 stop_bits, stop_width, stop_height,
	 fg, bg, DefaultDepthOfScreen(XtScreen(toplevel)));
  XtVaSetValues(stopW, XmNlabelType, XmPIXMAP, XmNlabelPixmap, pixmap, NULL);

  stepW = XmCreatePushButton(formW, "step", NULL, 0);
  XtAddCallback(stepW, XmNactivateCallback, StepVid, NULL);
  XtVaGetValues(stepW, XmNforeground, &fg, XmNbackground, &bg, NULL);
  pixmap = XCreatePixmapFromBitmapData(display,
	 RootWindowOfScreen(XtScreen(toplevel)),
	 step_bits, step_width, step_height,
	 fg, bg, DefaultDepthOfScreen(XtScreen(toplevel)));
  XtVaSetValues(stepW, XmNlabelType, XmPIXMAP, XmNlabelPixmap, pixmap, NULL);

  playW = XmCreatePushButton(formW, "play", NULL, 0);
  XtAddCallback(playW, XmNactivateCallback, PlayVid, NULL);
  XtVaGetValues(playW, XmNforeground, &fg, XmNbackground, &bg, NULL);
  pixmap = XCreatePixmapFromBitmapData(display,
	 RootWindowOfScreen(XtScreen(toplevel)),
	 play_bits, play_width, play_height,
	 fg, bg, DefaultDepthOfScreen(XtScreen(toplevel)));
  XtVaSetValues(playW, XmNlabelType, XmPIXMAP, XmNlabelPixmap, pixmap, NULL);

  loopW = XmCreatePushButton(formW, "loop", NULL, 0);
  XtAddCallback(loopW, XmNactivateCallback, LoopVid, NULL);
  XtVaGetValues(loopW, XmNforeground, &fg, XmNbackground, &bg, NULL);
  pixmap = XCreatePixmapFromBitmapData(display,
	 RootWindowOfScreen(XtScreen(toplevel)),
	 loop_bits, loop_width, loop_height,
	 fg, bg, DefaultDepthOfScreen(XtScreen(toplevel)));
  XtVaSetValues(loopW, XmNlabelType, XmPIXMAP, XmNlabelPixmap, pixmap, NULL);

  frformW = XmCreateForm(formW, "frform", NULL, 0);

  larrowW = XmCreateArrowButton(frformW, "larrow", NULL, 0);
  XtAddCallback(larrowW, XmNactivateCallback, CountDown, NULL);

  frnumW = XmCreateTextField(frformW, "frnum", NULL, 0);

  rarrowW = XmCreateArrowButton(frformW, "rarrow", NULL, 0);
  XtAddCallback(rarrowW, XmNactivateCallback, CountUp, NULL);

  exitW = XmCreatePushButton(formW, "exit", NULL, 0);
  XtAddCallback(exitW, XmNactivateCallback, Exit, NULL);

  numW = XmCreateTextField(formW, "num", NULL, 0);

  saveW = XmCreatePushButton(formW, "save", NULL, 0);
  XtAddCallback(saveW, XmNactivateCallback, manageSave, NULL);

  drawingW = XmCreateFrame(mainW, "drawing", NULL, 0);

  picW = XmCreateDrawingArea(drawingW, "pic", NULL, 0);
  XtAddCallback(picW, XmNexposeCallback, Redraw, NULL);

  saveFile = XmCreateFileSelectionDialog(mainW, "saveFile", NULL, 0);
  XtAddCallback(saveFile, XmNokCallback, SaveFile, NULL);
  XtAddCallback(saveFile, XmNcancelCallback, unmanageSave, NULL);
  XtUnmanageChild(XmFileSelectionBoxGetChild(saveFile, XmDIALOG_HELP_BUTTON)); 

  XtVaSetValues(picW, XmNbackground, black, NULL);
  
  XtVaSetValues(picW, XmNwidth,
		(horizontal_size > 320)?horizontal_size:320, NULL);
  XtVaSetValues(picW, XmNheight,
		(vertical_size > 200)?vertical_size:200, NULL);
  
  framenum = -1;
  PLAY = True;
  LOOP = False;
  
  XtManageChild(picW);
  XtManageChild(drawingW);
  XtManageChild(exitW);
  XtManageChild(saveW);
  XtManageChild(numW);
  XtManageChild(rarrowW);
  XtManageChild(frnumW);
  XtManageChild(larrowW);
  XtManageChild(frformW);
  XtManageChild(loopW);
  XtManageChild(playW);
  XtManageChild(stepW);
  XtManageChild(stopW);
  XtManageChild(rewindW);
  XtManageChild(formW);
  XtManageChild(mainW);

  XtRealizeWidget(toplevel);

  dovid = XtAppAddWorkProc(app, Draw, NULL);

  /* Here begins the Loop that NEVER, NEVER ends. */

  XtAppMainLoop(app);
}


void Exit()
{
  if (dovid)
    XtRemoveWorkProc(dovid);

  if (base.infile!=0)
    close(base.infile);

  if (twostreams)
    close(enhan.infile);

/*
#ifdef USE_TIME
  gettimeofday(&tstop,(struct timezone *)NULL);
  runtime = 100*(tstop.tv_sec-tstart.tv_sec)
            + (tstop.tv_usec-tstart.tv_usec)/10000;
  if (!quiet && runtime!=0)
    printf("%d.%02d seconds, %d frames, %d.%02d fps\n",runtime/100, runtime%100,
      framenum, ((10000*framenum+runtime/2)/runtime)/100,
      ((10000*framenum+runtime/2)/runtime)%100);
#endif

  if (outtype==T_X11)
    exit_display();
 */
  exit(0);
}

static void initdecoder()
{
  int i, cc, size;
  static int blk_cnt_tab[3] = {6,8,12};

  /* check scalability mode of enhancement layer */
  if (twostreams && enhan.scalable_mode!=SC_SNR &&
     !(base.scalable_mode==SC_DP && base.scalable_mode==SC_DP))
    error("unsupported scalability mode\n");

  /* clip table */
  if (!(clp=(unsigned char *)malloc(1024)))
    error("malloc failed\n");

  clp += 384;

  for (i= -384; i<640; i++)
    clp[i] = (i<0) ? 0 : ((i>255) ? 255 : i);

  /* force MPEG-1 parameters */
  if (!base.mpeg2)
  {
    prog_seq = 1;
    prog_frame = 1;
    pict_struct = FRAME_PICTURE;
    frame_pred_dct = 1;
    chroma_format = CHROMA420;
    matrix_coefficients = 5;
  }

  /* round to nearest multiple of coded macroblocks */
  mb_width = (horizontal_size+15)/16;
  mb_height = (base.mpeg2 && !prog_seq) ? 2*((vertical_size+31)/32)
                                        : (vertical_size+15)/16;
  coded_picture_width = 16*mb_width;
  coded_picture_height = 16*mb_height;

  chrom_width = (chroma_format==CHROMA444) ? coded_picture_width
                                           : coded_picture_width>>1;
  chrom_height = (chroma_format!=CHROMA420) ? coded_picture_height
                                            : coded_picture_height>>1;
  blk_cnt = blk_cnt_tab[chroma_format-1];

  for (cc=0; cc<3; cc++)
  {
    if (cc==0)
      size = coded_picture_width*coded_picture_height;
    else
      size = chrom_width*chrom_height;

    if (!(refframe[cc] = (unsigned char *)malloc(size)))
      error("malloc failed\n");

    if (!(oldrefframe[cc] = (unsigned char *)malloc(size)))
      error("malloc failed\n");

    if (!(auxframe[cc] = (unsigned char *)malloc(size)))
      error("malloc failed\n");

    if (base.scalable_mode==SC_SPAT)
    {
      /* this assumes lower layer is 4:2:0 */
      if (!(llframe0[cc] = (unsigned char *)malloc((llw*llh)/(cc?4:1))))
        error("malloc failed\n");
      if (!(llframe1[cc] = (unsigned char *)malloc((llw*llh)/(cc?4:1))))
        error("malloc failed\n");
    }
  }

  if (base.scalable_mode==SC_SPAT)
  {
    if (!(lltmp = (short *)malloc(llw*((llh*vn)/vm)*sizeof(short))))
      error("malloc failed\n");
  }

  /* IDCT */
  init_idct();

/*
  if (outtype==T_X11)
  {
    init_display("");
    init_dither();
  }
*/
}

void error(text)
char *text;
{
  fprintf(stderr,text);
  exit(1);
}

/* compliance warning messages to user, but don't exit() */
void warning(text)
char *text;
{
  fprintf(stderr,"%s\n",text);
} 

#ifdef TRACE
/* trace output */
void printbits(code,bits,len)
int code,bits,len;
{
  int i;
  for (i=0; i<len; i++)
    printf("%d",(code>>(bits-1-i))&1);
}
#endif

/* option processing */
static void options(argcp,argvp)
int *argcp;
char **argvp[];
{
  outtype = T_X11;

  while (*argcp>1 && (*argvp)[1][0]=='-')
  {
    if ((*argvp)[1][1]=='\0') /* single '-' */
      break;

    while ((*argvp)[1][1])
    {
      switch (toupper((*argvp)[1][1]))
      {
#ifdef USE_TIME
      case 'F':
	framerate = getval(*argvp);
	break;
#endif
      case 'V':
        verbose = getval(*argvp);
        break;
      case 'O':
        outtype = getval(*argvp);
        break;
      case 'L':
        loopflag = 1;
        break;
      case 'S':
        sflag = 1;
        break;
#ifdef TRACE
      case 'T':
        trace = 1;
        break;
#endif
      case 'Q':
        quiet = 1;
        break;
      default:
        fprintf(stderr,"undefined option -%c ignored\n",(*argvp)[1][1]);
      }

      (*argvp)[1]++;
    }

    (*argvp)++;
    (*argcp)--;
  }

  if (sflag)
  {
    /* input file for spatial prediction */
    llinputname = (*argvp)[1];
    (*argvp)++;
    (*argcp)--;
  }

  if (*argcp!=2 && *argcp!=3)
  {
    printf("\n%s, %s\n",version,author);
    printf("Usage:   mpeg2play {options} input.m2v {upper.m2v}\n\
Options: -vn  verbose output (n: level)\n\
         -on  output format (0: X11 (default), 1: none)\n\
         -l   loop sequence\n\
         -q   quiet (no error output)\n\
         -s infile   spatial scalable sequence\n");
#ifdef USE_TIME
    printf("\
	 -fn  frame rate (default = as fast as possible)\n");
#endif    
    exit(0);
  }
}

static int getval(argv)
char *argv[];
{
  int val;

  if (sscanf(argv[1]+2,"%d",&val)!=1)
    return 0;

  while (isdigit(argv[1][2]))
    argv[1]++;

  return val;
}

#ifdef USE_TIME
static void doframerate()
{
  struct timeval tfdiff;

  /* Compute desired frame rate */
  if (framerate <= 0)
    return;
  tftarget.tv_usec += 1000000 / framerate;

  /* this is where we should be */
  if (tftarget.tv_usec >= 1000000)
  {
    tftarget.tv_usec -= 1000000;
    tftarget.tv_sec++;
  }

  /* this is where we are */
  gettimeofday(&tfdiff,(struct timezone *)NULL);

  tfdiff.tv_usec = tftarget.tv_usec - tfdiff.tv_usec;
  tfdiff.tv_sec  = tftarget.tv_sec  - tfdiff.tv_sec;
  if (tfdiff.tv_usec < 0)
  {
    tfdiff.tv_usec += 1000000;
    tfdiff.tv_sec--;
  }

  /* See if we are already lagging behind */
  if (tfdiff.tv_sec < 0 || (tfdiff.tv_sec == 0 && tfdiff.tv_usec <= 0))
    return;
  
  /* Spin for awhile */
  select(0,NULL,NULL,NULL,&tfdiff);
}
#endif
