extern "C" {
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
 }
#include "objgfx30.h"
#include "ogSprite.h"

static bool
fileExists(const char *file)
{
  FILE *f = fopen(file, "r");
  if (!f)
    return false;
  fclose(f);
  return true;
}

ogSprite::ogSprite(void) {
  image     = NULL;
  pal       = NULL;
  imageSize = 0;
  width     = 0;
  height    = 0;
  bitDepth  = 0;
  RFP       = 0;
  GFP       = 0;
  BFP       = 0;
  AFP       = 0;
  RShift    = 0;
  GShift    = 0;
  BShift    = 0;
  AShift    = 0;
  tColour   = 0;
  return;
} // ogSprite

void
ogSprite::get(ogSurface& SrcObject, int32 x1, int32 y1, int32 x2, int32 y2) {
  ogPixelFmt pixfmt;
  uInt32 xx,yy,xOfs,yOfs;
  uInt32 rx1, ry1, rx2, ry2;
  uInt32 xCount, yCount, count;
  void *p;
  uInt32 bm; // bit multiplier
  uInt32 MaxX, MaxY;

  if (SrcObject.ogAvail()==false) return;

  free(image);
  free(pal);
  
  MaxX = SrcObject.ogGetMaxX();
  MaxY = SrcObject.ogGetMaxY();
  tColour = SrcObject.ogGetTransparentColor();
  
  SrcObject.ogGetPixFmt(pixfmt);

  bitDepth = pixfmt.BPP;
  RFP = pixfmt.redFieldPosition;
  GFP = pixfmt.greenFieldPosition;
  BFP = pixfmt.blueFieldPosition;
  AFP = pixfmt.alphaFieldPosition;
  RShift = 8-pixfmt.redMaskSize;
  GShift = 8-pixfmt.greenMaskSize;
  BShift = 8-pixfmt.blueMaskSize;
  AShift = 8-pixfmt.alphaMaskSize;

  if (bitDepth==8) {
    if (pal==NULL) pal = new ogRGBA[256];
    if (pal==NULL) return;
    for (count=0; count<256; count++)
      SrcObject.ogUnpackRGB(count,pal[count].red, pal[count].green, pal[count].blue);
//    memcpy(pal, SrcObject.pal, sizeof(ogRGBA)*256);
  } // if

  bm = ((bitDepth+7) >> 3);

  if (x1>x2) {
    xx = x1;
    x1 = x2;
    x2 = xx;
  } // if

  if (y1>y2) {
    yy = y1;
    y1 = y2;
    y2 = yy;
  } // if

  xCount = abs(x2-x1)+1;
  yCount = abs(y2-y1)+1;
  width = xCount;
  height = yCount;
  imageSize = xCount*yCount*bm;
  image = malloc(imageSize);
  p = image;

  if ( ((uInt32)x1>MaxX) || ((uInt32)y1>MaxY) ||
       ((uInt32)x2>MaxX) || ((uInt32)y2>MaxY) ) {
    for (count=0; count<xCount*yCount; count++) {
      setPixel(p,tColour);
      (uInt8 *)p+=bm;
    } // for
    p = image;  // reset the pointer;
  } // if

  xOfs = 0;
  yOfs = 0;

  if (y1<0) {
    yCount += y1;
    ry1 = 0;
    yOfs = xCount*abs(y1);
  } else ry1 = y1;

  if (x1<0) {
    xCount += x1;
    rx1 = 0;
    xOfs = abs(x1);
  } else rx1 = x1;

  if (x2>(int32)MaxX) {
    xCount -= MaxX-x2+1;
    rx2 = MaxX;
  } else rx2 = x2;

  if (y2>(int32)MaxY) {
    yCount -= MaxY-y2+1;
    ry2=MaxY;
  } else ry2 = y2;

  xCount *= bm;
//  rx1 *= bm;
  for (yy=0; yy<yCount; yy++) {
    ( (uInt8 *)p ) += xOfs;
    SrcObject.ogCopyLineFrom(rx1,ry1+yy,p,xCount);    
//    memcpy(p,((uInt8 *)SrcObject.Buffer+SrcObject.LineOfs[ry1+yy]+rx1),xCount);
    ( (uInt8 *)p ) += xCount;
  }
  return;
} // ogSprite::get

uInt32
ogSprite::getPixel(void * p) {
  uInt32 result;
  switch (bitDepth) {
  case 8:
    return *(uInt8 *)p;
    break;
  case 15:
  case 16:
    return *(uInt16 *)p;
    break;
  case 24:
    asm(
       "  xor  %%eax, %%eax   \n"  // xor     eax, eax
       "  mov  2(%%edi),%%al  \n"  // mov     al, [edi+2]
       "  shl  $16, %%eax     \n"  // shl     eax, 16
       "  mov  (%%edi), %%ax  \n"  // mov     ax, [edi]
       "  mov  %%eax, %1      \n"  // mov     result, eax
       :
       : "D" (p), "m" (result)
       );
     return result;
     break;
  case 32:
    return *(uInt32 *)p;
    break;
  default:
    return 0;
    break;
  } // switch
} // ogSprite::getPixel

void
ogSprite::setPixel(void * p, uInt32 colour) {
  switch (bitDepth) {
  case 8:
    *(uInt8 *)p=(uInt8)colour;
    break;
  case 15:
  case 16:
    *(uInt16 *)p=(uInt16)colour;
    break;
  case 24:
    asm(
        "  mov  %%ax, (%%edi) \n"  // mov     [edi], ax
        "  shr  $16, %%eax    \n"  // shr     eax, 16
        "  mov  %%al, 2(%%edi)\n"  // mov     [edi+2],al
       :
       : "D" (p), "a" (colour)
       );
    break;
  case 32:
    *(uInt32 *)p=colour;
    break;
  } // switch
  return;
} // ogSprite::setPixel

uInt32
ogSprite::getSize(void) {
 /*
  * getSize
  *
  * returns the size of the image as it would take on disk.  This includes
  * all header related information (width/height, bitdepth, pixel format,
  * etc) along with an extra sizeof(uInt32)
  * for storing the complete size. This allows easy indexing of images,
  * since you can figure out exactly how much the image will take up on
  * disk.  This function computes the size in the exact order it is on disk.
  * If the image is 8bpp, then there is a 768 byte palette stored after the
  * image data along with an extra sizeof(uInt32) for the palette size in
  * bytes.  Currently we store the entire palette, but later I expect we
  * will add the ability to optimize the palette so only used entries are
  * stored.
  *
  * If you were to store a single bitmap in a file, getSize would equal the
  * filesize.
  */
  uInt32 tmpsize;
  char header_ident[4];
  tmpsize = sizeof(header_ident)+
            sizeof(uInt32)+               // total size
            sizeof(width)+sizeof(height)+ // width/height
            sizeof(bitDepth)+             // bitDepth
            sizeof(RFP)+sizeof(GFP)+sizeof(BFP)+sizeof(AFP)+ // field positions
            sizeof(RShift)+sizeof(GShift)+sizeof(BShift)+sizeof(AShift)+ // shifters
            sizeof(tColour)+              // tColour
            sizeof(imageSize)+            // image size in bytes
            imageSize;                    // actual image area in bytes
  if (bitDepth==8) tmpsize += sizeof(uInt32)+sizeof(ogRGBA)*256;
  return tmpsize;
} // ogSprite::getSize

bool
ogSprite::load(const char * filename) {
  return loadFrom(filename,0);
} // ogSprite::load

bool
ogSprite::loadFrom(const char * filename, uInt32 offset) {
  FILE * infile;
  uInt32 lresult, tresult, totSize;
  uInt32 tmpSize;
  char header_ident[4];

  if (fileExists(filename)==false) return false;
  if ((infile = fopen(filename,"rb"))==NULL) return false;
  fseek(infile, offset, SEEK_SET);

  // for now just free up the previous image.  This will be changed
  // later so it doesn't affect the current image (if any) if there
  // is a failure loading

  free(image);
  free(pal);
  image = NULL;
  imageSize = 0;
  pal = NULL;
  
  tresult = 0;   // total bytes we've read in so far
  
  lresult = fread(&header_ident, sizeof(header_ident), 1, infile);
  tresult += lresult*sizeof(header_ident);
  if ((header_ident[0]!='S') ||
      (header_ident[1]!='P') ||
      (header_ident[2]!='R') ||
      (header_ident[3]!=(char)0x1A)) {
    fclose(infile);
    return false;
  }
  
  lresult = fread(&totSize, sizeof(totSize), 1, infile);
  tresult += lresult*sizeof(totSize);
  
  lresult = fread(&width, sizeof(width), 1, infile);
  tresult += lresult*sizeof(width);

  lresult = fread(&height, sizeof(height), 1, infile);
  tresult += lresult*sizeof(height);

  lresult = fread(&bitDepth, sizeof(bitDepth), 1, infile);
  tresult += lresult*sizeof(bitDepth);

  lresult = fread(&RFP, sizeof(RFP), 1, infile);
  tresult += lresult*sizeof(RFP);

  lresult = fread(&GFP, sizeof(GFP), 1, infile);
  tresult += lresult*sizeof(GFP);

  lresult = fread(&BFP, sizeof(BFP), 1, infile);
  tresult += lresult*sizeof(BFP);

  lresult = fread(&AFP, sizeof(AFP), 1, infile);
  tresult += lresult*sizeof(AFP);

  lresult = fread(&RShift, sizeof(RShift), 1, infile);
  tresult += lresult*sizeof(RShift);
  
  lresult = fread(&GShift, sizeof(GShift), 1, infile);
  tresult += lresult*sizeof(GShift);
  
  lresult = fread(&BShift, sizeof(BShift), 1, infile);
  tresult += lresult*sizeof(BShift);
  
  lresult = fread(&AShift, sizeof(AShift), 1, infile);
  tresult += lresult*sizeof(AShift);

  lresult = fread(&imageSize, sizeof(imageSize), 1, infile);
  tresult += lresult*sizeof(imageSize);
  
  image = malloc(imageSize);
  if (image== NULL) {
    fclose(infile);
    return false;
  }

  // I suppose we could interchange the imageSize and record count to produce
  // the number of bytes we read it... we'll try it this way for now.
  lresult = fread(image, imageSize, 1, infile);
  tresult += lresult*imageSize;
  
  if (bitDepth==8) {
    // 8bpp sprites have palettes
    if (pal==NULL) pal = new ogRGBA[256];
    if (pal==NULL) {
      fclose(infile);
      return false;
    } // if pal==NULL
    lresult = fread(&tmpSize, sizeof(tmpSize), 1, infile);
    tresult += lresult*sizeof(tmpSize);
    if (tmpSize>sizeof(ogRGBA)*256) {
      fclose(infile);
      return false;
    }
    lresult = fread(pal, tmpSize, 1, infile);
    tresult += lresult*tmpSize;
  } // if bitDepth == 8

  fclose(infile);
  return (tresult == totSize);
} // ogSprite::loadFrom;

void
ogSprite::put(ogSurface& DestObject, int32 x, int32 y) {
  uInt32 xx, yy, bm;
  int32 xCount, yCount;
  uInt32 yOfs;
  uInt32 xLeft, xRight;
  int32 MaxX, MaxY;
  void * p;
  uInt8  r,g,b;
  ogPixelFmt pixfmt;

  if (image==NULL) return;
  if (DestObject.ogAvail()==false) return;

  MaxX = DestObject.ogGetMaxX();
  MaxY = DestObject.ogGetMaxY();

  xCount = width;
  yCount = height;
  bm = (bitDepth+7) >> 3;

  // check to see if the image is totally off the screen
  if ((x+xCount<0) || (y+yCount<0) || (x>(int32)MaxX) || (y>(int32)MaxY)) return;

  p = image;
  if (y<0) {
    yOfs = abs(y)*xCount*bm;
    yCount+=y;
    y=0;
  } else yOfs=0;

  if (x<0) {
    xLeft = abs(x)*bm;
    xCount += x;
    x = 0;
  } else xLeft = 0;

  if (x+xCount > MaxX) {
    xRight = (xCount - (MaxX-x+1))*bm;
    xCount = (MaxX-x)+1;
  } else xRight = 0;

  if ((y+yCount)>MaxY) yCount = (MaxY-y)+1;

  DestObject.ogGetPixFmt(pixfmt);
  
  (uInt8 *)p += yOfs;
  
  if ((pixfmt.BPP!=bitDepth) ||
      (pixfmt.redFieldPosition!=RFP) ||
      (pixfmt.greenFieldPosition!=GFP) ||
      (pixfmt.blueFieldPosition!=BFP)) {
    for (yy=0; yy<(uInt32)yCount; yy++) {
      (uInt8 *)p += xLeft;
      for (xx=0; xx<(uInt32)xCount; xx++) {
        unpackRGB(getPixel(p),r,g,b);
        (uInt8 *)p += bm;
        DestObject.ogSetPixel(x+xx,y+yy,DestObject.ogRGB(r,g,b));
      } // for
      (uInt8 *)p += xRight;
    } // for
  } // if
  else {                            // pixel formats match
    xCount *= bm;
//    x *= (pixfmt.BPP+7) >> 3;
    for (yy=0; yy<(uInt32)yCount; yy++) {
      (uInt8 *)p += xLeft;
      DestObject.ogCopyLineTo(x,y+yy,p,xCount);
//      memcpy((uInt8 *)DestObject.Buffer+DestObject.LineOfs[y+yy]+x,p,xCount);
      (uInt8 *)p += xCount;
      (uInt8 *)p += xRight;
    } // for
  } // else
  return;
} // ogSurface::put

bool
ogSprite::save(const char * filename) {
  return saveTo(filename,0);
} // ogSprite::save

bool
ogSprite::saveTo(const char * filename, int32 offset) {
  /*
   * saveTo
   *
   * saves a bitmap to disk.  If the file doesn't exit then we will create
   * a new one (doing this will ignore any specified offset).  If the file
   * exists, we will seek to the specified offset and place the bitmap there.
   * If offset is -1, then we seek to the end of the file.
   *
   * This function will fail on files larger than 2GB.
   *
   */
  FILE * outfile = NULL;
  char header_ident[4];
  uInt32 tmpSize;
  
  if (image==NULL) return false;
  if ((bitDepth==8) && (pal==NULL)) return false;

  if (fileExists(filename)==false) {        // file doesn't exist
    if ((outfile = fopen(filename,"wb")) == NULL) return false;
  } else {
    // file exists. Now we check to see where we put it
    if (offset==-1) {
      if ((outfile = fopen(filename, "ab")) == NULL) return false;
    } else {
      // we have an existing file and an offset to place the data
      if ((outfile = fopen(filename, "wb")) == NULL) return false;
      if (offset!=0) fseek(outfile, offset, SEEK_SET);
    } // else
  } // else
  header_ident[0] = 'S';
  header_ident[1] = 'P';
  header_ident[2] = 'R';
  header_ident[3] = (char)0x1A;  // EOF marker

  // we store exactly how bit this sucker is inside the header. This includes
  // the header information before it, and the size itself
  tmpSize = getSize();
  fwrite(header_ident, sizeof(header_ident), 1, outfile);

  fwrite(&tmpSize, sizeof(tmpSize), 1, outfile);
  fwrite(&width, sizeof(width), 1, outfile);
  fwrite(&height, sizeof(height), 1, outfile);
  fwrite(&bitDepth, sizeof(bitDepth), 1, outfile);

  fwrite(&RFP, sizeof(RFP), 1, outfile);
  fwrite(&GFP, sizeof(GFP), 1, outfile);
  fwrite(&BFP, sizeof(BFP), 1, outfile);
  fwrite(&AFP, sizeof(AFP), 1, outfile);

  fwrite(&RShift, sizeof(RShift), 1, outfile);
  fwrite(&GShift, sizeof(GShift), 1, outfile);
  fwrite(&BShift, sizeof(BShift), 1, outfile);
  fwrite(&AShift, sizeof(AShift), 1, outfile);

  fwrite(&imageSize, sizeof(imageSize), 1, outfile);
  fwrite(image, imageSize, 1, outfile);

  if (bitDepth == 8) {
    tmpSize = sizeof(ogRGBA)*256;
    fwrite(&tmpSize, sizeof(tmpSize), 1, outfile);
    fwrite(pal, sizeof(ogRGBA), 256, outfile);
  } // if bitDepth == 8

  fclose(outfile);
  return true;
} // ogSprite::saveTo

void
ogSprite::unpackRGB(uInt32 colour, uInt8& red, uInt8& green, uInt8& blue) {
  switch (bitDepth) {
  case 8:
    if (colour>255) colour&=255;
    if (pal==NULL) return;
    red=pal[colour].red;
    green=pal[colour].green;
    blue=pal[colour].blue;
    break;
  case 15:
  case 16:
    red = (uInt8)(colour >> RFP) << RShift;
    green = (uInt8)(colour >> GFP) << GShift;
    blue = (uInt8)(colour >> BFP) << BShift;
    if ((red!=0) && (RShift!=0)) red+=(1 << RShift)-1;
    if ((green!=0) && (GShift!=0)) green+=(1 << GShift)-1;
    if ((blue!=0) && (BShift!=0)) blue+=(1 << BShift)-1;
    break;
  case 24:
  case 32:
    red = (uInt8)(colour >> RFP);
    green = (uInt8)(colour >> GFP);
    blue = (uInt8)(colour >> BFP);
    break;
  default:
    red = 0;
    green = 0;
    blue = 0;
    break;
  } // switch
  return;
} // ogSprite::unpackRGB

ogSprite::~ogSprite(void) {
  free(image);
  delete [] pal;
  image     = NULL;
  pal       = NULL;
  imageSize = 0;
  width     = 0;
  height    = 0;
  bitDepth  = 0;
  RFP       = 0;
  GFP       = 0;
  BFP       = 0;
  AFP       = 0;
  RShift    = 0;
  GShift    = 0;
  BShift    = 0;
  AShift    = 0;
  tColour   = 0;
  return;
} // ogSprite::~ogSprite


