/*
        Frank Lyonnet 1993
        Module MySprite
*/

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

#include "RISC_OSLib:wimp.h"
#include "RISC_OSLib:wimpt.h"
#include "RISC_OSLib:sprite.h"
#include "RISC_OSLib:flex.h"
#include "RISC_OSLib:visdelay.h"
#include "RISC_OSLib:msgs.h"
#include "RISC_OSLib:colourtran.h"

#include "Bool.h"
#include "Erreur.h"
#include "Filetypes.h"
#include "RGB2GCol.h"
#include "YUV2GCol.h"
#include "GRA2GCOL.h"
#include "RGB2RGB.h"
#include "YUV2RGB.h"
#include "GRA2RGB.h"
#include "Modes.h"
#include "Range.h"
#include "FYEOPaths.h"

/* We access settings */
#include "Settings.h"
extern FYEO_settings Settings;

/* Some funcs are called in a thread context */
#include "kernel.h"
#include "MyThread.h"
#include "FYEOMem.h"

#include "MySprite.h"

/* Dithering tables stuff */
#define RGB_DITHER_TABLE_LENGTH 32768
#define YUV_DITHER_TABLE_LENGTH 16384

static char RGBDitherTable[RGB_DITHER_TABLE_LENGTH];
static char YUVDitherTable[YUV_DITHER_TABLE_LENGTH];

typedef struct MY_SPRITE {
  /* Sprite stuff */
  void ** AreaAnchor;
  sprite_area *Area;              /* sprite area                  */
  sprite_id Id;                   /* sprite id                    */

  /* Display stuff */
  sprite_pixtrans Trans[256];     /* colour translation table     */
  sprite_factors Factors;

  /* Quick acess to sprite data */
  int Mode;
  int Bpp;
  int Width;
  int Height;

  /* Pic inside the sprite */
  int PixWidth;
  int PixHeight;
  int OffsetToImage;
  int RightOffset;
  int LeftOffset;

  /* Used during an output to sprite */
  int AreaOffset;
  int InColorSpace;

  /* Dithering and color conv stuff */
  void * FloydBuffer;  /* Must be word aligned ? */
  int * RRTable;
  int * BBTable;
  int * RGTable;
  int * BGTable;

  /* Non Handled by system */
  BOOL SystemDisplay;
  int NMode;
  sprite_area * NArea;
  sprite_id NId;
} mysprite;

#define INVALID_SPRITE_MODE 1800

/*
 * To know the exact sprite memory usage
 */

#define LINE_SIZE(X,B) (int)ceil((double)(X) * (double)((B) / 8) / 4) * 4

#define SPRITE_SIZE(X,Y,B) sizeof(sprite_header) + LINE_SIZE((X), (B)) * (Y)

#define AREA_SIZE(X,Y,B) sizeof(sprite_area) + SPRITE_SIZE((X), (Y), (B))

static void load_dither_tables(void) {
 FILE *FTable;

    if ((FTable = fopen(RGB_TABLE_PATH, "r")) == NULL)
        erreur_fatal(msgs_lookup("CFindDTE"));
    fread(RGBDitherTable, RGB_DITHER_TABLE_LENGTH, sizeof(char), FTable);
    fclose(FTable);

    if ((FTable = fopen(YUV_TABLE_PATH, "r")) == NULL)
        erreur_fatal(msgs_lookup("CFindDTE"));
    fread(YUVDitherTable, YUV_DITHER_TABLE_LENGTH, sizeof(char), FTable);
    fclose(FTable);
}

static void nsys_sprite_create(sprite_area * Area, char * Name, sprite_palflag PalFlag, int Width,
  int Height, int Mode, int Bpp) {
 sprite_header * Header;

  /* One sprite in the area */
  Area -> number = 1;
  Area -> freeoff = AREA_SIZE(Width,Height,Bpp);

  Header = (sprite_header *)((int)Area + Area -> sproff);

  /* Fill in the header */
  Header -> next = SPRITE_SIZE(Width,Height,Bpp);  /*  Offset to next sprite                */
  strcpy(Header -> name,Name);                     /*  Sprite name                          */
  Header -> width = LINE_SIZE(Width,Bpp)/4 - 1;    /*  Width in words-1      (0..639)       */
  Header -> height = Height - 1;                   /*  Height in scanlines-1 (0..255/511)   */
  Header -> lbit = 0;                              /*  First bit used (left end of row)     */
  Header -> rbit = 31 - (LINE_SIZE(Width,Bpp) * 8 - Width * Bpp);
                                                   /*  Last bit used (right end of row)     */
  Header -> image = sizeof(sprite_header);         /*  Offset to sprite image               */
  Header -> mask = sizeof(sprite_header);          /*  Offset to transparency mask          */
  Header -> mode = Mode;                           /*  Mode sprite was defined in           */

  /* Noircissement, SPRITE_SIZE - sizeof(header) */
  memset((void *)((int)Header + Header -> image),0,LINE_SIZE(Width,Bpp)*Height);
}

/**********************************************************************
 Funcs called in a standard context
 **********************************************************************/

BOOL mysprite_init(void) {
  load_dither_tables();
  return(TRUE);
}

void * mysprite_create(int Width, int Height, int Bpp, int Mode,
  int XPos, int YPos, int PixWidth, int PixHeight) {
 sprite_header * Header;
 os_error * SpriteError;
 mysprite * Sprite;

  /** Alloc the mysprite struct */
  if((Sprite = (mysprite *)malloc(sizeof(mysprite)))==NULL) {
    erreur(msgs_lookup("NEMemSE"));
    return(NULL);
  }

  /** Get some parms */
  Sprite -> Mode = Mode;
  Sprite -> Bpp = Bpp;
  Sprite -> Width = Width;
  Sprite -> Height = Height;
  Sprite -> PixWidth = PixWidth;
  Sprite -> PixHeight = PixHeight;

  /** Try to flex alloc the sprite , error and return FALSE if unable */
  if (flex_alloc((flex_ptr) & Sprite->Area, AREA_SIZE(Width, Height, Bpp)) == 0) {
    erreur(msgs_lookup("NEMemSE"));
    free(Sprite);  /* Free struct */
    return (NULL);
  }

  /** Get the anchor */
  Sprite -> AreaAnchor = (void **) & Sprite -> Area;

  /** Initialize the area */
  sprite_area_initialise(Sprite -> Area, AREA_SIZE(Width, Height, Bpp));

  /** Create a sprite within the area */
  SpriteError = sprite_create(Sprite -> Area, "picture", sprite_nopalette,
    Width, Height, Mode);

  if((SpriteError->errnum == INVALID_SPRITE_MODE)||(!atoi(getenv("FYEO$ColourCard"))&&atoi(getenv("FYEO$ColourCardForce"))&&(Bpp==16))) {
    int XDpi;
    int YDpi;

    /* If on display warning */
    if(atoi(getenv("FYEO$UndispWarn")))
      erreur(msgs_lookup("NEModeUH"));

    /* Hand made sprite */
    nsys_sprite_create(Sprite -> Area, "picture", sprite_nopalette, Width, Height, Mode, Bpp);

    /* Non SystemDisplay */
    Sprite -> SystemDisplay = FALSE;

    /* Create a 'black' sprite 1 * 1 * 8 bpp, with same dpi */
    if((Sprite -> NArea = (sprite_area *)malloc(AREA_SIZE(1,1,8))) == NULL) {
      erreur(msgs_lookup("NEMemSE"));
      flex_free((flex_ptr) & Sprite -> Area); /* Free flex */
      free(Sprite);  /* Free struct */
      return (NULL);
    }

    sprite_area_initialise(Sprite -> NArea, AREA_SIZE(1,1,8));
    XDpi = get_mode_xdpi(Mode);
    YDpi = get_mode_ydpi(Mode);
    Sprite -> NMode = get_mode_from_specs(8,XDpi,YDpi);
    sprite_create(Sprite -> NArea, "black", sprite_nopalette,1,1,Sprite -> NMode);
    /* Set up an Id */
    Sprite -> NId.tag = sprite_id_name;
    Sprite -> NId.s.name = "black";
  }
  else
    Sprite -> SystemDisplay = TRUE;

  /** Set Up Id, by name if memory moving (this case) */
  Sprite -> Id.tag = sprite_id_name;
  Sprite -> Id.s.name = "picture";

  /** Get a pointer to the sprite */
  Header = (sprite_header *)((int)Sprite -> Area + Sprite -> Area -> sproff);

  /** Where exactly begin the sprite data ? */
  Sprite -> OffsetToImage = Sprite -> Area -> sproff  + Header -> image;
  Sprite -> RightOffset = 4 - (Header -> rbit + 1) / 8;
  Sprite -> LeftOffset = Header -> lbit / 8;

  /** Position image in the sprite */
  Sprite -> OffsetToImage += YPos *
    (Sprite -> RightOffset + Sprite -> LeftOffset + Width * Bpp / 8);
  Sprite -> LeftOffset += XPos * Bpp / 8;
  Sprite -> RightOffset += (Width - PixWidth - XPos) * Bpp / 8;

  /** The sprite has been succesfully created */
  return (Sprite);
}

void mysprite_destroy(void * SpriteHandle) {
mysprite * Sprite = (mysprite *)SpriteHandle;

  flex_free((flex_ptr) & Sprite -> Area); /* Free flex */
  if(!Sprite -> SystemDisplay)
    free(Sprite -> NArea); /* Free NArea if non-sys display */
  free(Sprite); /* Free struct */
}

void mysprite_notify_display_change(void * SpriteHandle) {
 mysprite * Sprite = (mysprite *)SpriteHandle;

  if(Sprite -> SystemDisplay) {
    wimpt_noerr(colourtran_select_table(Sprite -> Mode, (wimp_paletteword *) 0,
      -1, (wimp_paletteword *) - 1, Sprite -> Trans));
  } else {
    wimpt_noerr(colourtran_select_table(Sprite -> NMode, (wimp_paletteword *) 0,
      -1, (wimp_paletteword *) - 1, Sprite -> Trans));
  }
}

void mysprite_resize_sprite(void * SpriteHandle, int MagWidth, int MagHeight,
  int DivWidth, int DivHeight) {
 mysprite * Sprite = (mysprite *)SpriteHandle;
 sprite_pixtrans PixTrans[256];

  if(Sprite -> SystemDisplay) {
    /* Ask how the WIMP is going to scale our sprite */
    wimp_readpixtrans(Sprite -> Area, &Sprite -> Id, &Sprite -> Factors , PixTrans);

    /* Add zoom scaling factors */
    Sprite -> Factors.xmag *= MagWidth;
    Sprite -> Factors.ymag *= MagHeight;
    Sprite -> Factors.xdiv *= DivWidth;
    Sprite -> Factors.ydiv *= DivHeight;
  } else {
    /* Ask how the WIMP is going to scale our sprite */
    wimp_readpixtrans(Sprite -> NArea, &Sprite -> NId, &Sprite -> Factors , PixTrans);

    /* Add zoom scaling factors */
    Sprite -> Factors.xmag *= MagWidth;
    Sprite -> Factors.ymag *= MagHeight;
    Sprite -> Factors.xdiv *= DivWidth;
    Sprite -> Factors.ydiv *= DivHeight;

    /* Expand to image size */
    Sprite -> Factors.xmag *= Sprite -> Width;
    Sprite -> Factors.ymag *= Sprite -> Height;
  }
}

void mysprite_put_scaled(void * SpriteHandle, wimp_redrawstr R, int ScaledOSHeight) {
 mysprite * Sprite = (mysprite *)SpriteHandle;

  if(Sprite -> SystemDisplay) {
    wimpt_noerr(sprite_put_scaled(Sprite -> Area, &Sprite -> Id, 0,
      R.box.x0 - R.scx, R.box.y1 - R.scy - ScaledOSHeight, & Sprite -> Factors,
      Sprite -> Trans));
  } else {
    wimpt_noerr(sprite_put_scaled(Sprite -> NArea, &Sprite -> NId, 0,
      R.box.x0 - R.scx, R.box.y1 - R.scy - ScaledOSHeight, & Sprite -> Factors,
      Sprite -> Trans));
  }
}

BOOL mysprite_save(void * SpriteHandle, char * Filename) {
 mysprite * Sprite = (mysprite *)SpriteHandle;
 BOOL Ok = FALSE;

  if (strlen(Filename) != 0) {
    visdelay_begin();
    wimpt_noerr(sprite_area_save(Sprite -> Area, Filename));
    set_filetype(Filename, FILE_SPRITE);
    visdelay_end();
    Ok = TRUE;
  }
  return (Ok);
}

int mysprite_get_size(void * SpriteHandle) {
 mysprite * Sprite = (mysprite *)SpriteHandle;

  return(AREA_SIZE(Sprite -> Width, Sprite -> Height, Sprite -> Bpp));
}

/**********************************************************************
 Funcs called in a thread context
 **********************************************************************/

BOOL mysprite_color_output_init(void * SpriteHandle , int InColorSpace) {
 mysprite * Sprite = (mysprite *)SpriteHandle;

   /* Get parms */
   Sprite -> InColorSpace = InColorSpace;

   /* Init specific datas */
   Sprite -> AreaOffset = Sprite -> OffsetToImage;
   Sprite -> FloydBuffer = NULL;

   switch(Sprite -> Bpp) {
   case 8 :
      switch(InColorSpace) {
      case FYEO_RGB :
         if(Settings.Output.Dither == DITHER_FLOYD) {
            if((Sprite -> FloydBuffer = (char *)FYEO_alloc(Sprite -> PixWidth * 3 + 6))==NULL) {
               erreur(msgs_lookup("MemProcH"));
               my_thread_leave(ERREUR);
            }
            init_rgb_to_gcol_convert(Sprite -> FloydBuffer,Sprite -> PixWidth);
         }
         break;
      case FYEO_YUV :
         if(Settings.Output.Dither == DITHER_FLOYD) {
            if((Sprite -> FloydBuffer = (char *)FYEO_alloc(Sprite -> PixWidth * 3 + 6))==NULL) {
               erreur(msgs_lookup("MemProcH"));
               my_thread_leave(ERREUR);
            }
            init_yuv_to_gcol_convert(Sprite -> FloydBuffer,Sprite -> PixWidth);
         }
         break;
      };
      break;
   case 16 :
   case 32 :
      switch(InColorSpace) {
      case FYEO_RGB :
         break;
      case FYEO_YUV :
         if((Sprite -> RRTable = (int *)FYEO_alloc(256*sizeof(int)))==NULL) {
            erreur(msgs_lookup("MemProcH"));
            my_thread_leave(ERREUR);
         }

         if((Sprite -> BBTable = (int *)FYEO_alloc(256*sizeof(int)))==NULL) {
            erreur(msgs_lookup("MemProcH"));
            my_thread_leave(ERREUR);
         }

         if((Sprite -> RGTable = (int *)FYEO_alloc(256*sizeof(int)))==NULL) {
            erreur(msgs_lookup("MemProcH"));
            my_thread_leave(ERREUR);
         }

         if((Sprite -> BGTable = (int *)FYEO_alloc(256*sizeof(int)))==NULL) {
            erreur(msgs_lookup("MemProcH"));
            my_thread_leave(ERREUR);
         }

         init_yuv_to_rgb_convert(Sprite -> RRTable,Sprite -> BBTable,Sprite -> RGTable,Sprite -> BGTable);
         break;
      }
      break;
   default:
      erreur_interne(msgs_lookup("InvBE"));
      break;
   };

  return(TRUE);
}

BOOL mysprite_gray_output_init(void * SpriteHandle) {
 mysprite * Sprite = (mysprite *)SpriteHandle;

   /* Init specific datas */
   Sprite -> InColorSpace = FYEO_GRAY;
   Sprite -> AreaOffset = Sprite -> OffsetToImage ;
   Sprite -> FloydBuffer = NULL;

   switch(Sprite -> Bpp) {
   case 8 :
      if(Settings.Output.Dither == DITHER_FLOYD) {
         if((Sprite -> FloydBuffer = (char *)FYEO_alloc(Sprite -> PixWidth * 3 + 6))==NULL) {
            erreur(msgs_lookup("MemProcH"));
            my_thread_leave(ERREUR);
         }

         init_gray_to_gcol_convert(Sprite -> FloydBuffer,Sprite -> PixWidth);
      }
      break;
   case 16 :
   case 32 :
      /* Nothing to do */
      break;
   default:
      erreur_interne(msgs_lookup("InvBE"));
      break;
   };

  return(TRUE);
}

void mysprite_gray_output_one_line(void * SpriteHandle, char *Gray) {
 mysprite * Sprite = (mysprite *)SpriteHandle;

   Sprite -> AreaOffset += Sprite -> LeftOffset;

   switch(Sprite -> Bpp) {
   case 8 :
      switch(Settings.Output.Dither) {
      case DITHER_NONE:
         gray_to_gcol_convert_none(Gray,(char *)((int)(*(Sprite -> AreaAnchor)) + Sprite -> AreaOffset),
            Sprite -> PixWidth , RGBDitherTable);
      break;
      case DITHER_SIMPLE:
         gray_to_gcol_convert_simple(Gray,(char *)((int)(*(Sprite -> AreaAnchor)) + Sprite -> AreaOffset),
            Sprite -> PixWidth , RGBDitherTable);
      break;
      case DITHER_FLOYD:
         gray_to_gcol_convert_floyd(Gray,(char *)((int)(*(Sprite -> AreaAnchor)) + Sprite -> AreaOffset),
            Sprite -> PixWidth, RGBDitherTable, Sprite -> FloydBuffer);
      break;
      default:
      break;
      }
      break;
   case 16:
      gray_to_16bpp_convert(Gray,(char *)((int)(*(Sprite -> AreaAnchor)) + Sprite -> AreaOffset),
         Sprite -> PixWidth );
      break;
   case 32:
      gray_to_32bpp_convert(Gray,(char *)((int)(*(Sprite -> AreaAnchor)) + Sprite -> AreaOffset),
         Sprite -> PixWidth );
      break;
   default:
      erreur_interne(msgs_lookup("InvBE"));
      break;
   }

   Sprite -> AreaOffset += Sprite -> PixWidth * ( Sprite -> Bpp / 8 ) + Sprite -> RightOffset;
}

void mysprite_color_output_one_line(void * SpriteHandle,char *Red,char *Green,char *Blue) {
 mysprite * Sprite = (mysprite *)SpriteHandle;

   Sprite -> AreaOffset += Sprite -> LeftOffset;

   switch(Sprite -> Bpp) {
   case 8 :
      switch(Sprite -> InColorSpace) {
      case FYEO_RGB :
         switch(Settings.Output.Dither) {
         case DITHER_NONE:
            rgb_to_gcol_convert_none(Red,Green,Blue,(char *)((int)(*(Sprite -> AreaAnchor)) + Sprite -> AreaOffset) ,
               Sprite -> PixWidth , RGBDitherTable);
         break;
         case DITHER_SIMPLE:
            rgb_to_gcol_convert_simple(Red,Green,Blue,(char *)((int)(*(Sprite -> AreaAnchor)) + Sprite -> AreaOffset) ,
               Sprite -> PixWidth , RGBDitherTable);
         break;
         case DITHER_FLOYD:
            rgb_to_gcol_convert_floyd(Red,Green,Blue,(char *)((int)(*(Sprite -> AreaAnchor)) + Sprite -> AreaOffset) ,
               Sprite -> PixWidth , RGBDitherTable, Sprite -> FloydBuffer);
         break;
         default:
         break;
         }
         break;

      case FYEO_YUV :
         switch(Settings.Output.Dither) {
         case DITHER_NONE:
            yuv_to_gcol_convert_none(Red,Green,Blue,(char *)((int)(*(Sprite -> AreaAnchor)) + Sprite -> AreaOffset) ,
               Sprite -> PixWidth , YUVDitherTable);
         break;
         case DITHER_SIMPLE:
            yuv_to_gcol_convert_simple(Red,Green,Blue,(char *)((int)(*(Sprite -> AreaAnchor)) + Sprite -> AreaOffset) ,
               Sprite -> PixWidth , YUVDitherTable);
         break;
         case DITHER_FLOYD:
            yuv_to_gcol_convert_floyd(Red,Green,Blue,(char *)((int)(*(Sprite -> AreaAnchor)) + Sprite -> AreaOffset) ,
               Sprite -> PixWidth , YUVDitherTable, Sprite -> FloydBuffer);
         break;
         default:
         break;
         };
         break;
      };
      break;
   case 16:
      switch(Sprite -> InColorSpace) {
      case FYEO_RGB :
         rgb_to_16bpp_convert(Red,Green,Blue,(char *)((int)(*(Sprite -> AreaAnchor)) + Sprite -> AreaOffset),
            Sprite -> PixWidth );
         break;
      case FYEO_YUV :
         yuv_to_16bpp_convert(Red,Green,Blue,(char *)((int)(*(Sprite -> AreaAnchor)) + Sprite -> AreaOffset),
            Sprite -> PixWidth , Sprite -> RRTable , Sprite -> BBTable , Sprite -> RGTable , Sprite -> BGTable);
         break;
      }
      break;
   case 32:
      switch(Sprite -> InColorSpace) {
      case FYEO_RGB :
         rgb_to_32bpp_convert(Red,Green,Blue,(char *)((int)(*(Sprite -> AreaAnchor)) + Sprite -> AreaOffset),
            Sprite -> PixWidth );
         break;
      case FYEO_YUV :
         yuv_to_32bpp_convert(Red,Green,Blue,(char *)((int)(*(Sprite -> AreaAnchor)) + Sprite -> AreaOffset),
            Sprite -> PixWidth , Sprite -> RRTable , Sprite -> BBTable , Sprite -> RGTable , Sprite -> BGTable);
         break;
      }
      break;
   default:
      erreur_interne(msgs_lookup("InvBE"));
      break;
   }

   Sprite -> AreaOffset += Sprite -> PixWidth * ( Sprite -> Bpp / 8) + Sprite -> RightOffset;
}

void mysprite_color_output_term(void * SpriteHandle) {
  /* Done by FYEO_mem_term */
}

void mysprite_gray_output_term(void * SpriteHandle) {
  /* Done by FYEO_mem_term */
}

