/*
** CEL Loader
** Written by Michal Durys - misha@femina.com.pl
**
** PUBLIC DOMAIN
*/

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

#include <proto/dos.h>
#include <proto/exec.h>
#include <dos/dos.h>
#include <exec/memory.h>

#include <scan/modall.h>
#include <scan/loadsave.h>


#include "kiss_files.h"
#include "cel_strings.h"

/* Useful macro */
#define pcword(var) (((var & 0x00ff)<<8) | ((var & 0xff00)>>8))

#ifdef _M68060
  #define _CPU "[68060]"
#else
  #ifdef _M68040
    #define _CPU "[68040]"
  #else
    #ifdef _M68030
      #define _CPU "[68030]"
    #else
      #ifdef _M68020
        #define _CPU "[68020]"
      #else
        #ifdef _M68010
          #define _CPU "[68010]"
        #else
          #define _CPU "[680x0]"
        #endif
      #endif
    #endif
  #endif
#endif

// compilation control
#define OPT_ALPHA_BLEND        // blend pixel value with background (fake alpha)
#define OPT_IFX_IO             // use i/o functions provided by IFX (faster?)

#define BLEND_COLOR_R 0
#define BLEND_COLOR_G 255
#define BLEND_COLOR_B 0

#ifdef OPT_IFX_IO
  #define MyOpen(filename, accessmode) (BPTR)BOpen(filename, accessmode, 0)
  #define MyClose(handle) BClose((struct BIO *)handle)
  #define MyRead(handle, buffer, length) BRead((struct BIO *)handle, (UBYTE *)buffer, length)
#else
  #define MyOpen(filename, accessmode) Open(filename, accessmode)
  #define MyClose(handle) Close(handle)
  #define MyRead(handle, buffer, length) Read(handle, buffer, length)
#endif

// prototypes
int load_cel_cherry(BPTR, struct Buffer *);
int load_cel_8bit(BPTR, struct Buffer *);
int load_cel_4bit(BPTR, struct Buffer *);
struct Palette * load_palette(struct Buffer *, char *, struct Palette *);

/************************************************************************
 *
 * LM_Load() - Load an image from the given file
 *
 * Attempt to load the contents of the given file and convert it
 * to 24-bits if necessary.  Should return a valid Buffer (see
 * scan/buf.h) structure filled out properly.
 *
 * Returns a pointer to a Buffer structure if successful or
 * NULL on failure, with an addition error code set by using
 * SetError().
 *
 * Inputs:
 *
 *    fname    - name of file to load
 *    id       - ID code (as provided by LM_Signatures below)
 *    args     - Arexx arguments
 *
 ************************************************************************/

struct Buffer * __saveds __asm LM_Load(register __a0 char *filename, register __d0 int id, register __a1 LONG *args)
{
  BPTR fh;
  struct Kiss_CelFileHeader *celheader=0;
  struct Buffer *imgbuf=0;

  // try to open file
  if (!(fh=MyOpen(filename,MODE_OLDFILE)))
  {
    SetError(ERR_Open);
    goto error;
  }

  // allocate memory for cel header
  if (!(celheader=(struct Kiss_CelFileHeader *)AllocMem(sizeof(struct Kiss_CelFileHeader), MEMF_ANY)))
  {
    SetError(ERR_Memory);
    goto error;
  }

  // check file type
  if (!(MyRead(fh, celheader, sizeof(struct Kiss_CelFileHeader))==sizeof(struct Kiss_CelFileHeader))) goto error;
  if (!((celheader->mark==FILEMARK_CEL) || (celheader->mark==FILEMARK_CHERRYCEL)))
  {
    SetError(ERR_BadFileType);
    goto error;
  }

  // allocate and fill Buffer structure
  if (!(imgbuf=AllocBuffer(NULL, pcword(celheader->width), pcword(celheader->height), 3, 8, NULL))) goto error;
  strcpy(imgbuf->Name, filename);
  strcpy(imgbuf->Type, "CEL");
  imgbuf->Flags=0;

  // display status information
  ShowStatus(imgbuf);
  BeginBar("Load CEL", pcword(celheader->height), TRUE);

  switch(celheader->bitsperpixel)
  {
    case 32:
      if (!(load_cel_cherry(fh, imgbuf)==NULL)) goto error;
      break;
    case 8:
      if (!(load_cel_8bit(fh, imgbuf)==NULL)) goto error;
      break;
    case 4:
      if (!(load_cel_4bit(fh, imgbuf)==NULL)) goto error;
      break;
    default:
      SetError(ERR_BadDepth);
      goto error;
  }

  // cleanup
  if (celheader) FreeMem(celheader, sizeof(struct Kiss_CelFileHeader));
  if (fh) MyClose(fh);
  EndBar(NULL);

  // exit
  return(imgbuf);

  // error handling
error:
  if (imgbuf) KillBuffer(imgbuf);
  if (celheader) FreeMem(celheader,sizeof(struct Kiss_CelFileHeader));
  if (fh) MyClose(fh);
  EndBar(NULL);
  return((struct Buffer *)FALSE);
}

/// "FUNC: load_cel_cherry"
int load_cel_cherry(BPTR fh, struct Buffer *imgbuf)
{
  int i, j;
  UBYTE *red_buf=0, *green_buf=0, *blue_buf=0;
  UBYTE *r,*g,*b, *line_buf, *pix, a;

  // alocate memory for line buffer
  if (!(line_buf=AllocMem(imgbuf->Width*4, MEMF_ANY)))
  {
    SetError(ERR_Memory);
    goto error;
  }

  // allocate memory for destination line buffers
  if (!(red_buf=AllocMem(imgbuf->Width, MEMF_ANY))) goto error;
  if (!(green_buf=AllocMem(imgbuf->Width, MEMF_ANY))) goto error;
  if (!(blue_buf=AllocMem(imgbuf->Width, MEMF_ANY))) goto error;
/* why this doesn't work!?
  red_buf=AllocMem(imgbuf->Width, MEMF_ANY);
  green_buf=AllocMem(imgbuf->Width, MEMF_ANY);
  blue_buf=AllocMem(imgbuf->Width, MEMF_ANY);
  if (red_buf==NULL || green_buf==NULL || blue_buf==NULL);
  {
    printf("r %x, g %x, b %x, all %d\n", red_buf, green_buf, blue_buf, (red_buf==NULL || green_buf==NULL || blue_buf==NULL));
    SetError(ERR_Memory);
    goto error;
  }
*/

  // read image data
  for (i=0; i<imgbuf->Height; i++)
  {

    // check if user wants to cancel operation
    if (Bar(i))
    {
      SetError(ERR_UserCancel);
      break;
    }

/*
The pixels look like this as stored in the file:

BBGGRRAA
BB - Blue byte
GG - Green byte
RR - Red byte  0x00 is OFF, 0xFF is 100% RED.
AA - Transparency (ALPHA).  0x00 is 100% transparent, 0xFF is 100% opaque.

*/

    // read image line from file
    if (!(MyRead(fh, line_buf, imgbuf->Width*4)==imgbuf->Width*4))
    {
      Errorf("File is corrupted!", NULL);
      break;
    }

    // fill line buffer
    for (j=0, pix=line_buf, r=red_buf, g=green_buf, b=blue_buf; j<imgbuf->Width; j++)
    {
#ifdef OPT_ALPHA_BLEND
      a=pix[3];
      *b++=(UBYTE)mixer(*pix++, BLEND_COLOR_B, a);
      *g++=(UBYTE)mixer(*pix++, BLEND_COLOR_G, a);
      *r++=(UBYTE)mixer(*pix++, BLEND_COLOR_R, a);
      *pix++;
#else
      *b++=*pix++;
      *g++=*pix++;
      *r++=*pix++;
      *pix++;
#endif
    }

    // store this line
    PutNewBufLine(imgbuf, red_buf, green_buf, blue_buf, i);
  }

  // clean up
  FreeMem(line_buf, imgbuf->Width*4);
  FreeMem(red_buf, imgbuf->Width);
  FreeMem(green_buf, imgbuf->Width);
  FreeMem(blue_buf, imgbuf->Width);
  return(0);

  // error handling
error:
  if (line_buf) FreeMem(line_buf, imgbuf->Width*4);
  if (red_buf) FreeMem(red_buf, imgbuf->Width);
  if (green_buf) FreeMem(green_buf, imgbuf->Width);
  if (blue_buf) FreeMem(blue_buf, imgbuf->Width);
  return(20);
}
///

/// "FUNC: load_cel_8bit"
int load_cel_8bit(BPTR fh, struct Buffer *imgbuf)
{
  int i, j;
  UBYTE *red_buf=0, *green_buf=0, *blue_buf=0, *r, *g, *b, *line_buf=0, *pix;
  UWORD offset;
  struct Palette *palette;

  palette=load_palette(imgbuf, NULL, NULL);

  // alocate memory for line buffer
  if (!(line_buf=AllocMem(imgbuf->Width, MEMF_ANY)))
  {
    SetError(ERR_Memory);
    goto error;
  }

  // allocate memory for destination line buffers
  if (!(red_buf=AllocMem(imgbuf->Width, MEMF_ANY))) goto error;
  if (!(green_buf=AllocMem(imgbuf->Width, MEMF_ANY))) goto error;
  if (!(blue_buf=AllocMem(imgbuf->Width, MEMF_ANY))) goto error;
/* why this doesn't work!?
  red_buf=AllocMem(imgbuf->Width, MEMF_ANY);
  green_buf=AllocMem(imgbuf->Width, MEMF_ANY);
  blue_buf=AllocMem(imgbuf->Width, MEMF_ANY);
  if (red_buf==NULL || green_buf==NULL || blue_buf==NULL);
  {
    printf("r %x, g %x, b %x, all %d\n", red_buf, green_buf, blue_buf, (red_buf==NULL || green_buf==NULL || blue_buf==NULL));
    SetError(ERR_Memory);
    goto error;
  }
*/

  // read image data
  for (i=0; i<imgbuf->Height; i++)
  {

    // check if user wants to cancel operation
    if (Bar(i))
    {
      SetError(ERR_UserCancel);
      break;
    }

    // read image line from file
    if (!(MyRead(fh, line_buf, imgbuf->Width)==imgbuf->Width))
    {
      Errorf("File is corrupted!", NULL);
      break;
    }

    // fill image buffer
    if (palette)
    {
      for (j=0, pix=line_buf, r=red_buf, g=green_buf, b=blue_buf; j<imgbuf->Width; j++, *pix++)
      {
        offset=*pix * 3;
        *r++=palette->Table[offset];
        *g++=palette->Table[offset+1];
        *b++=palette->Table[offset+2];
      }
    }
    else
    {
      for (j=0, pix=line_buf, r=red_buf, g=green_buf, b=blue_buf; j<imgbuf->Width; j++, *pix++)
      {
        offset=*pix * 3;
        *r++=offset;
        *g++=offset;
        *b++=offset;
      }
    }

    // store this line
    PutNewBufLine(imgbuf, red_buf, green_buf, blue_buf, i);
  }

  // clean up
  FreeMem(line_buf, imgbuf->Width);
  FreeMem(red_buf, imgbuf->Width);
  FreeMem(green_buf, imgbuf->Width);
  FreeMem(blue_buf, imgbuf->Width);
  if (palette)
  {
    if (palette->Table) FreeMem(palette->Table, palette->Count*3);
    FreeMem(palette, sizeof(struct Palette));
  }
  return(0);

  // error handling
error:
  if (line_buf) FreeMem(line_buf, imgbuf->Width);
  if (red_buf) FreeMem(red_buf, imgbuf->Width);
  if (green_buf) FreeMem(green_buf, imgbuf->Width);
  if (blue_buf) FreeMem(blue_buf, imgbuf->Width);
  if (palette)
  {
    if (palette->Table) FreeMem(palette->Table, palette->Count*3);
    FreeMem(palette, sizeof(struct Palette));
  }
  return(20);
}
///

/// "FUNC: load_cel_4bit"
int load_cel_4bit(BPTR fh, struct Buffer *imgbuf)
{
  int i, j, line_buf_size;
  UBYTE *red_buf=0, *green_buf=0, *blue_buf=0, *r, *g, *b, *line_buf=0, *pix;
  UWORD offset;
  struct Palette *palette;

  palette=load_palette(imgbuf, NULL, NULL);
  line_buf_size=(imgbuf->Width+1)/2;

  // alocate memory for source line buffer
  if (!(line_buf=AllocMem(line_buf_size, MEMF_ANY)))
  {
    SetError(ERR_Memory);
    goto error;
  }

  // allocate memory for destination line buffers
  if (!(red_buf=AllocMem(imgbuf->Width, MEMF_ANY))) goto error;
  if (!(green_buf=AllocMem(imgbuf->Width, MEMF_ANY))) goto error;
  if (!(blue_buf=AllocMem(imgbuf->Width, MEMF_ANY))) goto error;
/* why this doesn't work!?
  red_buf=AllocMem(imgbuf->Width, MEMF_ANY);
  green_buf=AllocMem(imgbuf->Width, MEMF_ANY);
  blue_buf=AllocMem(imgbuf->Width, MEMF_ANY);
  if (red_buf==NULL || green_buf==NULL || blue_buf==NULL);
  {
    printf("r %x, g %x, b %x, all %d\n", red_buf, green_buf, blue_buf, (red_buf==NULL || green_buf==NULL || blue_buf==NULL));
    SetError(ERR_Memory);
    goto error;
  }
*/

  // read image data
  for (i=0; i<imgbuf->Height; i++)
  {

    // check if user wants to cancel operation
    if (Bar(i))
    {
      SetError(ERR_UserCancel);
      break;
    }

    // read image line from file
    if (!(MyRead(fh, line_buf, line_buf_size)==line_buf_size))
    {
      Errorf("File is corrupted!", NULL);
      break;
    }

    // fill image buffer
    if (palette)
    {
      for (j=0, pix=line_buf, r=red_buf, g=green_buf, b=blue_buf; j<imgbuf->Width; j+=2)
      {
        offset=((*pix & 0xf0)>>4) * 3;
        *r++=palette->Table[offset];
        *g++=palette->Table[offset+1];
        *b++=palette->Table[offset+2];

        if ((j+1)<imgbuf->Width)   // really needed?
        {
          offset=(*pix++ & 0x0f) * 3;
          *r++=palette->Table[offset];
          *g++=palette->Table[offset+1];
          *b++=palette->Table[offset+2];
        }
      }
    }
    else
    {
      for (j=0, pix=line_buf, r=red_buf, g=green_buf, b=blue_buf; j<imgbuf->Width; j+=2)
      {
        offset=((*pix & 0xf0)>>4) * 3;
        *r++=offset;
        *g++=offset;
        *b++=offset;

        if ((j+1)<imgbuf->Width)   // really needed?
        {
          offset=(*pix++ & 0x0f) * 3;
          *r++=offset;
          *g++=offset;
          *b++=offset;
        }
      }
    }

    // store this line
    PutNewBufLine(imgbuf, red_buf, green_buf, blue_buf, i);
  }

  // clean up
  FreeMem(line_buf, line_buf_size);
  FreeMem(red_buf, imgbuf->Width);
  FreeMem(green_buf, imgbuf->Width);
  FreeMem(blue_buf, imgbuf->Width);
  if (palette)
  {
    if (palette->Table) FreeMem(palette->Table, palette->Count*3);
    FreeMem(palette, sizeof(struct Palette));
  }
  return(0);

  // error handling
error:
  if (line_buf) FreeMem(line_buf, line_buf_size);
  if (red_buf) FreeMem(red_buf, imgbuf->Width);
  if (green_buf) FreeMem(green_buf, imgbuf->Width);
  if (blue_buf) FreeMem(blue_buf, imgbuf->Width);
  if (palette)
  {
    if (palette->Table) FreeMem(palette->Table, palette->Count*3);
    FreeMem(palette, sizeof(struct Palette));
  }
  return(20);
}
///

/// "FUNC: load_palette"
struct Palette * load_palette(struct Buffer *imgbuf, char *filename, struct Palette *palette)
{
  BPTR fh=0;
  struct Kiss_PaletteFileHeader *palheader=0;
  struct FileInfo *fi=0;
  UBYTE *tmp_buffer, *c;
  UWORD offset;
  int i;

  if (filename==NULL)
  {
    // alocate memory for file info structure and fill it
    if (!(fi=AllocMem(sizeof(struct FileInfo), MEMF_ANY | MEMF_CLEAR)))
    {
      SetError(ERR_Memory);
      goto error;
    }
    strncpy(fi->Dir, imgbuf->Name, (PathPart(imgbuf->Name))-imgbuf->Name);
    strcpy(fi->Pattern, "#?.kcf");

    // display requester
    filename=NewGetFile(fi, "Select Palette...", NULL);
  }

  // try to open file
  if (!(fh=MyOpen(filename, MODE_OLDFILE)))
  {
    SetError(ERR_Open);
    goto error;
  }

  // allocate memory for cel header
  if (!(palheader=(struct Kiss_PaletteFileHeader *)AllocMem(sizeof(struct Kiss_PaletteFileHeader), MEMF_ANY)))
  {
    SetError(ERR_Memory);
    goto error;
  }

  // read file header
  if (!(MyRead(fh, palheader, sizeof(struct Kiss_PaletteFileHeader))==sizeof(struct Kiss_PaletteFileHeader))) goto error;

  // check file type
  if (!(palheader->id==KISS_ID)) goto error;
  if (!(palheader->mark==FILEMARK_PALETTE))
  {
    SetError(ERR_BadFileType);
    goto error;
  }

  // allocate memory for Palette structure (if needed)
  if (palette==NULL)
  {
    if (!(palette=AllocMem(sizeof(struct Palette), MEMF_ANY | MEMF_CLEAR)))
    {
      SetError(ERR_Memory);
      goto error;
    }
  }

//  InfoRequest("Bits per color: %d\nColors %d\nGroups: %d", palheader->bitspercolor, pcword(palheader->colors), pcword(palheader->groups));

  // fill Palette structure
  palette->Depth=8;
  palette->Count=pcword(palheader->colors);

  // allocate memory for Palette structure
  if (!(palette->Table=AllocMem(palette->Count*3, MEMF_ANY)))
  {
    SetError(ERR_Memory);
    goto error;
  }

  switch(palheader->bitspercolor)
  {
    // palette format: rr, gg, bb, rr, ...
    case 24:
      // read palette data
      if (!(MyRead(fh, palette->Table, palette->Count*3))==palette->Count*3) goto error;
      break;

    // palette format: rg, 0b, rg, 0b, ...
    case 12:
      // allocate memory for temporary palette buffer
      if (!(tmp_buffer=AllocMem((palette->Count*2), MEMF_ANY)))
      {
        SetError(ERR_Memory);
        goto error;
      }

      // read palette data
      MyRead(fh, tmp_buffer, palette->Count<<1);

      // convert palette
      for (i=0, c=tmp_buffer; i<palette->Count; i++)
      {
        offset=i*3;
        palette->Table[offset]=(*c & 0xf0) | ((*c & 0xf0)>>4);        // red
        palette->Table[offset+2]=((*c<<4) & 0xf0) | (*c++ & 0x0f);  // blue
        palette->Table[offset+1]=((*c<<4) & 0xf0) | (*c++ & 0x0f);  // green
      }

      // clean up
      FreeMem(tmp_buffer, (palette->Count<<1));
      break;

    default:
      InfoRequest("Palette depth not supported!", NULL);
      break;
  }

  // clean up
  MyClose(fh);
  if (fi) FreeMem(fi, sizeof(struct FileInfo));
  return(palette);

  // error handling
error:
  if (palette)
  {
    if (palette->Table) FreeMem(palette->Table, palette->Count*3);
    FreeMem(palette, sizeof(struct Palette));
  }
  if (palheader) FreeMem(palheader, sizeof(struct Kiss_PaletteFileHeader));
  if (fh) MyClose(fh);
  if (fi) FreeMem(fi, sizeof(struct FileInfo));
  return((struct Palette *)NULL);
}
///

/************************************************************************
 *
 * LM_LoadPalette() - Load a palette from file
 *
 * Attempts to extract palette information from the given file.
 * This function should fill in the supplied Palette structure (see
 * scan/loadsave.h) as best it can.  Specifically, you need to fill
 * in the Depth, Count, and Table fields at least.  Others are
 * optional.
 *
 * Return TRUE if successful, FALSE on failure with an additional
 * error code set by using SetError().
 *
 * Inputs:
 *
 *    fname    - name of file to load
 *    pal      - struct Palette to fill in
 *    id       - ID code (as provided by LM_Signatures below)
 *
 ************************************************************************/

BOOL __saveds __asm LM_LoadPalette (register __a0 char *filename, register __a1 struct Palette *palette, register __d0 int id)
{
  if (palette=load_palette(NULL, filename, palette))
  {
    return(TRUE);
  }
  else
  {
    ReturnError(ERR_NoPalette, FALSE);
  }
}


//  Format descriptor table - describes the bytes to look for at the
static struct LoadFormat loadformats[]=
{
   {"KiSS", 4, "CEL", 0},
   {NULL}
};

// Return signature bytes
struct LoadFormat * __saveds LM_Signatures(void)
{
  return(loadformats);
}

// LM_CheckFile() - Custom file identification
BOOL __saveds __asm LM_CheckFile (register __a0 char *fname)
{
   return(FALSE);
}


/**********************************************************************\

                         Standard Module Stuff

\**********************************************************************/

/************************************************************************
 * Function table.  Referenced in "lib.o".
 *
 * The first four entries are the standard library vectors, defined
 * in "lib.o", the remaining entries define functions specific to
 * this module.
 *
 * The table ends with -1.
 *
 */
ULONG FuncTable[] = {
   (ULONG) LibOpen,
   (ULONG) LibClose,
   (ULONG) LibExpunge,
   (ULONG) LibNull,

   (ULONG) LM_Load,
   (ULONG) LM_LoadPalette,
   (ULONG) LM_Signatures,
   (ULONG) LM_CheckFile,

   (ULONG) 0,        // reserved
   (ULONG) 0,        // reserved
   (ULONG) 0,        // reserved

   (ULONG) -1L
};


// ID string for this module.  References in "lib.o".
UBYTE LibraryID[] = "\0$VER: CEL Loader 1.0 " _CPU " " __AMIGADATE__;
// Type of module.  Referenced in "lib.o".
UBYTE LibraryType = NT_LOADER;


// Module initialization code.  Referenced by "lib.o".
/// "FUNC: UserOpen"
LONG __saveds __stdargs UserOpen(struct ModuleBase *modbase)
{
  modbase->Language="Loader_CEL";
  modbase->LangCount=TXT_COUNT;
  modbase->Text=Default_Strings;
  modbase->CmdTable=NULL;
  modbase->Version=107;
  return(TRUE);
}
///


// Module cleanup code.  Referenced by "lib.o".
LONG __saveds __stdargs UserClose(struct ModuleBase *modbase)
{
  return(TRUE);
}

