/*
 * Copyright (c) 1993 Niklas Rjemo
 * 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 AUTHOR 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 AUTHOR
 * HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 * THE AUTHOR 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 AUTHOR HAS NO OBLIGATION TO
 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 */
/*
 * Title:   mpegplay.c
 */

#include "wimp.h"
#include "wimpt.h"
#include "win.h"
#include "event.h"
#include "baricon.h"
#include "res.h"
#include "resspr.h"
#include "menu.h"
#include "template.h"
#include "colourtran.h"
#include "sprite.h"
#include "dbox.h"
#include "werr.h"
#include "flex.h"
#include "visdelay.h"
#include "xferrecv.h"
#include "saveas.h"
#include "magnify.h"

#include "extra.h"
#include "video.h"

#include <stdlib.h>
#include <setjmp.h>
#include <string.h>

/********************************* CONSTANTS ********************************/

/* Menu items */
#define IM_info     1
#define IM_quit     2

#define MM_run           1
#define MM_pause         2
#define MM_stop          3
#define MM_dither        4
#define MM_magnify       5
#define MM_save          6

#define MM_ordered       1
#define MM_fs4           2
#define MM_fs2           3
#define MM_fs2fast       4
#define MM_hybrid        5
#define MM_hybrid2       6
#define MM_2x2           7
#define MM_gray          8
#define MM_mono          9
#define MM_threshold    10

/* Info box field for the version string */
#define info_field    4

#define XtoWork(x) ((x)<<1)
#define YtoWork(y) ((y)<<1)
#define SCALE(t,x) (t = mpeg_multiply*(x),t/mpeg_divide)
#define REVSCALE(t,x) (t = mpeg_divide*(x),t/mpeg_multiply)

/******************************** GLOBAL DATA *******************************/

/* Application version */
static char *Version_String = "1.20 (" __DATE__ ")";

/* The top of the menu tree */
static menu icon_menu;
static menu mpeg_menu;
static menu mpeg_menu_dither;

/* Handle for the example window */
static wimp_w mpeg_handle;

/* Flag - is the window open */
static BOOL mpeg_open = FALSE;
static BOOL mpeg_status = MM_stop;

static int oldDitherType = NO_DITHER;
extern int ditherType;

sprite_pixtrans mpeg_trans[256];     /* colour translation table     */
sprite_area *mpeg_area = 0;
static int AreaSize;
sprite_id    mpeg_sprite;
static int   mpeg_mode = 21;
static int   mpeg_sprite_size;
static int   sprite_width = 0;
static int   sprite_height = 0;
static int   mpeg_width = 8*16;
static int   mpeg_height= 1*16;
static int   mpeg_multiply = 1;
static int   mpeg_divide = 1;

jmp_buf OkEnd,ErrorEnd;

/***************************** WINDOW FUNCTIONS *****************************/

static int depthDither(int dither)
{
  if(dither == NO_DITHER) return -1;
  return (dither == MONO_DITHER || dither == MONO_THRESHOLD)?1:8;
}
#if 0
static int scaleDither(int dither)
{
  if(dither == NO_DITHER) return -1;
  return dither==Twox2_DITHER?2:1;
}
#endif
void mpeg_get_info(int dither,int w,int h)
{
  mpeg_mode = depthDither(dither)==1?18:21;
  mpeg_width = w;
  mpeg_height = h;
  if (depthDither(dither)==1)
    mpeg_sprite_size = ((mpeg_width+31)&~31)*mpeg_height / 8;
  else
    mpeg_sprite_size = ((mpeg_width+3)&~3)*mpeg_height;
} 



void CreateSprite(void)
{
  sprite_ptr ptr;
  AreaSize = sizeof(sprite_header)+sizeof(sprite_area);

  if(mpeg_area) {
    if((depthDither(oldDitherType) == depthDither(ditherType)) &&
       (mpeg_width == sprite_width) && (mpeg_height == sprite_height))
      return;
    free(mpeg_area);
    mpeg_area = 0;
    oldDitherType = NO_DITHER;
  }

  AreaSize += mpeg_sprite_size;
  if((mpeg_area = malloc(AreaSize)) == NULL) {
    werr(FALSE,"Sorry not enough memory");
    return;
  }
  sprite_area_initialise(mpeg_area, AreaSize);
  wimpt_noerr(sprite_create(mpeg_area, "pict",
                   sprite_nopalette, mpeg_width,mpeg_height, mpeg_mode));


  mpeg_sprite.tag = sprite_id_name;
  mpeg_sprite.s.name = "pict";
  wimpt_noerr(sprite_select_rp(mpeg_area, &mpeg_sprite, &ptr));
  mpeg_sprite.tag = sprite_id_addr;
  mpeg_sprite.s.addr = ptr;
  oldDitherType = ditherType;
  sprite_width = mpeg_width;
  sprite_height = mpeg_height;

#if 0
  {
    sprite_factors factors;
    sprite_pixtrans pixtrans[256];
    wimp_readpixtrans(mpeg_area, &mpeg_sprite, &factors, pixtrans);
  }
  wimpt_checkmode();
#endif  
/* --- read the palette --- */
  wimpt_noerr(colourtran_select_table(mpeg_mode,0,-1,
                                     (wimp_paletteword *)-1,mpeg_trans));

}

/*--- Create the window, yielding its handle. Return TRUE if ok. ---*/
static BOOL create_window(char *name, wimp_w *handle)
{
  wimp_wind *window;    /* Pointer to window definition */

  /* Find template for the window */
    window = template_syshandle(name);
    if (window == 0)
      return FALSE;

  /* Create the window, dealing with errors */
  return (wimpt_complain(wimp_create_wind(window, handle)) == 0);
}

FILE *mpeg_open_file(char *file)
{
  FILE *fp = fopen(file, "r");

  if (fp == NULL) {
    static char err[80];
    sprintf(err,"Could not open file:\n%s",file);
    werr(FALSE,err);
  }
  return fp;
}

/*--- Load from a file. ---*/
static int mpeg_load_file(void)
{
  extern FILE *input;
  char      *filename;

  /* Fetch the type and name of the file */
  int filetype = xferrecv_checkinsert(&filename);

    /* Check file type, get rid of old memory and allocate new */
  if(filetype == 0x123) {
    FILE *fp = mpeg_open_file(filename);
    if(fp != NULL) {
      if(input != NULL)
        fclose(input); 
      input = fp;
      xferrecv_insertfileok();
      return 1;
    }
  }
  return 0;
}


int mpeg_resize(int top,int w,int h)
{
  wimp_wstate    wstate;
  int tmp;
  mpeg_get_info(ditherType,w,h);
  wimpt_noerr(wimp_get_wind_state(mpeg_handle,&wstate));
  wstate.o.box.x1 = wstate.o.box.x0+XtoWork(SCALE(tmp,mpeg_width));
  wstate.o.box.y0 = wstate.o.box.y1-YtoWork(SCALE(tmp,mpeg_height));
  if(top)
    wstate.o.behind = -1;          /* Make sure window is opened in front */
  wimpt_noerr(wimp_open_wind(&wstate.o));
  if(!top)
    CreateSprite();
  return TRUE;
}

static void redo_window(wimp_redrawstr r, BOOL more)
{
  BOOL            more_to_do = more;
  wimp_redrawstr  new_r = r;
  sprite_factors factors;
  sprite_pixtrans pixtrans[256];

  /* --- ask how the WIMP is going to scale our sprite --- */
  wimp_readpixtrans(mpeg_area, &mpeg_sprite, &factors, pixtrans);

  factors.xmag *= mpeg_multiply;
  factors.xdiv *= mpeg_divide;
  factors.ymag *= mpeg_multiply;
  factors.ydiv *= mpeg_divide;

  if(!mpeg_area)
     mpeg_status = MM_stop;
  
  /* --- refresh the window's contents --- */
  while (more_to_do) {
    if(mpeg_area)
      wimpt_complain(sprite_put_scaled(mpeg_area, &mpeg_sprite, 0,
                                       r.box.x0, r.box.y0, &factors, mpeg_trans));

    wimp_get_rectangle(&new_r, &more_to_do);
  }
}

/*--- Individual event routines for the window ---*/
static void redraw_window(wimp_w handle)
{
  /* Redrawing the window here does nothing - just go through the loop */
  int            more;
  wimp_redrawstr r;

  /* Start the redraw */
  r.w = handle;
  wimp_redraw_wind(&r, &more);

  /* Do the redraw loop */
  if (more)
    redo_window(r, more);
}

void update_window(void)
{
  BOOL  more;
  wimp_redrawstr r;
  
  /* --- do the redraw --- */
  r.w = mpeg_handle;
  r.box.x0 = 0;
  r.box.x1 = 4096;
  r.box.y0 = -4096;
  r.box.y1 = 0;
  wimpt_noerr(wimp_update_wind(&r, &more));

  if (more)
    redo_window(r, more);
}

void showBits(char *image)
{
  if(!mpeg_area)
    werr(TRUE,"Internal error no sprite!");
  if (ditherType == MONO_DITHER || ditherType == MONO_THRESHOLD)
    bitreverseB(sizeof(sprite_header)+(char *)mpeg_sprite.s.addr,image,mpeg_sprite_size);
  else
    memcpy(sizeof(sprite_header)+(char *)mpeg_sprite.s.addr,image,mpeg_sprite_size);
  update_window();
}

static void open_window(wimp_openstr *o)
{
  /* Just pass the open request on to the wimp */
  wimpt_noerr(wimp_open_wind(o));
}

static void mpeg_extend(void *h)
{
  wimp_wstate    wstate;
  int tmp;
  wimp_w handle = (wimp_w)h;

  wimpt_noerr(wimp_get_wind_state(handle,&wstate));
  wstate.o.box.x1 = wstate.o.box.x0+XtoWork(SCALE(tmp,mpeg_width));
  wstate.o.box.y0 = wstate.o.box.y1-YtoWork(SCALE(tmp,mpeg_height));
  wimpt_noerr(wimp_open_wind(&wstate.o));
  update_window();
}

/****************************** EVENT HANDLERS ******************************/

/*--- Event handler called on a left click on the icon. ---*/
static void iconclick(wimp_i icon)
{
  icon = icon; /* We don't need the handle: this stops compiler warning */

  /* Open the window - only one allowed */
  if (mpeg_open)
    werr(FALSE, "Only one window may be opened");
  else {
    mpeg_open = mpeg_resize(TRUE,mpeg_width,mpeg_height);
  }
}

/*--- Display the program info box - called from the menu processor. ---*/
static void info_about_program(void)
{
  dbox  d;  /* Dialogue box handle */

  /* Create the dialogue box */
  if (d = dbox_new("ProgInfo"), d != NULL)
  {
    /* Fill in the version number */
    dbox_setfield(d, info_field, Version_String);

    /* Show the dialogue box */
    dbox_show(d);

    /* Keep it on the screen as long as needed */
    dbox_fillin(d);

    /* Dispose of the dialogue box */
    dbox_dispose(&d);
  }
}

/*--- Event handler for the icon menu. ---*/
static void icon_menuproc(void *handle, char *hit)
{
  handle = handle; /* We don't need handle: this stops compiler warning */

  /* Find which menu item was hit and take action as appropriate */
  switch (hit[0])
  {
    case IM_info:
      info_about_program();
      break;

    case IM_quit:
      /* Exit from the program. The wimp gets rid of the window and icon */
      exit(0);
  }
}

BOOL saver(char *filename, void *handle)
{
  os_error *e;
  handle = handle;

  /* --- save the sprite area in a sprite file --- */
    visdelay_begin();
    e = wimpt_complain(sprite_area_save(mpeg_area, filename));
    visdelay_end();

  return (e == 0);
}

/*--- Event handler for the mpeg menu. ---*/
static void mpeg_menuproc(void *handle, char *hit)
{
  handle = handle; /* We don't need handle: this stops compiler warning */

  /* Find which menu item was hit and take action as appropriate */
  switch (hit[0]) {
    case  MM_run:
      if(mpeg_status == MM_stop) {
        extern void start_mpeg(void);
        start_mpeg();
      }
      mpeg_status = MM_run;
      break;
    case  MM_pause:
      mpeg_status = mpeg_status == MM_run?MM_pause:MM_run;
      break;
    case  MM_stop:
      mpeg_status = MM_stop;
      break;
    case  MM_dither:
      switch(hit[1]) {
        case  MM_ordered: ditherType = ORDERED_DITHER; break;
        case  MM_fs4:     ditherType = FS4_DITHER;     break;
        case  MM_fs2:     ditherType = FS2_DITHER;     break;
        case  MM_fs2fast: ditherType = FS2FAST_DITHER; break;
        case  MM_hybrid:  ditherType = HYBRID_DITHER;  break;
        case  MM_hybrid2: ditherType = HYBRID2_DITHER; break;
        case  MM_2x2:     ditherType = Twox2_DITHER;   break;
        case  MM_gray:    ditherType = GRAY_DITHER;    break;
        case  MM_mono:    ditherType = MONO_DITHER;    break;
        case  MM_threshold:ditherType = MONO_THRESHOLD;break;
      }
     break;
    case MM_magnify:
        magnify_select(&mpeg_multiply,&mpeg_divide,100,100,
                       mpeg_extend,(void *)mpeg_handle);
      break;
    case MM_save:
      /* Initiate save operation */
      saveas(0xff9, "MPEGSprite", AreaSize,saver,0,0,0);
      break;

  }
  if(mpeg_status == MM_run)
    event_setmask(event_getmask() & ~wimp_EMNULL);
  else
    event_setmask(event_getmask()|wimp_EMNULL);

}

menu mpeg_menu_maker(void *handle)
{
  extern int no_mpeg(void);
  handle = handle;
  menu_setflags(mpeg_menu,MM_run,   mpeg_status==MM_run,  no_mpeg());
  menu_setflags(mpeg_menu,MM_pause, mpeg_status==MM_pause,mpeg_status==MM_stop);
  menu_setflags(mpeg_menu,MM_stop,  mpeg_status==MM_stop, FALSE);
  menu_setflags(mpeg_menu,MM_dither,FALSE,                mpeg_status!=MM_stop);
  menu_setflags(mpeg_menu,MM_save,  FALSE,                mpeg_area==0);

  menu_setflags(mpeg_menu_dither,MM_hybrid,   ditherType==HYBRID_DITHER, FALSE);
  menu_setflags(mpeg_menu_dither,MM_hybrid2,  ditherType==HYBRID2_DITHER,FALSE);
  menu_setflags(mpeg_menu_dither,MM_fs4,      ditherType==FS4_DITHER,    FALSE);
  menu_setflags(mpeg_menu_dither,MM_fs2,      ditherType==FS2_DITHER,    FALSE);
  menu_setflags(mpeg_menu_dither,MM_fs2fast,  ditherType==FS2FAST_DITHER,FALSE);
  menu_setflags(mpeg_menu_dither,MM_2x2,      ditherType==Twox2_DITHER,  FALSE);
  menu_setflags(mpeg_menu_dither,MM_gray,     ditherType==GRAY_DITHER,   FALSE);
  menu_setflags(mpeg_menu_dither,MM_ordered,  ditherType==ORDERED_DITHER,FALSE);
  menu_setflags(mpeg_menu_dither,MM_mono,     ditherType==MONO_DITHER,   FALSE);
  menu_setflags(mpeg_menu_dither,MM_threshold,ditherType==MONO_THRESHOLD,FALSE);
  return mpeg_menu;
}

extern int mpeg_next(void);

/*--- Event handler for window. ---*/
static void event_handler(wimp_eventstr *e, void *handle)
{
  handle = handle; /* We don't need handle: this stops compiler warning */

  /* Deal with event */
  switch (e->e)
  {
    case wimp_ENULL:
      if(!mpeg_next()) {
        mpeg_status = MM_stop;
        event_setmask(event_getmask()|wimp_EMNULL);
      }
      break;

    case wimp_EREDRAW:
      redraw_window(e->data.o.w);
      break;

    case wimp_EOPEN:
      open_window(&e->data.o);
      break;

    case wimp_ECLOSE:  /* Pass on close request */
      wimpt_noerr(wimp_close_wind(e->data.o.w));
      if(mpeg_status == MM_run)
        mpeg_status = MM_pause;
      event_setmask(event_getmask()|wimp_EMNULL);
      mpeg_open = FALSE;
      break;

    case wimp_ESEND:
    case wimp_ESENDWANTACK:     /* 
                                 * this code checks for mode/palette
                                 * broadcasts
                                 */
      switch(e->data.msg.hdr.action) {
        case wimp_PALETTECHANGE:
          wimpt_complain(colourtran_select_table(mpeg_mode, 0, -1,
                        (wimp_paletteword *)-1, mpeg_trans));
          break;

        case wimp_MMODECHANGE:
          wimpt_complain(colourtran_select_table(mpeg_mode, 0, -1,
                        (wimp_paletteword *)-1, mpeg_trans));
          
          break;
#if 0
        case wimp_MDATASAVE:   /* import data */
          drawex_load_ram(d);
          break;

#endif
        case wimp_MDATALOAD:   /* insert data */
        case wimp_MDATAOPEN:
          if(mpeg_load_file()) {
            if(!mpeg_open)
              iconclick((wimp_i)0);
            mpeg_status = MM_run;
            event_setmask(event_getmask() & ~wimp_EMNULL);
            start_mpeg();
          }
          break;
      }
      break;
  }
}

/****************************** INITIALISATION ******************************/

/*--- Initialise the program, returning TRUE if it was all OK. ---*/
static BOOL initialise(void)
{
  /* RISC_OSlib initialisation */
  wimpt_init("MPEG Player");       /* Main Wimp initialisation */
  res_init("MPEGPlay");            /* Resources */
  resspr_init();                   /* Application sprites */
  template_init();                 /* Templates */
  dbox_init();                     /* Dialogue boxes */

  /* Create the main window, and declare its event handler */
  if (!create_window("mpeg", &mpeg_handle))
    return FALSE; /* Window creation failed */
  win_register_event_handler(mpeg_handle, event_handler, 0);
  /* --- make the window we just created get delivered null events --- */
  /* --- and also unknown events (ie. msgs for palette/mode change --- */
  win_claim_idle_events(mpeg_handle);
  win_claim_unknown_events(mpeg_handle);

  /* Create the menu tree */
  mpeg_menu = menu_new("MPEGPlay", "!Run,Pause,Stop,Dither,>Zoom,>Save");
  mpeg_menu_dither = menu_new("Dither","ordered,fs4,fs2,fs2fast,hybrid,hybrid2,2x2,gray,mono,threshold");
  if(mpeg_menu == NULL || mpeg_menu_dither == NULL)
     return FALSE;
  menu_submenu(mpeg_menu,MM_dither,mpeg_menu_dither);
#if0
  if (!event_attachmenu(mpeg_handle, mpeg_menu, mpeg_menuproc, 0))
    return FALSE; /* Unable to attach menu */
#endif
  if(!event_attachmenumaker(mpeg_handle,mpeg_menu_maker,mpeg_menuproc,(void*)0))
    return FALSE;

  /* Create the menu tree */
  if (icon_menu = menu_new("MPEGPlayer", ">Info,Quit"), icon_menu == NULL)
    return FALSE; /* Menu create failed */

  /* Set up the icon on the icon bar, and declare its event handlers */
  baricon("!mpegplay", (int)resspr_area(), iconclick);
  if (!event_attachmenu(win_ICONBAR, icon_menu, icon_menuproc, 0))
    return FALSE; /* Unable to attach menu */

  /* All went ok */
  return TRUE;
}

/******************************* MAIN PROGRAM ********************************/

/*--- Main entry point. ---*/
int main(int argc,char **argv)
{
  extern FILE *input;
  if (initialise()) {
    extern init_mpeg(int,char **);

    /* The main event loop */
    if (setjmp(ErrorEnd) != 0) {
      werr(TRUE,"Internal error in mpegplay");
    }
    if (setjmp(OkEnd) != 0) {
      werr(FALSE,"restart?");
    }
    if(!init_mpeg(argc,argv)) {
       werr(TRUE,"Error in init_mpeg");
    }
    if (setjmp(env) != 0) {
      mpeg_status = MM_stop;
    }
    if(input) {
      iconclick((wimp_i)0);
      mpeg_status = MM_run;
      event_setmask(event_getmask() & ~wimp_EMNULL);
      start_mpeg();
    }
    while (TRUE)
      event_process();
  }

  return 0;
}

