/************************************************************************/
/*                                                                      */
/* Write image as ilbm                                                  */
/*                                                                      */
/*                                                                      */
/*                                                                      */
/*                                                                      */
/************************************************************************/

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

#include <exec/types.h>
#include <datatypes/pictureclass.h>

#include <scanner.h>
#include <MakeILBM.h>
#include <packer.h>

#define ID_DPI  MAKE_ID('D','P','I',' ')
#define ID_XBMI MAKE_ID('X','B','M','I')
#define ILBM_BUFLEN 1500000

struct ImageBuf
{
  UBYTE* buf;
  ULONG  actualLen;
  FILE*  fh;
};

struct ILBMFile
{
  char* fileName;
  int   formLenOffset;
  int   bodyLenOffset;
  UBYTE flags;
  
  UBYTE depth;
  UWORD width;
  UWORD height;
  UWORD xdpi;
  UWORD ydpi;

  LineFormat format;

  ULONG scanLineWidth;
  ULONG bufLen;
  struct ImageBuf buf[3];
};

#define ILBMFLAGS_COMPRESS  (0x01 << 0)
#define ILBMFLAGS_TEMPFILE  (0x01 << 1)

struct DPIHeader
{
  WORD dpi_x;
  WORD dpi_y;
};

struct xbmi
{
  WORD             xbmi_type;   /* ILBM_PAL, ILBM_RGB, ... (se below) */
  struct DPIHeader xbmi_dpi;
};

#define ILBM_PAL        0       /* BODY data = indexes into the palette (CMAP)
                                 * numcolors = 1<<depth;
                                 */

#define ILBM_GREY       1       /* BODY =  grayscale values
                                 * Bits per sample = number of bitplanes.
                                 * Samples per pixel = 1.
                                 * black = 0, white = (1<<depth)-1;
                                 */

#define ILBM_RGB        2       /* BODY data = red, green, and blue values.
                                 * Bits per sample = depth/3.
                                 * Samples per pixel = 3.
                                 */

#define ILBM_RGBA       3       /* BODY data = red, green, blue, and alpha
                                 * channel values.
                                 * Bits per sample = depth/4.
                                 * Samples per pixel = 4.
                                 */

#define ILBM_CMYK       4       /* BODY data = cyan, magenta, yellow, and black
                                 * values.
                                 * Bits per sample = depth/4.
                                 * Samples per pixel = 4.
                                 */

#define ILBM_CMYKA      5       /* BODY data = cyan, magenta, yellow, black,
                                 * and alpha channel values.
                                 * Bits per sample = depth/5.
                                 * Samples per pixel = 5.
                                 */

#define ILBM_BW         6       /* BODY data = black and white bits.
                                 * Bits per sample = 1.
                                 * Samples per pixel = 1.
                                 * white = 0, black = 1.
                                 */

static char* tempfile[3] = {"ram:temp.red","ram:temp.green","ram:temp.blue"};

static void makePlane(UBYTE* source,UBYTE* dest,int bit,int pixelPerLine,int bytesPerPixel)
{
  int   bitNum,byteNum,offset;
  UBYTE b,sourceMask;

  offset = bit/8;
  bit %= 8;
  
  sourceMask = 0x01 << bit;
  
  source += offset;
  for( byteNum = 0 ; byteNum < pixelPerLine ; byteNum++ )
  {
    UBYTE destMask;
    
    destMask = 0x80;
    b = 0;
    for( bitNum = 0 ; bitNum < 8 ; bitNum++ )
    {
      if( (*source) & sourceMask )
        b |= destMask;

      destMask >>= 1;
      source += bytesPerPixel;
    }
    *(dest++) = b;
  }
} 

static void makeILBM(struct ILBMFile* ilbmfh)
{
  struct BitMapHeader BMHD;
  struct xbmi         XBMI;
  FILE*  fh;
  int    c;
  
  if( ilbmfh->flags & ILBMFLAGS_TEMPFILE )
  {
    for( c = 0 ; c < 3 ; c++ )
    {
      if( ilbmfh->buf[c].fh && (ilbmfh->buf[c].fh = fopen(tempfile[c],"r")) )
        setvbuf(ilbmfh->buf[c].fh,ilbmfh->buf[c].buf,_IOFBF,ilbmfh->bufLen);
    }
  }
  
  BMHD.bmh_Width = ilbmfh->width;
  BMHD.bmh_Height = ilbmfh->height;
  BMHD.bmh_Left = 0;
  BMHD.bmh_Top = 0;
  BMHD.bmh_Depth = ilbmfh->depth;
  BMHD.bmh_Masking = 0;
  BMHD.bmh_Pad = 0;
  BMHD.bmh_Transparent = 0;
  BMHD.bmh_XAspect = 1;
  BMHD.bmh_YAspect = 1;
  BMHD.bmh_PageWidth = ilbmfh->width;
  BMHD.bmh_PageHeight = ilbmfh->height;
  
  switch( ilbmfh->depth )
  {
    case 1:
      XBMI.xbmi_type = ILBM_BW;
      break;
    case 8:
      XBMI.xbmi_type = ILBM_GREY;
      break;
    case 24:
      XBMI.xbmi_type = ILBM_RGB;
      break;
  }        
  XBMI.xbmi_dpi.dpi_x = ilbmfh->xdpi;
  XBMI.xbmi_dpi.dpi_y = ilbmfh->ydpi;

  if( fh = fopen(ilbmfh->fileName,"w") )
  {
    int scanLine,color,bitNum;
    ULONG lineLen,bodyLen,formLen;
    ULONG temp,psize;
    UBYTE *line;
    UBYTE *pline = NULL;
    UBYTE *scanbuf = NULL;

    lineLen = ((ilbmfh->width+7)/8+1) & 0xFFFFFFFE; // Number of bits in each plane row
                                                    // Rounded up to an even number of words
    line = malloc(ilbmfh->format==FORMAT_RGB?3*lineLen:lineLen);
    if( (ilbmfh->flags & ILBMFLAGS_COMPRESS) && (pline = malloc(ilbmfh->format==FORMAT_RGB?6*lineLen:lineLen*2)) )
      BMHD.bmh_Compression = 1;
    else
      BMHD.bmh_Compression = 0;
      
    temp = ID_FORM;
    fwrite(&temp,4,1,fh);
    ilbmfh->formLenOffset = ftell(fh);
    temp = 0;
    fwrite(&temp,4,1,fh);

    temp = ID_ILBM;
    fwrite(&temp,4,1,fh);

    temp = ID_BMHD;
    fwrite(&temp,4,1,fh);
    temp = sizeof(struct BitMapHeader);
    fwrite(&temp,4,1,fh);
    fwrite(&BMHD,sizeof(struct BitMapHeader),1,fh);
    
    temp = ID_DPI;
    fwrite(&temp,4,1,fh);

    temp = sizeof(struct DPIHeader);
    fwrite(&temp,4,1,fh);
    fwrite(&XBMI.xbmi_dpi,sizeof(struct DPIHeader),1,fh);

    temp = ID_XBMI;
    fwrite(&temp,4,1,fh);
    temp = sizeof(struct xbmi);
    
    fwrite(&temp,4,1,fh);
    fwrite(&XBMI,sizeof(struct xbmi),1,fh);

    if( ilbmfh->depth == 1 )
    {
      UBYTE b;
      
      temp = ID_CMAP;
      fwrite(&temp,4,1,fh);
      temp = 3*2;;
      fwrite(&temp,4,1,fh);

      b = 0xFF;
      fwrite(&b,1,1,fh);
      fwrite(&b,1,1,fh);
      fwrite(&b,1,1,fh);

      b = 0;
      fwrite(&b,1,1,fh);
      fwrite(&b,1,1,fh);
      fwrite(&b,1,1,fh);
    }
    else if( ilbmfh->depth == 8 )
    {
      UBYTE b;
      int   i;
      
      temp = ID_CMAP;
      fwrite(&temp,4,1,fh);
      temp = 3*256;;
      fwrite(&temp,4,1,fh);
      for( i = 0 ;  i < 256 ; i++ )
      {
        b = (UBYTE)i;
        fwrite(&b,1,1,fh);
        fwrite(&b,1,1,fh);
        fwrite(&b,1,1,fh);
      }
    }
    
    temp = ID_BODY;
    fwrite(&temp,4,1,fh);
    ilbmfh->bodyLenOffset = ftell(fh);
    temp = 0;
    fwrite(&temp,4,1,fh);

    if( ilbmfh->flags & ILBMFLAGS_TEMPFILE )
      scanbuf = malloc(ilbmfh->scanLineWidth);
    
    switch( ilbmfh->format )
    {
      case FORMAT_BW:
        for( scanLine = 0 ; scanLine < ilbmfh->height ; scanLine++ )
        {
          if( ilbmfh->flags & ILBMFLAGS_TEMPFILE )
            fread(scanbuf,ilbmfh->scanLineWidth,1,ilbmfh->buf[0].fh);
          else
            scanbuf = ilbmfh->buf[0].buf + scanLine*ilbmfh->scanLineWidth;

          if( pline == NULL )
            fwrite(scanbuf,lineLen,1,fh);
          else
          {
            psize = PackRow(scanbuf,pline,lineLen);
            fwrite(pline,psize,1,fh);
          }
        }
        break;
      case FORMAT_GRAY:
      case FORMAT_RED:
      case FORMAT_GREEN:
      case FORMAT_BLUE:
        for( scanLine = 0 ; scanLine < ilbmfh->height ; scanLine++ )
        {
          if( ilbmfh->flags & ILBMFLAGS_TEMPFILE )
            fread(scanbuf,ilbmfh->scanLineWidth,1,ilbmfh->buf[0].fh);
          else
            scanbuf = ilbmfh->buf[0].buf + scanLine*ilbmfh->scanLineWidth;
          for( bitNum = 0 ; bitNum < 8 ; bitNum++ )
          {
            makePlane(scanbuf,line,bitNum,lineLen,1);
            if( pline == NULL )
              fwrite(line,lineLen,1,fh);
            else
            {
              psize = PackRow(line,pline,lineLen);
              fwrite(pline,psize,1,fh);
            }
          }
        }
        break;
      case FORMAT_RGB:
        for( scanLine = 0 ; scanLine < ilbmfh->height ; scanLine++ )
        {
          if( ilbmfh->flags & ILBMFLAGS_TEMPFILE )
            fread(scanbuf,ilbmfh->scanLineWidth,1,ilbmfh->buf[0].fh);
          else
            scanbuf = ilbmfh->buf[0].buf + scanLine*ilbmfh->scanLineWidth;
          for( bitNum = 0 ; bitNum < 24 ; bitNum++ )
          {
            makePlane(scanbuf,line,bitNum,lineLen,3);
            if( pline == NULL )
              fwrite(line,lineLen,1,fh);
            else
            {
              psize = PackRow(line,pline,lineLen);
              fwrite(pline,psize,1,fh);
            }
          }
        }
        break;
      case FORMAT_RGB_RANDOM:
        for( scanLine = 0 ; scanLine < ilbmfh->height ; scanLine++ )
        {
          for( color = 0 ; color < 3 ; color++ )
          {
            if( ilbmfh->buf[color].buf )
            {
              if( ilbmfh->flags & ILBMFLAGS_TEMPFILE )
                fread(scanbuf,ilbmfh->scanLineWidth,1,ilbmfh->buf[color].fh);
              else
                scanbuf = ilbmfh->buf[color].buf + scanLine*ilbmfh->scanLineWidth;
              for( bitNum = 0 ; bitNum < 8 ; bitNum++ )
              {
                makePlane(scanbuf,line,bitNum,lineLen,1);
                if( pline == NULL )
                  fwrite(line,lineLen,1,fh);
                else
                {
                  psize = PackRow(line,pline,lineLen);
                  fwrite(pline,psize,1,fh);
                }
              }
            }
          }
        }
        break;
    }        
    free(line);
    if( pline )
      free(pline);
    if( ilbmfh->flags & ILBMFLAGS_TEMPFILE )
      free(scanbuf);

    for( color = 0 ; color < 3 ; color++ )
    {
      if( ilbmfh->buf[color].fh )
        fclose(ilbmfh->buf[color].fh);
      if( ilbmfh->buf[color].buf )
        free(ilbmfh->buf[color].buf);
    }

    // Write FORM and BODY length
    formLen = ftell(fh) - ilbmfh->formLenOffset - 4;
    bodyLen = ftell(fh) - ilbmfh->bodyLenOffset - 4;

    fseek(fh,ilbmfh->formLenOffset,SEEK_SET);
    fwrite(&formLen,4,1,fh);

    fseek(fh,ilbmfh->bodyLenOffset,SEEK_SET);
    fwrite(&bodyLen,4,1,fh);
    
    fclose(fh);
  }
}


struct ILBMFile *openILBM(const char* name,const short compr,struct ScanInformation* info)
{
  struct ILBMFile* fh;
  
  if( fh = calloc(1,sizeof(struct ILBMFile)) )
  {
    fh->fileName = name;
    if( compr )
      fh->flags = ILBMFLAGS_COMPRESS;
    else
      fh->flags = 0;
    fh->depth = info->sv_imageDepth;
    fh->width = info->sv_imageWidth;
    fh->height = info->sv_imageHeight;
    fh->xdpi = (UWORD)(info->sv_xResolution+0.5);
    fh->ydpi = (UWORD)(info->sv_yResolution+0.5);
    fh->format = info->sv_lineFormat;
    fh->scanLineWidth = info->sv_bytesPerLine;
    fh->bufLen = fh->scanLineWidth*fh->height;
    
    switch( fh->format )
    {
      case FORMAT_BW:
      case FORMAT_GRAY:
      case FORMAT_RED:
      case FORMAT_GREEN:
      case FORMAT_BLUE:
      case FORMAT_RGB:
        if( fh->bufLen > ILBM_BUFLEN )
          fh->bufLen = ILBM_BUFLEN;
        fh->buf[0].buf = malloc(fh->bufLen);
        break;
      case FORMAT_RGB_RANDOM:
        {
          int c;
          
          if( 3*fh->bufLen > ILBM_BUFLEN )
            fh->bufLen = ILBM_BUFLEN/3;
            
          for( c = 0 ; c < 3 ; c++ )
            fh->buf[c].buf = malloc(fh->bufLen);
        }
        break;
      default:
        free(fh);
        return NULL;
    }
  }
        
  return fh;
}

void closeILBM(struct ILBMFile* ilbmfh,short error)
{
  int    c;
  
  if( ilbmfh->flags & ILBMFLAGS_TEMPFILE )
  {
    for( c = 0 ; c < 3 ; c++ )
    {
      if( ilbmfh->buf[c].fh )
      {
        fwrite(ilbmfh->buf[c].buf,ilbmfh->buf[c].actualLen,1,ilbmfh->buf[c].fh);
        fclose(ilbmfh->buf[c].fh);
        // ilbmfh->buf[c].fh = NULL;
      }
    }
  }

  if( !error )
    makeILBM(ilbmfh);

  if( ilbmfh->flags & ILBMFLAGS_TEMPFILE )
  {
    for( c = 0 ; c < 3 ; c++ )
      remove(tempfile[c]);
  }

  free(ilbmfh);
}

void writeILBM(struct ILBMFile* fh,struct ScanLine* line)
{
  int cPlane;
  
  if( fh->format == FORMAT_RGB_RANDOM )
  {
    switch( line->sl_color )
    {
      case FORMAT_RED:
        cPlane = 0;
        break;
      case FORMAT_GREEN:
        cPlane = 1;
        break;
      case FORMAT_BLUE:
        cPlane = 2;
        break;
      default:
        return;
    }
  }
  else
    cPlane = 0;
      
  if( fh->buf[cPlane].actualLen + fh->scanLineWidth > fh->bufLen )
  {
    if( fh->buf[cPlane].fh == NULL )
    {
      if( fh->buf[cPlane].fh = fopen(tempfile[cPlane],"w") )
        fh->flags |= ILBMFLAGS_TEMPFILE;
      else
        return;
    }
          
    fwrite(fh->buf[cPlane].buf,fh->buf[cPlane].actualLen,1,fh->buf[cPlane].fh);
    fh->buf[cPlane].actualLen = 0;
  }

  memcpy(fh->buf[cPlane].buf+fh->buf[cPlane].actualLen,line->sl_data,fh->scanLineWidth);
  fh->buf[cPlane].actualLen += fh->scanLineWidth;
}
