/* IFF images
 *
 * dieter fiebelkorn 13.05.91
 *
 */


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <math.h>
#include <vdi.h>
#include <aes.h>
#include "image.h"
#include "imageopt.h"
#include "module.h"
#include "iff.h"

extern char *DupString(LOAD_Structure *loadS, char *s);


COLOR_ENTRY    color_map[256];

static int loadIffMonoCompressed(ZFILE *zf, LOAD_Structure *loadS, char *data,
                                 unsigned int width, unsigned int height)
{
  unsigned char  wert, run;
  signed char    command;
  long           read, toRead;
  
  toRead = (width >> 3) + ((width & 0x0007) ? 1 : 0);
  toRead *= height;
  read = 0UL;
  do {
    command = (signed char) (*loadS->input.getchr)(zf);
    if (command == -128) {
      (*loadS->input.close)(zf);
      (*loadS->print.printout)("DegasLoad: Bad read on image data\n");
      return(-1);
    }
    if (command < 0) {
      wert = (unsigned char) (*loadS->input.getchr)(zf);
      for (run = 0; run < -command+1; run++, read++)
        *data++ = wert;
    }
    else {
      for (run = 0; run < command+1; run++, read++) {
        wert = (unsigned char) (*loadS->input.getchr)(zf);
        *data++ = wert;
      }
    }
  } while (read < toRead);
  return(0);
}



static int loadIffColorCompressed(ZFILE *zf, LOAD_Structure *loadS, char *data,
                                  unsigned int width, unsigned int height,
                                  unsigned int planes, unsigned int ismask)
{
  unsigned char  wert, run,
                 buffer[BUFSIZ];
  signed char    command, warning;
  long           read;
  unsigned long  x, y, plane, offs;
  unsigned long  set_bit, mask;
  
  warning = '\0';
  for (y= 0; y < height; y++) {
    for (plane= 0; plane < planes+(ismask!=0); plane++) {
      read = 0UL;
      do {
        command = (signed char) (*loadS->input.getchr)(zf);
        if (command == -128)
          continue;
        
        if (command < 0) {
          wert = (unsigned char) (*loadS->input.getchr)(zf);
          for (run = 0; run < -command+1; run++, read++)
            buffer[read] = wert;
        }
        else {
          for (run = 0; run < command+1; run++, read++) {
            wert = (unsigned char) (*loadS->input.getchr)(zf);
            buffer[read] = wert;
          }
        }
      } while (read < width/8);
  
      if (read > width/8 && warning == '\0')
      {
        (*loadS->print.printout)("  \034\001Warning:\034\100 Incorrect length of decoded scanline\n");
        warning = '\x01';
      }
      
      if (plane < planes) {
        set_bit = 1U << plane;
        for (x= 0; x < width/8; x++) {
          mask = 0x80;
          offs = 0;
          do {
            if (buffer[x] & mask)
              data[8UL * x + offs] |= set_bit;
            mask >>= 1;
            offs++;
          } while(mask);
        }
      }
    }
    data += width;
  }
  return(0);
}



static void loadIffMonoUnCompressed(ZFILE *zf, LOAD_Structure *loadS, char *data,
                                    unsigned int width, unsigned int height)
{
  unsigned long  toRead;
  
  toRead = (width >> 3) + ((width & 0x0007) ? 1 : 0);
  toRead *= height;
  (*loadS->input.read)(zf, data, toRead);
}



static int loadIffColorUnCompressed(ZFILE *zf, LOAD_Structure *loadS, char *data,
                                    unsigned int width, unsigned int height,
                                    unsigned int planes, unsigned int ismask)
{
  long           x, y;
  unsigned int   buffer, plane;
  unsigned int   mask, offs;
  
  for (y= 0; y < height; y++) {
    for (plane= 0; plane < planes+(ismask!=0); plane++) {
      for (x= 0; x < width/16; x++) {
        if ((*loadS->input.read)(zf, &buffer, 2UL) < 2UL) {
          (*loadS->error.printerr)("  Error reading IFF!");
          (*loadS->input.close)(zf);
          return(-1);
        }
        if (plane < planes) {
          mask = 0x8000;
          offs = 0;
          while (mask) {
            if (buffer & mask)
              data[16*x + offs] |= 1U << plane;
            mask >>= 1;
            offs++;
          }
        }
      }
    }
    data += width;
  }
  return(0);
}



static loadIffHam(ZFILE *zf, LOAD_Structure *loadS, char *data,
                  unsigned int width, unsigned int height, unsigned int alignwidth,
                  unsigned int planes, unsigned int compressed)
{
  COLOR_ENTRY    color;
  unsigned char *new_data;
  unsigned int   skip, x, y;
  unsigned int   op_shift, col_shift;
  unsigned char  op, col, col_mask;
  
  new_data = data + 2L * width * height;
  if (compressed)
    loadIffColorCompressed(zf, loadS, new_data, width, height, planes, 0);
  else
    loadIffColorUnCompressed(zf, loadS, new_data, width, height, planes, 0);
  
  if (planes != 6 && planes != 8)
    return(0);

  color.red   = 0;
  color.green = 0;
  color.blue  = 0;

  if (planes == 6) {
    op_shift = 4;
    col_mask = 0x0F;
    col_shift = 4;
  }
  if (planes == 8) {
    op_shift = 6;
    col_mask = 0x3F;
    col_shift = 2;
  }
  
  skip = alignwidth - width;
  for (y= 0; y < height; y++) {
    for (x= 0; x < width; x++) {
      op = *new_data >> op_shift;
      col = *new_data & col_mask;
      if (op == 0) {
        color.red   = color_map[(int)col].red;
        color.green = color_map[(int)col].green;
        color.blue  = color_map[(int)col].blue;
      }
      if (op == 2) {
        color.red   = col << col_shift;
      }
      if (op == 3) {
        color.green = col << col_shift;
      }
     if (op == 1) {
        color.blue  = col << col_shift;
      }
      *data++ = color.red;
      *data++ = color.green;
      *data++ = color.blue;
      new_data++;
    }
    data += 3L * skip;
  }
  return(0);
}



int iffIdent(LOAD_Structure *loadS, unsigned int verbose)
{
  ZFILE         *zf;
  IFF_HEADER     iffheader;
  CHUNK_HEADER   chunkheader;
  BITMAP_HEADER  bitmapheader;
  int            found_camgchunk,
                 found_bitmapheader;
  long           i;
  char          *fullname = loadS->in_filename;
  
  if ((zf= (*loadS->input.open)(fullname, 0x00)) != NULL) {
    (*loadS->input.read)(zf, &iffheader, sizeof(iffheader));
    
    while ((*(long*)&iffheader.form_id == 'FORM') && (*(long*)&iffheader.file_id != 'ILBM')) {
      (*loadS->input.skip)(zf, iffheader.lenght);
      if ((*loadS->input.read)(zf, &iffheader, sizeof(iffheader)) != sizeof(iffheader)) {
        (*loadS->input.close)(zf);
        return(0);
      }
    }
    if ((*(long*)&iffheader.form_id != 'FORM') || (*(long*)&iffheader.file_id != 'ILBM')) {
      (*loadS->input.close)(zf);
      return(0);
    }

    found_camgchunk = 0;
    found_bitmapheader = 0;

    while(1) {
      (*loadS->input.read)(zf, &chunkheader, sizeof(chunkheader));
      if ((!isupper(chunkheader.chunk_id[0]) && chunkheader.chunk_id[0] != ' ') ||
          (!isupper(chunkheader.chunk_id[1]) && chunkheader.chunk_id[1] != ' ') ||
          (!isupper(chunkheader.chunk_id[2]) && chunkheader.chunk_id[2] != ' ') ) {
        (*loadS->error.printerr)("  Error reading IFF!");
        break;
      }
      if (*(long*)&chunkheader.chunk_id == 'CAMG') {
        (*loadS->input.read)(zf, &i, sizeof(long));
        if (i & 0x00000800L)
          found_camgchunk = 1;
        continue;
      }
      if (*(long*)&chunkheader.chunk_id == 'BMHD')
      {
        (*loadS->input.read)(zf, &bitmapheader, sizeof(bitmapheader));
        found_bitmapheader = 1;
        continue;
      }
      if (*(long*)&chunkheader.chunk_id == 'BODY')
      {
        if (bitmapheader.planes == 1)
          (*loadS->print.printout)("%s\n  is a %dx%d monochrome/duochrome IFF-image\n", fullname,
                  bitmapheader.width, bitmapheader.height);
        else
          if (found_camgchunk == 1 && (bitmapheader.planes == 6 || bitmapheader.planes == 8))
            (*loadS->print.printout)("%s\n  is a %dx%d HAM-IFF-image\n", fullname,
                    bitmapheader.width, bitmapheader.height);
          else
            (*loadS->print.printout)("%s\n  is a %dx%d IFF-image with %d colors\n", fullname,
                    bitmapheader.width, bitmapheader.height, 1U << bitmapheader.planes);

        (*loadS->input.close)(zf);
        return(found_bitmapheader);
      }
      (*loadS->input.skip)(zf, chunkheader.lenght);
    }
  }
  (*loadS->input.close)(zf);
  return(0);
}



Image *iffLoad(LOAD_Structure *loadS, unsigned int verbose)
{
  ZFILE         *zf;
  Image         *image = NULL;
  IFF_HEADER     iffheader;
  CHUNK_HEADER   chunkheader;
  BITMAP_HEADER  bitmapheader;
  int            found_bitmapheader;
  int            found_camgchunk;
  int            found_cmapchunk;
  int            bw_pict;
  long           map_colors, colors,
                 max_colors, i;
  unsigned long  factor;
  char          *fullname = loadS->in_filename;
  int            id_only  = loadS->identify_only;
  
  found_bitmapheader = 0;
  map_colors         = 0;
  max_colors         = 8;
  
  if (id_only)
    return((Image*)iffIdent(loadS, verbose));
  
  if (iffIdent(loadS, verbose) == 0)
    return(NULL);
  
  if ((zf= (*loadS->input.open)(fullname, 0x00)) == 0) {
    (*loadS->error.printerr)("  Error reading IFF!");
    return(NULL);
  }
  (*loadS->input.read)(zf, &iffheader, sizeof(iffheader));
  while ((*(long*)&iffheader.form_id == 'FORM') && (*(long*)&iffheader.file_id != 'ILBM')) {
    (*loadS->input.skip)(zf, iffheader.lenght);
    if ((*loadS->input.read)(zf, &iffheader, sizeof(iffheader)) != sizeof(iffheader)) {
      (*loadS->input.close)(zf);
      return(0);
    }
  }
  
  found_bitmapheader = 0;
  found_camgchunk = 0;
  found_cmapchunk = 0;
  while (1) {
    (*loadS->input.read)(zf, &chunkheader, sizeof(chunkheader));
    if ((!isupper(chunkheader.chunk_id[0]) && chunkheader.chunk_id[0] != ' ') ||
        (!isupper(chunkheader.chunk_id[1]) && chunkheader.chunk_id[1] != ' ') ||
        (!isupper(chunkheader.chunk_id[2]) && chunkheader.chunk_id[2] != ' ') ) {
      (*loadS->error.printerr)("  Error reading IFF!");
      if (image)
        (*loadS->images.freeGVImage)(image);
      image = NULL;
      (*loadS->input.close)(zf);
      return(image);
    }
    if (*(long*)&chunkheader.chunk_id == 'BMHD')
    {
      found_bitmapheader = 1;
      (*loadS->input.read)(zf, &bitmapheader, sizeof(bitmapheader));
      if (bitmapheader.planes > 8) {
        (*loadS->print.printout)("  Don't support more then 8 planes");
        (*loadS->input.close)(zf);
        return(NULL);
      }
      if (bitmapheader.compressed > 1) {
        (*loadS->print.printout)("  Don't supported compression algorithm");
        (*loadS->input.close)(zf);
        return(NULL);
      }
      if ((bitmapheader.planes == 1) && (bitmapheader.mask & 0x01)) {
        (*loadS->print.printout)("  Don't support monochrome Images with _mask_");
        (*loadS->input.close)(zf);
        return(NULL);
      }
      continue;
    }
    if (*(long*)&chunkheader.chunk_id == 'CMAP')
    {
      map_colors = chunkheader.lenght / 3;
      if (map_colors > 256) {
        (*loadS->print.printout)("  Don't support more then 256 colors");
        (*loadS->input.close)(zf);
        return(NULL);
      }
      found_cmapchunk = 1;
      if (map_colors <= 2)
        bw_pict = 1;
      for (i = 0; i < map_colors; i++) {
        (*loadS->input.read)(zf, &(color_map[i]), 3UL /*sizeof(COLOR_ENTRY)*/);
        if (max_colors < 256) {
          if (max_colors < 16) {
            if ((color_map[i].red > 7) || (color_map[i].green > 7) || (color_map[i].blue > 7))
              max_colors = 16;
          }
          if ((color_map[i].red > 15) || (color_map[i].green > 15) || (color_map[i].blue > 15))
            max_colors = 256;
        }
        if (bw_pict == 1) {
          switch((int)max_colors)
          {
            case   8:
              bw_pict &= (color_map[i].red >= 7 && color_map[i].green >= 7 && color_map[i].blue >= 7)
                      || (color_map[i].red <= 0 && color_map[i].green <= 0 && color_map[i].blue <= 0);
              break;
            case  16:
              bw_pict &= (color_map[i].red >= 15 && color_map[i].green >= 15 && color_map[i].blue >= 15)
                      || (color_map[i].red <=  0 && color_map[i].green <=  0 && color_map[i].blue <=  0);
              break;
            case 256:
              bw_pict &= (color_map[i].red >= 254 && color_map[i].green >= 254 && color_map[i].blue >= 254)
                      || (color_map[i].red <=   0 && color_map[i].green <=   0 && color_map[i].blue <=   0);
              break;
          }
        }
      }
      continue;
    }
    if (*(long*)&chunkheader.chunk_id == 'CAMG') {
      (*loadS->input.read)(zf, &i, sizeof(long));
      if (i & 0x00000800L)
        found_camgchunk = 1;
      continue;
    }

    if (*(long*)&chunkheader.chunk_id == 'BODY') {
      if (found_bitmapheader) {
        if (bitmapheader.planes == 1 && (found_cmapchunk == 0 || bw_pict != 0)) {
          if ((bitmapheader.width == 0) ||
              (bitmapheader.height == 0)) {
            (*loadS->input.close)(zf);
            return (NULL);
          }
          
          image = (*loadS->images.newBitImage)(NULL, (unsigned int) bitmapheader.width, (unsigned int) bitmapheader.height);
          if (!image) {
            (*loadS->input.close)(zf);
            return (image);
          }
          
          if (bitmapheader.width % 16)
          {
            image->unalignwidth = bitmapheader.width;
            image->width = (bitmapheader.width += 16 - (bitmapheader.width % 16));
          }
          if (bitmapheader.compressed)
            loadIffMonoCompressed(zf, loadS, image->data, bitmapheader.width, bitmapheader.height);
          else
            loadIffMonoUnCompressed(zf, loadS, image->data, bitmapheader.width, bitmapheader.height);
        }
        else
        {
          if ((bitmapheader.width == 0) ||
              (bitmapheader.height == 0) ||
              (bitmapheader.planes == 0)) {
            (*loadS->input.close)(zf);
            return (NULL);
          }

          if (found_camgchunk == 0 || (bitmapheader.planes != 6 && bitmapheader.planes != 8))
            image = (*loadS->images.newRGBImage)(NULL, (unsigned int) bitmapheader.width, (unsigned int) bitmapheader.height, bitmapheader.planes);
          else
            image = (*loadS->images.newTCImage)(NULL, (unsigned int) bitmapheader.width, (unsigned int) bitmapheader.height);
          if (!image) {
            (*loadS->input.close)(zf);
            return (image);
          }

          if (bitmapheader.width % 16)
          {
            image->unalignwidth = bitmapheader.width;
            image->width = (bitmapheader.width += 16 - (bitmapheader.width % 16));
          }

          if (found_camgchunk == 1 && (bitmapheader.planes == 6 || bitmapheader.planes == 8)) {
            if (loadIffHam(zf, loadS, image->data, bitmapheader.width, bitmapheader.height, image->width, bitmapheader.planes, bitmapheader.compressed)) {
              (*loadS->images.freeGVImage)(image);
              image = NULL;
            }
            if (image)
              image->title= DupString(loadS, fullname);
            (*loadS->input.close)(zf);
            return(image);
          }
          
          image->rgb.red  [ 0]= 0xFF00;
          image->rgb.green[ 0]= 0xFF00;
          image->rgb.blue [ 0]= 0xFF00;
          colors = 1U << bitmapheader.planes;
          switch((int)max_colors)
          {
            case   8:
              factor = 364;
              break;
            case  16:
              factor = 170;
              break;
            case 256:
              factor = 10;
              break;
          }

          if (map_colors > colors)
            map_colors = colors;

/*        colors -= map_colors;
*/        for (i = 0; i < map_colors; i++) {
            image->rgb.red[i]   = ((int) (factor * color_map[i].red   / 10)) << 8;
            image->rgb.green[i] = ((int) (factor * color_map[i].green / 10)) << 8;
            image->rgb.blue[i]  = ((int) (factor * color_map[i].blue  / 10)) << 8;
          }
          image->rgb.used = (int)map_colors;
          for (/* i = 0 */; i < colors; i++) {
            image->rgb.red[i]   = 0x0000;
            image->rgb.green[i] = 0x0000;
            image->rgb.blue[i]  = 0x0000;
          }
          if (bitmapheader.compressed)
            loadIffColorCompressed(zf, loadS, image->data, bitmapheader.width, bitmapheader.height, bitmapheader.planes, bitmapheader.mask & 0x01);
          else
            if (loadIffColorUnCompressed(zf, loadS, image->data, bitmapheader.width, bitmapheader.height, bitmapheader.planes, bitmapheader.mask & 0x01) == -1) {
              (*loadS->images.freeGVImage)(image);
              image = NULL;
              (*loadS->input.close)(zf);
              return(image);
            }
        }
      }
      break;
    }
    (*loadS->input.skip)(zf, chunkheader.lenght);
  }
  
  if (image)
    image->title= DupString(loadS, fullname);

  (*loadS->input.close)(zf);
  return(image);
}
