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

#include <amscimglib4/amscimglib4.h>
#include <amscimglib4/amscimglib4_intl.h>

#ifdef __cplusplus
extern "C" {  
#endif

typedef struct amscimglib4_bitmapfileheader
{
  unsigned char magic[2]; //2 bytes 'BM', 'BA', 'CI', 'CP', 'IC', or 'PT'
  unsigned int bmpsize;   //4 bytes (32 bit)
  unsigned short res1, res2; //2 and 2 bytes
  unsigned int pixeloff; //4 bytes - starting address where pixel array can be found
} amscimglib4_bitmapfileheader;

typedef struct amscimglib4_bitmapinfoheader
{
  //conventional NT version windows bitmap info header
  unsigned int headersize; //4 bytes, offset 14 (40 bytes for NT BITMAPINFOHEADER)
  int width;                //4. offset 18 - (signed! can be negative)
  int height;               //4 offset 22
  unsigned short colorplanes; //2 bytes = must be 1
  unsigned short bitsperpixel; //2 bytes {1, 4, 8, 16, 24, and 32}
  unsigned int compressionmethod; //4 bytes
      //0 - BI_RGB - the only one I'll be supporting...
      //1 - BI_RLE8
      //2 - BI_RLE4
      //3 - BI_BITFIELDS
      //4 - BI_JPEG
      //5 - BI_PNG
      //6 - BI_ALPHABITFIELDS
      //7 - BI_CMYK
      //8 - BI_CMYKRLE8
      //9 - BI_CMYKRLE4
  unsigned int imsz; //4 - image size. dummy of 0 given for BI_RGB bitmaps
  int hrez;           //4 - horizontal resolution (pixel per meter) [I'll use a default of 3780 for each]
  int vrez;           //4 - vertical resolution of image (pixel per meter, signed int)
  unsigned int colpallette; //4 - 0, or default to 2^N
  unsigned int ignore; //4 not really used
} amscimglib4_bitmapinfoheader;

//bits for bitmap pixels are stored in packed rows. The size of each row is rounded up to a multiple of 4 bytes.
//rowsize in bytes: rowsize = floor((bitsperpixel*imagewidth+31)/32)*4;

void amscimglib4_printheader(amscimglib4_bitmapfileheader *filehead, amscimglib4_bitmapinfoheader *infohead)
{
  printf("magic: %c%c\n", filehead->magic[0],filehead->magic[1]);
  printf("bmpsize: %d\n", filehead->bmpsize);
  printf("res1: %d\n", filehead->res1);
  printf("res2: %d\n", filehead->res2);
  printf("pixel offset: %d\n", filehead->pixeloff);
  printf("\n");
  printf("header size: %d\n", infohead->headersize);
  printf("width: %d\n", infohead->width);
  printf("height: %d\n", infohead->height);
  printf("colorplanes: %d\n", infohead->colorplanes);
  printf("bitsperpixel: %d\n", infohead->bitsperpixel);
  printf("compressionmethod: %d\n", infohead->compressionmethod);
  printf("imsz: %d\n", infohead->imsz);
  printf("horizontal rez: %d\n",infohead->hrez);
  printf("vertical rez: %d\n",infohead->vrez);
  printf("color pallette: %d\n",infohead->colpallette);
  printf("ignore: %d\n", infohead->ignore);

  return;
}


AMSCIMGLIB4_API void amscimglib4_readimage_bmp(const char *fname, amscimglib4_image *img)
{
  FILE *fp = NULL;
  int I,J;
  unsigned int sz;

  amscimglib4_bitmapfileheader bmpfilehead;
  amscimglib4_bitmapinfoheader bmpinfohead;

  fp = fopen(fname,"rb");
  sz = amscimglib4_filesize(fp);
  if(fp==NULL)
  {
    printf("amscimglib4_readimage_bmp: %s cannot be opened!\n",fname);
  }
  else
  {
    fread(bmpfilehead.magic, 2, 1, fp);
    if(!(bmpfilehead.magic[0]=='B' && bmpfilehead.magic[1]=='M') || sz<54)
    {
      printf("amscimglib4_readimage_bmp: %s has file-signature %c%c. Not a bitmap (BM)...\n",fname,bmpfilehead.magic[0],bmpfilehead.magic[1]);
    }
    else
    {
      fread(&(bmpfilehead.bmpsize),4,1,fp);
      fread(&(bmpfilehead.res1),2,1,fp);
      fread(&(bmpfilehead.res2),2,1,fp);
      fread(&(bmpfilehead.pixeloff),4,1,fp);

      fread(&(bmpinfohead.headersize),4,1,fp);
      fread(&(bmpinfohead.width),4,1,fp);
      fread(&(bmpinfohead.height),4,1,fp);
      fread(&(bmpinfohead.colorplanes),2,1,fp);
      fread(&(bmpinfohead.bitsperpixel),2,1,fp);
      fread(&(bmpinfohead.compressionmethod),4,1,fp);
      fread(&(bmpinfohead.imsz),4,1,fp);
      fread(&(bmpinfohead.hrez),4,1,fp);
      fread(&(bmpinfohead.vrez),4,1,fp);
      fread(&(bmpinfohead.colpallette),4,1,fp);
      fread(&(bmpinfohead.ignore),4,1,fp);

      //_DEBUG
      //amscimglib4_printheader(&bmpfilehead,&bmpinfohead); //show bitmap information

      if(bmpinfohead.colorplanes!=1)
      {
        printf("amscimglib4_readimage_bmp: error! non-standard colorplane number %d\n",bmpinfohead.colorplanes);
      }
      else
      {
        if(bmpinfohead.compressionmethod != 0)
        {
          printf("amscimglib4_readimage_bmp: %s is not an RGB bitmap. amsimglib currently only handles RGB bitmaps...\n",fname);
        }
        else
        {
          //begin loading the image
          unsigned char *buffer = NULL;

          int rowsize;
          unsigned int bpp = bmpinfohead.bitsperpixel;
          unsigned char R,G,B,A;
          fseek(fp,bmpfilehead.pixeloff,SEEK_SET);
          amscimglib4_image_resize(img,abs(bmpinfohead.width),abs(bmpinfohead.height));

          rowsize = (bpp*abs(bmpinfohead.width)+31)/32;
          rowsize = rowsize*4;

          //printf("rowsize = %d, width = %d\n", rowsize, bmpinfohead.width);

          buffer = (unsigned char *) malloc(rowsize);
          for(I=0;I<img->sizey;I++)
          {
            fread(buffer,rowsize,1,fp);
            //handle the buffer:
            for(J=0;J<img->sizex;J++)
            {
              if(bpp==8)
              {
                R = buffer[J*bpp/8+0];
                G = buffer[J*bpp/8+0];
                B = buffer[J*bpp/8+0];
                A = 255;
              }
              else if(bpp==24)
              { //BGR order
                R = buffer[J*bpp/8+2];
                G = buffer[J*bpp/8+1];
                B = buffer[J*bpp/8+0];
                A = 255;
              }
              else if(bpp==32)
              { //BGRA order? I've never seen one of these in the wild, so I don't know...
                R = buffer[J*bpp/8+2];
                G = buffer[J*bpp/8+1];
                B = buffer[J*bpp/8+0];
                A = buffer[J*bpp/8+3];
              }
              else
              {
                R = 0; G = 0; B = 0; A = 0;
              }
              amscimglib4_set_pixel_RGBA(img,J,I,R,G,B,A);

            }
          }
          free(buffer);

          if(bmpinfohead.width*bmpinfohead.hrez < 0)
            amscimglib4_image_flipx(img);
          if(bmpinfohead.height*bmpinfohead.vrez > 0)
            amscimglib4_image_flipy(img);
        }
      }
    }
    fclose(fp);
  }

  return;
}


AMSCIMGLIB4_API void amscimglib4_writeimage_bmp(const char *fname, amscimglib4_image *img)
{
  FILE *fp = NULL;

  // if(img->sizex>0&&img->sizey>0) //do not write zero size images
  // {

  fp = fopen(fname,"wb");
  if(fp==NULL)
  {
    printf("amscimglib4_writeimage_bmp: %s could not be opened!\n",fname);
  }
  else
  {
    int I,J;
    amscimglib4_bitmapfileheader bmpfilehead;
    amscimglib4_bitmapinfoheader bmpinfohead;
    int bpp = 24;
    int rowsize;
    unsigned char R,B,G,A;

    unsigned char *buffer;

    rowsize = (bpp*img->sizex+31)/32;
    rowsize = rowsize*4;

    if(img->sizex>0&&img->sizey>0)
    {
      bmpfilehead.magic[0] = 'B';
      bmpfilehead.magic[1] = 'M';
      bmpfilehead.bmpsize = 54+rowsize*img->sizey;
      bmpfilehead.res1 = 0;
      bmpfilehead.res2 = 0;
      bmpfilehead.pixeloff = 54;

      fwrite(bmpfilehead.magic,2,1,fp);
      fwrite(&(bmpfilehead.bmpsize),4,1,fp);
      fwrite(&(bmpfilehead.res1),2,1,fp);
      fwrite(&(bmpfilehead.res2),2,1,fp);
      fwrite(&(bmpfilehead.pixeloff),4,1,fp);

      bmpinfohead.headersize = 40;
      fwrite(&(bmpinfohead.headersize),4,1,fp);
      bmpinfohead.width = img->sizex;
      fwrite(&(bmpinfohead.width),4,1,fp);
      bmpinfohead.height = img->sizey;
      fwrite(&(bmpinfohead.height),4,1,fp);
      bmpinfohead.colorplanes = 1;
      fwrite(&(bmpinfohead.colorplanes),2,1,fp);
      bmpinfohead.bitsperpixel = 24;
      fwrite(&(bmpinfohead.bitsperpixel),2,1,fp);
      bmpinfohead.compressionmethod = 0;
      fwrite(&(bmpinfohead.compressionmethod),4,1,fp);
      bmpinfohead.imsz = 0;
      fwrite(&(bmpinfohead.imsz),4,1,fp);
      bmpinfohead.hrez = 3780;
      fwrite(&(bmpinfohead.hrez),4,1,fp);
      bmpinfohead.vrez = 3780;
      fwrite(&(bmpinfohead.vrez),4,1,fp);
      bmpinfohead.colpallette = 0;
      fwrite(&(bmpinfohead.colpallette),4,1,fp);
      bmpinfohead.ignore = 0;
      fwrite(&(bmpinfohead.ignore),4,1,fp);

      buffer = (unsigned char *) malloc(rowsize);
      for(I=0;I<rowsize;I++) buffer[I] = 0;

      //begin writing out the image file
      for(I=0;I<img->sizey;I++)
      {
        for(J=0;J<img->sizex;J++)
        {
          amscimglib4_get_pixel_RGBA(img,J,img->sizey-I-1,&R,&G,&B,&A); //go from bottom to top
          //R = img->data[4*(J + img->sizex*(img->sizey-I-1)) + 0];
          //G = img->data[4*(J + img->sizex*(img->sizey-I-1)) + 1];
          //B = img->data[4*(J + img->sizex*(img->sizey-I-1)) + 2];
          
          buffer[J*bpp/8+0] = B;
          buffer[J*bpp/8+1] = G;
          buffer[J*bpp/8+2] = R;
        }

        fwrite(buffer,rowsize,1,fp);
      }

      free(buffer);
    }

    fclose(fp);
  }

  // } //do not write zero size images

  return;
}

#ifdef __cplusplus
}; //end extern "C"
#endif