/* Jpeg2Spr
   Convert JPEGs to sprites using SpriteExtend 0.99's JPEG SWIs
*/

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

#include "oslib/hourglass.h"
#include "oslib/jpeg.h"
#include "oslib/os.h"
#include "oslib/osbyte.h"
/*#include "oslib/osfile.h"*/
#include "oslib/osspriteop.h"
#include "oslib/wimp.h"

os_dynamic_area_no dynamic_area = NULL;
osspriteop_area *sprite_area;

osbool show_hourglass = TRUE;
osbool showing_hourglass = FALSE;
osbool use_changefsi = FALSE;
/*osbool check_file = TRUE;*/

osbool vdu_redirected = FALSE;
int context1, context2, context3;

bits quality = jpeg_SCALE_DITHERED | jpeg_SCALE_ERROR_DIFFUSED;
const char * infile = "";
const char * outfile = "";
const char * cfsi_mode = "";

#define MODE ((os_mode) -1)

#define SYNTAX \
"Syntax: Jpeg2Spr <JPEG file in> <Sprite file out> {Mode} [-{d0|d1|d2}{h}{c}]"
#define JPEG2SPR "Jpeg2Spr"
#define SPRITENAME "Jpeg2Spr"
#define DYNAMIC_AREA_NAME "Jpeg2Spr output"

void reset_context(void)
{
  if (vdu_redirected)
  {
    xosspriteop_switch_output_to_sprite(osspriteop_USER_AREA,
    	(const osspriteop_area *) context1,
    	(osspriteop_id) context2,
    	(osspriteop_save_area *) context3,
    	NULL, NULL, NULL, NULL);
    vdu_redirected = FALSE;
  }
}

void tidy_up(void)
{
  reset_context();
  if (dynamic_area)
    xosdynamicarea_delete(dynamic_area);
  if (showing_hourglass)
    xhourglass_off();
}

void report_error(const os_error *err)
{
  reset_context();
  xwimp_report_error(err, wimp_ERROR_BOX_CANCEL_ICON, JPEG2SPR, NULL);
  exit(0);
}

static const os_error *last_error;
#define E(err) if ((last_error = (err)) != NULL) report_error(last_error)

void generate_error(int code, const char *string)
{
  os_error err;
  err.errnum = code;
  strncpy(err.errmess, string, 252);
  report_error(&err);
}

void scan_args(int argc, char *argv[])
{
  int arg;
  osbool gotin = FALSE;
  osbool gotout = FALSE;
  osbool gotmode = FALSE;
  for (arg = 1; arg < argc; arg++)
  {
    if (argv[arg][0] == '-')
    {
      int ch;
      int len = strlen(argv[arg]);
      if (len < 2)
        generate_error(0, SYNTAX);
      for (ch = 1; ch < len; ch++)
      {
        switch (argv[arg][ch])
        {
          case 'h' : case 'H' :
            show_hourglass = FALSE;
            break;
          case 'd' : case 'D' :
            switch (argv[arg][++ch])
            {
              case '0' :
                quality = 0;
                break;
              case '1' :
                quality = jpeg_SCALE_DITHERED;
                break;
              case '2' :
                quality = jpeg_SCALE_DITHERED | jpeg_SCALE_ERROR_DIFFUSED;
                break;
              default :
                generate_error(0, SYNTAX);
                break;
            }
            break;
          case 'c' : case 'C' :
            use_changefsi = TRUE;
            break;
          /*case 'f' : case 'F' :
            check_file = FALSE;
            break;*/
          default :
            generate_error(0, SYNTAX);
            break;
        }
      }
    }
    else
    {
      if (gotin)
      {
        if (gotout)
        {
          if (gotmode)
            generate_error(0, SYNTAX);
          else
          {
            cfsi_mode = argv[arg];
            gotmode = TRUE;
          }
        }
        else
        {
          outfile = argv[arg];
          gotout = TRUE;
        }
      }
      else
      {
        infile = argv[arg];
        gotin = TRUE;
      }
    }
  }
  if (!gotin || !gotout)
    generate_error(0, SYNTAX);
}

void changefsi(void)
{
  wimp_t task;
  int freepool;
  char command[256];
  /* Set ourselves up as a WimpTask so we can call Wimp_StartTask */
  E(xwimp_initialise(200, JPEG2SPR, NULL, NULL, &task));
  /* Claim all free memory for ChangeFSI */
  E(xwimp_slot_size(-1, -1, NULL, NULL, &freepool));
  E(xwimp_slot_size(-1, freepool, NULL, NULL, NULL));
  /* Make ChangeFSI call */
  sprintf(command, "<ChangeFSI$Dir>.ChangeFSI %s %s %s -nomode",
  	infile, outfile, cfsi_mode);
  E(xwimp_start_task(command, NULL));
  /* Shut down our dummy task */
  E(xwimp_close_down(task));
  exit(0);
}

void get_jpeg_info(const char *jpeg_file, os_mode sprite_mode,
	int *spritex, int *spritey,
	os_factors *scale_factors)
{
  int jpeg_width, jpeg_height;
  int jpeg_xdpi, jpeg_ydpi;
  int xeig, yeig;
  os_error *fir;
  /* Initially determine scale factors from JPEG
     Ignore 'real' size, only use relative size of x and y */
  fir = xjpegfileinfo_dimensions(infile, NULL,
  	&jpeg_width, &jpeg_height,
  	&jpeg_xdpi, &jpeg_ydpi,
  	NULL);
  if (fir)
  {
    if (use_changefsi)
      changefsi();
    else
      report_error(fir);
  }
  scale_factors->xmul = scale_factors->ymul = 1;
  if (jpeg_xdpi == jpeg_ydpi)
  {
    scale_factors->xdiv = scale_factors->ydiv = 1;
  }
  else if (jpeg_xdpi > jpeg_ydpi)
  {
    scale_factors->xdiv = jpeg_xdpi / jpeg_ydpi;
    scale_factors->ydiv = 1;
  }
  else
  {
    scale_factors->ydiv = jpeg_ydpi / jpeg_xdpi;
    scale_factors->xdiv = 1;
  }
  /* Now modify this to suit current mode */
  E(xos_read_mode_variable(sprite_mode, os_MODEVAR_XEIG_FACTOR, &xeig, NULL));
  E(xos_read_mode_variable(sprite_mode, os_MODEVAR_YEIG_FACTOR, &yeig, NULL));
  if (xeig > 1)
    scale_factors->xdiv <<= (xeig-1);
  else if (!xeig)
    scale_factors->xmul <<= 1;
  if (yeig > 1)
    scale_factors->ydiv <<= (yeig-1);
  else if (!yeig)
    scale_factors->ymul <<= 1;
  /* Now we can calculate sprite size */
  *spritex = jpeg_width * scale_factors->xmul / scale_factors->xdiv;
  *spritey = jpeg_height * scale_factors->ymul / scale_factors->ydiv;
}

/* Create a sprite area in a dynamic area, containing sprite with given
   characteristics */
void create_sprite(const char *name, os_mode sprite_mode,
	int width, int height)
{
  int size;
  int log2bpp;

  E(xos_read_mode_variable(sprite_mode, os_MODEVAR_LOG2_BPP, &log2bpp, NULL));
  size = ((((width << log2bpp) + 7) / 8 + 3) & ~3) * height +
  	sizeof(osspriteop_header) + sizeof(osspriteop_area);
  /* ie Word-aligned(width * bpp/8 [bytes per pixel]) * height + headers */

  E(xosdynamicarea_create(osdynamicarea_ALLOCATE_AREA, size,
  	osdynamicarea_ALLOCATE_BASE,
  	1<<7, /* Not draggable by user */ -1, /* No size limit */
  	NULL, NULL,
  	DYNAMIC_AREA_NAME, &dynamic_area, (byte **) &sprite_area, NULL));

  sprite_area->size = size;
  sprite_area->first = 16;
  E(xosspriteop_clear_sprites(osspriteop_USER_AREA, sprite_area));

  if (sprite_mode == (os_mode) -1)
    sprite_mode = (os_mode) osbyte2(osbyte_SCREEN_CHAR, 0, 0);
  E(xosspriteop_create_sprite(osspriteop_NAME,
  	sprite_area, SPRITENAME,
  	FALSE,
  	width, height, sprite_mode));
}

int main(int argc, char *argv[])
{
  int sprite_width, sprite_height;
  os_factors scale_factors;

  atexit(tidy_up);

  scan_args(argc, argv);

  if (show_hourglass)
  {
    xhourglass_on();
    showing_hourglass = TRUE;
  }
/*
  if (check_file)
  {
    fileswitch_object_type otype;
    xosfile_read(infile, &otype, NULL, NULL, NULL, NULL);
    if (otype != osfile_IS_FILE)
      exit(0);
  }
*/
  get_jpeg_info(infile, MODE, &sprite_width, &sprite_height, &scale_factors);
/*
printf("Sprite size %d x %d, scale factors (%d/%d), (%d/%d)\n",
sprite_width, sprite_height,
scale_factors.xmul, scale_factors.xdiv,
scale_factors.ymul, scale_factors.ydiv);
*/
  create_sprite(SPRITENAME, MODE, sprite_width, sprite_height);

  E(xosspriteop_switch_output_to_sprite(osspriteop_USER_AREA, sprite_area,
  	(osspriteop_id) SPRITENAME, NULL,
  	NULL, &context1, &context2, &context3));
  vdu_redirected = TRUE;

  E(xjpeg_plot_file_scaled(infile, 0, 0, &scale_factors, quality));

  reset_context();

  E(xosspriteop_save_sprite_file(osspriteop_USER_AREA, sprite_area, outfile));

  if (show_hourglass)
  {
    xhourglass_off();
    showing_hourglass = FALSE;
  }
}
