// ---------------------------- PIX.C -----------------------
// Written by Javier Ar‚valo Baeza.
// Screen loading functions.

#include "pix.h"
#include "jclib.h"
#include <string.h>
#include <ctype.h>

// =======================================================
// PCX

typedef struct {
    byte id;
    byte version;
    byte rle;
    byte bpp;
    word xstart;
    word ystart;
    word xend;
    word yend;
    word hres;
    word vres;
    byte pal[48];
    byte rsvd1;
    byte nbitp;
    word bytesperline;
    word paltype;
    word hsize;
    word vsize;
    byte rsvd2[54];
} PCXHeader;

PUBLIC int PIX_LoadPCX(const char* file, unsigned char *outpix, unsigned char * pal, int* width, int* height)
{
    int            i, w, h;
    PCXHeader      hdr;
    long pos;
    int ret = 0;

    FILE* fp = JCLIB_Open(file);
    if (fp == NULL)
        goto bye;
    pos = ftell(fp);
    fread (&hdr, 1, sizeof(hdr), fp);
    if (hdr.id != 0x0A || hdr.version != 5 || hdr.rle != 1
     || (hdr.bpp == 8 && hdr.nbitp != 1))
        goto bye;

    w = hdr.xend - hdr.xstart + 1;
    if (width)
        *width = w;
    h = hdr.yend - hdr.ystart + 1;
    if (height)
        *height = h;
    if (pal != NULL) {
        if (hdr.bpp == 1 && hdr.nbitp == 4) {
            memset(pal, 0, 768);
            for (i = 0; i < 48; i++)
                pal[i] = hdr.pal[i] >> 2;
        } else if (hdr.bpp == 8 && hdr.nbitp == 1) {
            fseek(fp, pos + JCLIB_FileSize(file)-768, SEEK_SET);
            for (i = 0; i < 768; i++)
                pal[i] = ((unsigned char)getc(fp)) >> 2;
        } else
            goto bye;
    }
    ret = 1;
    if (outpix == NULL)
        goto bye;
    fseek(fp, pos + sizeof(PCXHeader), SEEK_SET);

    while (h-- > 0) {
        unsigned char c;
        unsigned char *outpt;
        int np = 0;

        outpt = outpix;
        memset(outpix, 0, w);
        for (np = 0; np < hdr.nbitp; np++) {
            i = 0;
            outpix = outpt;
            do {
                c = (byte)getc(fp);
                if ((c & 0xC0) != 0xC0) {
                    if (hdr.bpp == 1) {
                        int k;
                        for (k = 7; k >= 0; k--)
                            *outpix++ |= ((c >> k) & 1) << np;
                        i += 8;
                    } else {
                        *outpix++ = c;
                        i++;
                    }
                } else {
                    unsigned char v;
                    v = (byte)getc(fp);
                    c &= ~0xC0;
                    while (c > 0 && i < w) {
                        if (hdr.bpp == 1) {
                            int k;
                            for (k = 7; k >= 0; k--)
                                *outpix++ |= ((v >> k) & 1) << np;
                            i += 8;
                        } else {
                            *outpix++ = v;
                            i++;
                        }
                        c--;
                    }
                }
            } while (i < w);
        }
    }

  bye:
    if (fp != NULL)
        JCLIB_Close(fp);
    return ret;
}

// =======================================================
// LBM

typedef unsigned char  LBMUBYTE;
typedef short          LBMWORD;
typedef unsigned short LBMUWORD;
typedef long           LBMLONG;
typedef char           LBMID[4];
typedef struct {
    LBMID      id;
    LBMLONG    size;
    LBMUBYTE   data[];
} LBMCHUNK;

    // A BitMapHeader is stored in a BMHD chunk.
typedef struct {
    LBMUWORD w, h;         /* raster width & height in pixels */
    LBMUWORD  x, y;         /* position for this image */
    LBMUBYTE nPlanes;      /* # source bitplanes */
    LBMUBYTE masking;    /* masking technique */
    LBMUBYTE compression;  /* compression algoithm */
    LBMUBYTE pad1;         /* UNUSED.  For consistency, put 0 here.*/
    LBMUWORD transparentColor;   /* transparent "color number" */
    LBMUBYTE xAspect, yAspect;   /* aspect ratio, a rational number x/y */
    LBMUWORD pageWidth, pageHeight;  /* source "page" size in pixels */
} LBMBMHD;

    // RowBytes computes the number of bytes in a row, from the width in pixels.
#define RowBytes(w)   (((w) + 15) >> 4 << 1)

#define LBMIDEQ(i1,i2) (*(long*)(i1) == *(long*)(i2))



PRIVATE LBMLONG EndianSwapL(LBMLONG l) {
    LBMLONG t = ((LBMLONG)((LBMUBYTE *)&l)[0] << 24) +
                ((LBMLONG)((LBMUBYTE *)&l)[1] << 16) +
                ((LBMLONG)((LBMUBYTE *)&l)[2] <<  8) +
                ((LBMLONG)((LBMUBYTE *)&l)[3] <<  0);
    return t;
}

PRIVATE LBMUWORD EndianSwapW(LBMUWORD l) {
    LBMUWORD t = ((LBMUWORD)((LBMUBYTE *)&l)[0] << 8) +
                 ((LBMUWORD)((LBMUBYTE *)&l)[1] << 0);
    return t;
}

PRIVATE bool ReadChunk(LBMCHUNK *chunk, FILE *f) {
    if (fread(chunk, sizeof(*chunk), 1, f) != 1)
        return FALSE;
    chunk->size = EndianSwapL(chunk->size);
    if (chunk->size & 1)
        chunk->size++;
    return TRUE;
}

PUBLIC int PIX_LoadLBM(const char* file, unsigned char *outpix, unsigned char * pal, int* width, int* height)
{
    FILE *fp;
    long pos;
    LBMCHUNK c;
    LBMID    fid;
    LBMLONG    total;
    bool compression;
    int     w = 0, h = 0;

    fp = JCLIB_Open(file);
    if (fp == NULL)
        return 0;
    pos = ftell(fp);
    if (!ReadChunk(&c, fp) || !LBMIDEQ(c.id, "FORM")
     || c.size < (4+8+sizeof(LBMBMHD)+8+768))
        goto FAIL;
    if (fread(&fid, sizeof(fid), 1, fp) != 1 || !LBMIDEQ(fid, "PBM "))
        goto FAIL;
    total = c.size - sizeof(fid);
    while (total > 0) {
        if (!ReadChunk(&c, fp))
            goto FAIL;
        total -= sizeof(c) + c.size;
        if (LBMIDEQ(c.id, "BMHD")) {
            LBMBMHD hd;
            if (c.size != sizeof(LBMBMHD) || fread(&hd, sizeof(hd), 1, fp) != 1
             || hd.nPlanes != 8)
                goto FAIL;
            w = EndianSwapW(hd.w);
            h = EndianSwapW(hd.h);
            if (width != NULL)  *width  = w;
            if (height != NULL) *height = h;
            compression = hd.compression;
//            if (pal == NULL && outpix == NULL)
//                break;
        } else if (LBMIDEQ(c.id, "CMAP")) {
            int i;
            if (c.size > 768)
                goto FAIL;
            if (pal == NULL) {
                fseek(fp, c.size, SEEK_CUR);
                continue;
            }
            if (fread(pal, c.size, 1, fp) != 1)
                goto FAIL;
            for (i = 0; i < c.size; i++)
                pal[i] >>= 2;
        } else if (LBMIDEQ(c.id, "BODY")) {
            int i;
            if (outpix == NULL) {
                fseek(fp, c.size, SEEK_CUR);
                continue;
            }
            for (i = 0; i < h; i++) {
                if (!compression) {
                    if (fread(outpix, w, 1, fp) != 1)
                        goto FAIL;
                    if (w & 1)
                        fgetc(fp);
                    outpix += w;
                } else {
                    int d, v;
                    d = w;
                    if (d & 1) d++;
                    while (d > 0) {
                        v = (signed char)fgetc(fp);
                        if (v > 0) {
                            v++;
                            d -= v;
                            if (fread(outpix, v, 1, fp) != 1)
                                goto FAIL;
                            outpix += v;
                        } else {
                            int c;
                            v = -v + 1;
                            d -= v;
                            c = fgetc(fp);
                            if (c == EOF)
                                goto FAIL;
                            memset(outpix, c, v);
                            outpix += v;
                        }
                    }
                }
            }
        } else {
            fseek(fp, c.size, SEEK_CUR);
        }
    }
    JCLIB_Close(fp);
    return TRUE;
  FAIL:
    JCLIB_Close(fp);
    return FALSE;
}

#define MAX_LZW_BITS 12                         /* Code can grow to this */
#define MAXSTACK 4096                           /* Maximum with 12 bits */
static int decodeLZW(FILE* f, byte* b)
{
    int clear_code, eoi_code, min_code_size, free_code;
    int code = -1, first;
    int nrworkbits = 0, code_size, code_mask, sp = 0;
    int bufidx = 0, buflen = 0, prev_code = -2;
    unsigned char buf[256];
    unsigned long workbits = 0;
    int stack[MAXSTACK], prefix[MAXSTACK], extnsn[MAXSTACK];

    /* Read initial code size, initialize special codes */
    if (! fread(buf, 1, 1, f)) return FALSE;
    min_code_size = buf[0];
    clear_code = 1 << min_code_size;
    eoi_code = clear_code + 1;
    free_code = clear_code + 2;
    code_size = min_code_size + 1;
    code_mask = (1 << code_size) - 1;

    /* Decode until we find an End-of-Information code */
    while (code != eoi_code) {

        /* Add bytes until we have enough bits for next code */
        while (nrworkbits < code_size) {

            /* Read new data block if needed */
            if (bufidx == buflen) {
                if (! fread(buf, 1, 1, f) || buf[0] == '\0') return FALSE;
                buflen = buf[0];
                if (! fread(buf, buflen, 1, f)) return FALSE;
                bufidx = 0;
            }
            workbits |= ((unsigned long) buf[bufidx++]) << nrworkbits;
            nrworkbits += 8;
        }

        /* Get next code */
        code = workbits & code_mask;
        workbits >>= code_size;
        nrworkbits -= code_size;

        /* Force first code of image to be a clear code */
        if (prev_code == -2) code = clear_code;

        /* Branch on type of code */
        if (code == clear_code) {               /* Reset the decoder */
            code_size = min_code_size + 1;      /* Original code size */
            code_mask = (1 << code_size) - 1;   /* Corresponding mask */
            free_code = clear_code + 2;         /* First pos. in tables */
            prev_code = -1;                     /* Next code is a root code */
        } else if (code == eoi_code) {          /* End of Information */
            /* skip */
        } else if (prev_code == -1) {           /* 1st code after clearcode */
            *b++ = code;               /* Add to image */
            first = prev_code = code;
        } else {                                /* We've got a normal code */
            if (code >= free_code) {            /* It's a new code */
                stack[sp++] = first;
                first = prev_code;
            } else                              /* It's an existing code */
                first = code;
            while (first >= clear_code) {       /* Push string of pixels */
                stack[sp++] = extnsn[first];
                first = prefix[first];
            }
            stack[sp++] = first;                /* Push string's root code */
            while (sp != 0)                     /* Now add pixels to image */
                *b++ = stack[--sp];
            prefix[free_code] = prev_code;
            extnsn[free_code++] = first;
            prev_code = code;

            /* Check if code_size needs to increase */
            if (free_code > code_mask && code_size != MAX_LZW_BITS) {
                code_size++;
                code_mask = (1 << code_size) - 1;
            }
        }
    }
    return TRUE;
}


PUBLIC int PIX_LoadGIF(const char* file, unsigned char *outpix, unsigned char * pal, int* width, int* height, bool Fix)
{
  FILE *fp = NULL;
  int i;
  byte buf[256];
  bool hascolortable;
  int  colortablesize;
  int  is_interlaced;
  int  ireturn;

  fp = JCLIB_Open(file);
  if (fp == NULL)
      goto FAIL;

  fread(buf, 6, 1, fp);
    if (!memcmp(buf,"GIF87a", 6) && !memcmp(buf, "GIF89a", 6)) goto FAIL;

  fread(buf, 7, 1, fp);

  hascolortable = (buf[4] & 0x80) == 0x80;
  colortablesize = 2 << (buf[4] & 0x07);

  assert(hascolortable && (colortablesize == 256 || colortablesize == 128));

  fread(pal, colortablesize*3, 1, fp);

  if (!Fix)
  {
    for (i = 0 ; i < colortablesize*3 ; i++) pal[i]>>=2;
  }
  else
  {
    for (i = 0 ; i < colortablesize*3 ; i++) pal[i];
  }


  /* Extensions */
  if (! fread(buf, 1, 1, fp)) goto FAIL;
  while (buf[0] == 0x21) {                    /* It's an extension */
      if (! fread(buf, 1, 1, fp)) goto FAIL; /* Label */
      switch (buf[0]) {
      case 0xf9:                              /* Graphic Control Extension */
          if (! fread(buf, 6, 1, fp)) goto FAIL;
          //b->transparent = buf[4];            /* Index in colortable */
          break;
      default:                                /* Other extension */
          if (! fread(buf, 1, 1, fp)) goto FAIL;
          while (buf[0] != 0) {
              if (! fread(buf, buf[0], 1, fp)) goto FAIL;
              if (! fread(buf, 1, 1, fp)) goto FAIL;
          }
      }
      if (! fread(buf, 1, 1, fp)) goto FAIL;
  }

  /* Image Descriptor */
  if (buf[0] != 0x2c) goto FAIL;       /* Check Image Seperator */
  if (! fread(buf, 9, 1, fp)) goto FAIL; /* Image descriptor */
  if (width) *width = ((int)buf[5] * 256) | (int)buf[4];
  if (height) *height = ((int)buf[7] * 256) | (int)buf[6];
  hascolortable = (buf[8] & 0x80) == 0x80;
  is_interlaced = (buf[8] & 0x40) == 0x40;
  colortablesize = 2 << (buf[8] & 0x07);

  assert(!hascolortable);

  /* Local Color Table */
  if (hascolortable)
      goto FAIL;

  

  ireturn = decodeLZW(fp, outpix);

  JCLIB_Close(fp);

  assert(ireturn == TRUE);

  return ireturn;

FAIL:
  assert(0);
  return FALSE;
}

// =======================================================
// TGA

#pragma pack(1)

typedef struct {
    byte idsize;
    byte cmapt;
    byte imgtype;
    word cmstart;
    word cmlen;
    byte cmdepth;
    word xoffs;
    word yoffs;
    word width;
    word height;
    byte bpp;
    byte imgd;
} TGAHeader;

typedef struct {
    dword extoffs;
    dword devoffs;
    char sig[18];
} TGAFooter;

#pragma pack()

PUBLIC int PIX_LoadTGA(const char* file, unsigned char *outpix, unsigned char * pal, int* width, int* height)
{
    FILE *fp = NULL;
    long pos;
    TGAHeader hdr;
    TGAFooter foot;
    int i;

    fp = JCLIB_Open(file);
    if (fp == NULL)
        goto FAIL;
    pos = ftell(fp);

    fseek(fp, pos + JCLIB_FileSize(file) - sizeof(foot), SEEK_SET);
    memset(&foot, 0, sizeof(foot));
    fread(&foot, sizeof(foot), 1, fp);
    if (memcmp(foot.sig, "TRUEVISION-XFILE.", sizeof(foot.sig)) != 0)
        goto FAIL;

    fseek(fp, pos, SEEK_SET);
    memset(&hdr, 0, sizeof(hdr));
    fread(&hdr, sizeof(hdr), 1, fp);

    if (width != NULL)
        *width = hdr.width;
    if (height != NULL)
        *height = hdr.height;

    if (outpix == NULL && pal == NULL)
        goto ok;

        // Now it should read the file itself. But doesn't.
    if (pal != NULL) {
        if (hdr.cmapt != 1)
            goto FAIL;
        memset(pal, 0, 768);
        fseek(fp, pos + sizeof(hdr) + hdr.idsize, SEEK_SET);
        for (i = 0; i < 256 && i < hdr.cmlen; i++) {
            word  vw;
            switch (hdr.cmdepth) {
                case 15:
                case 16:
                    vw = (uint16) (((dword)((byte)getc(fp))) + (((dword)((byte)getc(fp))) << 8));
                    pal[3*i+0] = (vw & 0x7C00) >> (10-3+2);
                    pal[3*i+1] = (vw & 0x03E0) >> (5-3+2);
                    pal[3*i+2] = (vw & 0x001F) << (3-0-2);
                    break;
                case 24:
                    pal[3*i+2] = getc(fp) >> 2;
                    pal[3*i+1] = getc(fp) >> 2;
                    pal[3*i+0] = getc(fp) >> 2;
                    break;
                case 32:
                    pal[3*i+2] = getc(fp) >> 2;
                    pal[3*i+1] = getc(fp) >> 2;
                    pal[3*i+0] = getc(fp) >> 2;
                    getc(fp);
                    break;
            }
        }
    }
    if (outpix != NULL) {
        long offs;

        if (hdr.imgtype != 1 && hdr.imgtype != 9)
            goto FAIL;
        if (hdr.bpp != 8)
            goto FAIL;
        offs = pos + sizeof(hdr) + hdr.idsize;
        if (hdr.cmapt != 0)
            offs += (hdr.cmdepth+1)/8*hdr.cmlen;
        fseek(fp, offs, SEEK_SET);
        if (hdr.imgtype == 1) {     // Raw
            for (i = 0; i < hdr.height; i++) {
                fread(outpix + (hdr.height - i - 1)*hdr.width, hdr.width, 1, fp);
            }
        } else {                    // RLE
            long size;
            byte *outp;

            for (i = 0; i < hdr.height; i++) {
                size = hdr.width;
                outp = outpix + (hdr.height - i - 1)*hdr.width;
                while (size > 0) {
                    byte p;

                    p = (byte)getc(fp);
                    if (p & 0x80) {
                        byte v;
                        v = getc(fp);
                        p = p & 0x7F;
                        p += 1;
                        memset(outp, v, p);
                    } else {
                        p += 1;
                        fread(outp, p, 1, fp);
                    }
                    outp += p;
                    size -= p;
                }
            }
        }
    }

  ok:
    JCLIB_Close(fp);
    return TRUE;
  FAIL:
    if (fp != NULL)
        JCLIB_Close(fp);
    return FALSE;
}

// =======================================================


PUBLIC int PIX_Load(const char* file, unsigned char *outpix, unsigned char * pal, int* width, int* height, bool Fix) {
  if (!PIX_LoadGIF(file, outpix, pal, width, height, Fix))
    if (!PIX_LoadPCX(file, outpix, pal, width, height))
        if (!PIX_LoadLBM(file, outpix, pal, width, height))
            if (!PIX_LoadTGA(file, outpix, pal, width, height))
              
                return 0;
    return 1;
}

// ---------------------------- PIX.C -----------------------
