/********************************************************************************
*                                                                               *
*                          I F F   I n p u t / O u t p u t                      *
*                                                                               *
*********************************************************************************
* Copyright (C) 2004,2026 by Jeroen van der Zijp.   All Rights Reserved.        *
*********************************************************************************
* This library is free software; you can redistribute it and/or modify          *
* it under the terms of the GNU Lesser General Public License as published by   *
* the Free Software Foundation; either version 3 of the License, or             *
* (at your option) any later version.                                           *
*                                                                               *
* This library is distributed in the hope that it will be useful,               *
* but WITHOUT ANY WARRANTY; without even the implied warranty of                *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                 *
* GNU Lesser General Public License for more details.                           *
*                                                                               *
* You should have received a copy of the GNU Lesser General Public License      *
* along with this program.  If not, see <http://www.gnu.org/licenses/>          *
********************************************************************************/
#include "xincs.h"
#include "fxver.h"
#include "fxdefs.h"
#include "fxmath.h"
#include "FXElement.h"
#include "FXHash.h"
#include "FXElement.h"
#include "FXStream.h"


/*
  Notes:
  - EA IFF 85 Electronic Arts' standard for interchange format files.
  - Based on "EA IFF 85" Standard for Interchange Format Files, Jerry Morrison,
    January 14, 1985.
  - And on "ILBM" IFF Interleaved Bitmap specification, Jerry Morrison,
    January 17, 1986.
  - Some day, when I have some sample files, I may support Maya IFF also.

*/

#define TOPIC_DETAIL 1015

using namespace FX;

/*******************************************************************************/

namespace FX {


#ifndef FXLOADIFF
extern FXAPI FXbool fxcheckIFF(FXStream& store);
extern FXAPI FXbool fxloadIFF(FXStream& store,FXColor*& data,FXint& width,FXint& height);
#endif


// Tags
enum{
  FORM = 0x464F524D,
  FOR1 = 0x464F5231,
  FOR2 = 0x464F5232,
  FOR3 = 0x464F5233,
  FOR4 = 0x464F5234,
  ILBM = 0x494C424D,
  BMHD = 0x424D4844,
  CMAP = 0x434D4150,
  CAMG = 0x43414D47,
  BODY = 0x424F4459,
  FLDS = 0x464C4453,
  FMT1 = 0x464D5401,
  TBMP = 0x54424D50,
  CIMG = 0x43494D47,
  TBHD = 0x54424844,
  RGBA = 0x52474241,
  CLPZ = 0x434C505A,
  ESXY = 0x45535859,
  ZBUF = 0x5A425546,
  BLUR = 0x424C5552,
  BLRT = 0x424C5254,
  HIST = 0x48495354,
  CAT  = 0x43415420,
  GRAB = 0x47524142,
  PBM  = 0x50424D20
  };


// Format
enum {
 ILBM_NORMAL,
 ILBM_EHB,
 ILBM_HAM,
 ILBM_HAM8,
 ILBM_24BIT
 };


// Check if stream contains a IFF
FXbool fxcheckIFF(FXStream& store){
  if(store.direction()==FXStreamLoad){
    FXuchar signature[4];
    store.load(signature,4);
    store.position(-4,FXFromCurrent);
    return signature[0]=='F' && signature[1]=='O' && signature[2]=='R' && (signature[3]=='M' || signature[3]=='1' || signature[3]=='2' || signature[3]=='3' || signature[3]=='4');
    }
  return false;
  }


// Load IFF image from stream
FXbool fxloadIFF(FXStream& store,FXColor*& data,FXint& width,FXint& height){
  FXbool result=false;

  // Init
  data=nullptr;
  width=0;
  height=0;

  // Stream must be loading
  if(store.direction()==FXStreamLoad){

    // Old swap state
    FXbool swap=store.swapBytes();

    // Make big-endian
    store.setBigEndian(true);


    // Read tag
    FXuint tag;
    store >> tag;

    FXTRACE(TOPIC_DETAIL,"fxloadIFF tag=%c%c%c%c\n",(tag>>24)&255,(tag>>16)&255,(tag>>8)&255,tag&255);

    // Check for FORM tag
    if(tag==FORM || tag==FOR1 || tag==FOR2 || tag==FOR3 || tag==FOR4){
      FXuint   pixels,bit,view,size,type,colors,i,bytesperline,value,plane,remainingbytes,fmt;
      FXuchar *buffer,*ptr,planes,masking,compress,padding,XAspect,YAspect,c1,c2,c3,color,count;
      FXColor  colormap[256],*dest,pixelcolor;
      FXshort  X,Y,Width,Height,PageWidth,PageHeight,TransparentColor;
      FXlong   pos,end;
      FXint    x,y;
      FXbool   swap;

      buffer=nullptr;
      pixels=0;
      view=0;
      colors=0;
      planes=0;
      bytesperline=0;
      pixelcolor=0;
      fmt=ILBM_NORMAL;

      // Read size
      store >> size;

      // Calculate end of data
      pos=store.position();
      end=pos+size;

      // Read type
      store >> type;
      if(type==ILBM){

        // Read stuff
        while(1){

          // Bail if at the end
          if(store.position()+8>end) goto x;

          // Read next chunk header
          store >> tag;
          store >> size;
          pos=store.position();

          // Empty block is a problem too
          if(size==0) goto x;

          FXTRACE(TOPIC_DETAIL,"CHUNK %c%c%c%c POS=%ld SIZE=%d\n",(tag>>24)&255,(tag>>16)&255,(tag>>8)&255,tag&255,pos,size);

          // Bitmap header
          if(tag==BMHD){
            store >> Width;
            store >> Height;
            store >> X;
            store >> Y;
            store >> planes;
            store >> masking;
            store >> compress;
            store >> padding;
            store >> TransparentColor;
            store >> XAspect;
            store >> YAspect;
            store >> PageWidth;
            store >> PageHeight;
            pixels=Width*Height;                      // Total image size
            bytesperline=((Width+15)>>4)<<1;          // Bytes per line
            }

          // Colormap
          else if(tag==CMAP){
            colors=size/3;
            if(colors<1) goto x;
            if(colors>256) goto x;
            clearElms(colormap,ARRAYNUMBER(colormap));
            for(i=0; i<colors; i++){                  // Load colors
              store >> c1 >> c2 >> c3;
              colormap[i]=FXRGB(c1,c2,c3);
              }
            }

          // Commodore AMiGa
          else if(tag==CAMG){
            store >> view;
            FXTRACE(TOPIC_DETAIL,"view=%04x\n",view);
            }

          // Body
          else if(tag==BODY){
            break;
            }

          // Next chunk
          store.position(pos+size+(size&1));
          }

        // Wat voor vlees in de kuip?
        FXTRACE(TOPIC_DETAIL,"fxloadIFF: Width=%d Height=%d planes=%d masking=%d compress=%d padding=%d\n",Width,Height,planes,masking,compress,padding);

        // Determine format
        if(planes==24){
          fmt=ILBM_24BIT;
          }
        else if(planes==8){
          if(view&0x800){
            fmt=ILBM_HAM8;
            }
          }
        else if(planes>5){
          if(view&0x80){
            fmt=ILBM_EHB;
            }
          else if(view&0x800){
            fmt=ILBM_HAM;
            }
          }

        // Tweak colormap
        if((fmt==ILBM_NORMAL) || (fmt==ILBM_EHB) || (fmt==ILBM_HAM)){

          // Colors based on HAM mode
          switch(fmt) {
            case ILBM_NORMAL: colors=1<<planes; break;
            case ILBM_EHB: colors=32*2; break;
            case ILBM_HAM: colors=16; break;
            }

          // Divide colors
          for(i=0; i<colors; i++){
            ((FXuchar*)&colormap[i])[0]=(((FXuchar*)&colormap[i])[0]>>4)*17;
            ((FXuchar*)&colormap[i])[1]=(((FXuchar*)&colormap[i])[1]>>4)*17;
            ((FXuchar*)&colormap[i])[2]=(((FXuchar*)&colormap[i])[2]>>4)*17;
            }

          // Extended half-bright mode
          if(fmt==ILBM_EHB){
            for(i=0; i<32; i++) {
              ((FXuchar*)&colormap[colors+i])[0]=((FXuchar*)&colormap[i])[0]>>1;
              ((FXuchar*)&colormap[colors+i])[1]=((FXuchar*)&colormap[i])[1]>>1;
              ((FXuchar*)&colormap[colors+i])[2]=((FXuchar*)&colormap[i])[2]>>1;
              }
            }
          }

        // Temp buffer
        if(allocElms(buffer,bytesperline*planes)){

          // Try allocate image
          if(allocElms(data,pixels)){

            // Returned image size
            width=Width;
            height=Height;

            dest=data;

            // Loop over y
            for(y=0; y<height; y++){

              // Uncompress data of each planes
              for(plane=0; plane<planes; plane++){
                ptr=buffer+bytesperline*plane;
                remainingbytes=bytesperline;
                if(compress==1){          // Compressed
                  do{
                    store >> count;
                    if(count&0x80){
                      count^=0xFF;
                      count+=2;
                      store >> color;
                      if(count>remainingbytes) count=remainingbytes;
                      fillElms(ptr,color,count);
                      }
                    else{
                      count+=1;
                      if(count>remainingbytes) count=remainingbytes;
                      store.load(ptr,count);
                      }
                    ptr+=count;
                    remainingbytes-=count;
                    }
                  while(remainingbytes>0);
                  }
                else{                     // Not compressed
                  store.load(ptr,bytesperline);
                  }
                }

              // Loop over x
              for(x=0; x<width; x++){

                // Build value from the planes
                for(plane=value=0; plane<planes; plane++){
                  bit=(buffer[bytesperline*plane+(x>>3)]>>(7-(x&7)))&1;
                  value|=bit<<plane;
                  }

                // Determine what to do based on mode
                switch(fmt){
                  case ILBM_NORMAL:
                  case ILBM_EHB:
                    pixelcolor=colormap[value&255];
                    break;
                  case ILBM_24BIT:
                    pixelcolor=(value<<8);
                    break;
                  case ILBM_HAM:
                    switch(value&0x30) {
                      case 0x00: pixelcolor=colormap[value&15]; break;
                      case 0x10: pixelcolor=(pixelcolor&FXRGB(255,255,0))|FXRGB(0,0,(value&15)*17); break;
                      case 0x20: pixelcolor=(pixelcolor&FXRGB(0,255,255))|FXRGB((value&15)*17,0,0); break;
                      case 0x30: pixelcolor=(pixelcolor&FXRGB(255,0,255))|FXRGB(0,(value&15)*17,0); break;
                      }
                    break;
                  case ILBM_HAM8:
                    switch(value&0xc0) {
                      case 0x00: pixelcolor=colormap[value&0x3f]; break;
                      case 0x40: pixelcolor=(pixelcolor&FXRGB(255,255,0))|FXRGB(0,0,(value&0x3f)<<2); break;
                      case 0x80: pixelcolor=(pixelcolor&FXRGB(0,255,255))|FXRGB((value&0x3f)<<2,0,0); break;
                      case 0xc0: pixelcolor=(pixelcolor&FXRGB(255,0,255))|FXRGB(0,(value&0x3f)<<2,0); break;
                      }
                    break;
                  }
                *dest++=pixelcolor;
                }
              }

            // OK
            result=true;
            }

          // Release buffer
          freeElms(buffer);
          }
        }
      }
x:  store.swapBytes(swap);
    }
  return result;
  }

}
