#include "loadbmp.h"
#include "winexception.h"
#include "config.h"
#include "ressources.h"
#include "resstring.h"
#include <fstream>
#include <math.h>

#define PIXEL_WHITE (-1)
#define RASTER_MIN(a, b) ((a) < (b) ? (a) : (b))

CRaster::CRaster(): Width(0), Height(0), BPP(0),
    BytesPerRow(0), Raster(NULL), Palette(NULL), pbmi(NULL),
    _resized(false)
{} 

CRaster::~CRaster()
{
    if(Raster != NULL)  free(Raster);
    if(Palette != NULL) delete[] Palette;
    if(pbmi != NULL)    delete[] pbmi;
}

CRaster::CRaster(const CRaster &rhs): Width(rhs.Width),
    Height(rhs.Height), BPP(rhs.BPP), BytesPerRow(rhs.BytesPerRow),
    _resized(rhs._resized)
{
    int m, n;
    
    // Allocate Raster
    if(!(Raster = (char*) malloc(sizeof(char)*BytesPerRow*Height)))
      throw WinException(ResString(ERROR_MEMORY_ALLOC));
    
    for(n=0; n<Height; n++)
       {
       char *pPixel, *newpPixel;

       pPixel = (char*)Raster + BytesPerRow *
              ((Height > 0) ? Height-n-1 : n);
       newpPixel = (char*)rhs.Raster + BytesPerRow *
                 ((Height > 0) ? n : Height-n-1);

       for(m=0; m<Width*BPP/8; m++)
          *(pPixel+m) = *(newpPixel+m);
       }
    
    // Allocate BITMAPFILEHEADER bmfh
    bmfh.bfType = rhs.bmfh.bfType;
    bmfh.bfSize = rhs.bmfh.bfSize;
    bmfh.bfReserved1 = rhs.bmfh.bfReserved1;
    bmfh.bfReserved2 = rhs.bmfh.bfReserved2;
    bmfh.bfOffBits = rhs.bmfh.bfOffBits;
    
    // Allocate pbmi and Palette if needed
    if(BPP == 24)
      pbmi = (BITMAPINFO*) new char[sizeof(BITMAPINFO)];
    else
      pbmi = (BITMAPINFO*) new char[sizeof(BITMAPINFOHEADER) + (1<<BPP)*sizeof(RGBQUAD)];

    pbmi->bmiHeader.biSize = rhs.pbmi->bmiHeader.biSize;
    pbmi->bmiHeader.biWidth = rhs.pbmi->bmiHeader.biWidth;
    pbmi->bmiHeader.biHeight = rhs.pbmi->bmiHeader.biHeight;
    pbmi->bmiHeader.biPlanes = rhs.pbmi->bmiHeader.biPlanes;
    pbmi->bmiHeader.biBitCount = rhs.pbmi->bmiHeader.biBitCount;
    pbmi->bmiHeader.biCompression = rhs.pbmi->bmiHeader.biCompression;
    pbmi->bmiHeader.biSizeImage = rhs.pbmi->bmiHeader.biSizeImage;
    pbmi->bmiHeader.biXPelsPerMeter = rhs.pbmi->bmiHeader.biXPelsPerMeter;
    pbmi->bmiHeader.biYPelsPerMeter = rhs.pbmi->bmiHeader.biYPelsPerMeter;
    pbmi->bmiHeader.biClrUsed = rhs.pbmi->bmiHeader.biClrUsed;
    pbmi->bmiHeader.biClrImportant = rhs.pbmi->bmiHeader.biClrImportant;

    pbmi->bmiColors[0].rgbBlue     = rhs.pbmi->bmiColors[0].rgbBlue;
    pbmi->bmiColors[0].rgbGreen    = rhs.pbmi->bmiColors[0].rgbGreen;
    pbmi->bmiColors[0].rgbRed      = rhs.pbmi->bmiColors[0].rgbRed;
    pbmi->bmiColors[0].rgbReserved = rhs.pbmi->bmiColors[0].rgbReserved;      
    
    // Palette
    Palette = (RGBQUAD*)((char*)pbmi + sizeof(BITMAPINFOHEADER));    
} 

CRaster &CRaster::operator=(const CRaster &rhs)
{
if(&rhs != this)
  {
    // Allocate Raster if needed  
    if(BytesPerRow != rhs.BytesPerRow || Height != rhs.Height)
      {
      if(rhs.BytesPerRow*rhs.Height != 0)
        {
        char *CopyRaster;
        CopyRaster = (char*) realloc(Raster, sizeof(char)*rhs.BytesPerRow*rhs.Height);
        
        if(!CopyRaster)
          throw WinException(ResString(ERROR_MEMORY_REALLOC));
        else
          Raster = CopyRaster;
        }          
      else
        {
        if(Raster != NULL) free(Raster);
        Raster = NULL;    
        }      
      }
      
    int m, n;
    _resized = rhs._resized;
    Width = rhs.Width;
    Height = rhs.Height;
    BPP = rhs.BPP;
    BytesPerRow = rhs.BytesPerRow;
    
    for(n=0; n<Height; n++)
       {
       char *pPixel, *newpPixel;

       pPixel = (char*)Raster + BytesPerRow *
              ((Height > 0) ? Height-n-1 : n);
       newpPixel = (char*)rhs.Raster + BytesPerRow *
                 ((Height > 0) ? n : Height-n-1);

       for(m=0; m<Width*BPP/8; m++)
          *(pPixel+m) = *(newpPixel+m);
       }
    
    // Allocate BITMAPFILEHEADER bmfh
    bmfh.bfType = rhs.bmfh.bfType;
    bmfh.bfSize = rhs.bmfh.bfSize;
    bmfh.bfReserved1 = rhs.bmfh.bfReserved1;
    bmfh.bfReserved2 = rhs.bmfh.bfReserved2;
    bmfh.bfOffBits = rhs.bmfh.bfOffBits;      
    
    // Allocate pbmi and Palette if needed
    if(BPP == 24)
      pbmi = (BITMAPINFO*) new char[sizeof(BITMAPINFO)];
    else
      pbmi = (BITMAPINFO*) new char[sizeof(BITMAPINFOHEADER)+(1<<BPP)*sizeof(RGBQUAD)];

    pbmi->bmiHeader.biSize = rhs.pbmi->bmiHeader.biSize;
    pbmi->bmiHeader.biWidth = rhs.pbmi->bmiHeader.biWidth;
    pbmi->bmiHeader.biHeight = rhs.pbmi->bmiHeader.biHeight;
    pbmi->bmiHeader.biPlanes = rhs.pbmi->bmiHeader.biPlanes;
    pbmi->bmiHeader.biBitCount = rhs.pbmi->bmiHeader.biBitCount;
    pbmi->bmiHeader.biCompression = rhs.pbmi->bmiHeader.biCompression;
    pbmi->bmiHeader.biSizeImage = rhs.pbmi->bmiHeader.biSizeImage;
    pbmi->bmiHeader.biXPelsPerMeter = rhs.pbmi->bmiHeader.biXPelsPerMeter;
    pbmi->bmiHeader.biYPelsPerMeter = rhs.pbmi->bmiHeader.biYPelsPerMeter;
    pbmi->bmiHeader.biClrUsed = rhs.pbmi->bmiHeader.biClrUsed;
    pbmi->bmiHeader.biClrImportant = rhs.pbmi->bmiHeader.biClrImportant;

    pbmi->bmiColors[0].rgbBlue     = rhs.pbmi->bmiColors[0].rgbBlue;
    pbmi->bmiColors[0].rgbGreen    = rhs.pbmi->bmiColors[0].rgbGreen;
    pbmi->bmiColors[0].rgbRed      = rhs.pbmi->bmiColors[0].rgbRed;
    pbmi->bmiColors[0].rgbReserved = rhs.pbmi->bmiColors[0].rgbReserved;

    // Palette
    Palette = (RGBQUAD*)((char*)pbmi + sizeof(BITMAPINFOHEADER));    
  }
  
    return (*this);      
}   

// CRaster::LoadBMPFile (FileName);
//   - loads a BMP file into a CRaster object
//   * supports non-RLE-compressed files of 1, 2, 4, 8 & 24 bits-per-pixel
int CRaster::LoadBMP(HWND hwnd, const char *szFile)
{
    BITMAPINFOHEADER bmih;
    
    // Open file
    std::ifstream bmpfile(szFile, std::ios::in | std::ios::binary);
    
    if(!bmpfile.is_open())
      throw WinException(ResString(ERROR_READING_FILE));
      
    // Load bitmap fileheader & info header
    bmpfile.read((char*)&bmfh, sizeof(BITMAPFILEHEADER));
    bmpfile.read((char*)&bmih, sizeof(BITMAPINFOHEADER));
    
    if((char)bmfh.bfType != 'B')
      throw WinException(ResString(ERROR_FILE_NO_BITMAP));

    // Assign some short variables:
    BPP = bmih.biBitCount;
    Width = bmih.biWidth;
    Height = (bmih.biHeight > 0) ? bmih.biHeight : -bmih.biHeight;
    BytesPerRow = Width * BPP / 8;
    BytesPerRow += (4 - BytesPerRow%4) % 4; // int alignment
    
    // If BPP aren't 24, load Palette:
    if(BPP == 24) pbmi = (BITMAPINFO*) new char[sizeof(BITMAPINFO)];    
    else
      {
      pbmi = (BITMAPINFO*) new char[sizeof(BITMAPINFOHEADER)+(1<<BPP)*sizeof(RGBQUAD)];
      Palette = (RGBQUAD*) ((char*)pbmi + sizeof(BITMAPINFOHEADER));
      bmpfile.read((char*)Palette, sizeof(RGBQUAD) * (1<<BPP));
      }
      
    pbmi->bmiHeader = bmih;
    
    // Load Raster
    bmpfile.seekg(bmfh.bfOffBits, std::ios::beg);
    if(!(Raster = (char*) malloc(sizeof(char)*BytesPerRow*Height)))
      throw WinException(ResString(ERROR_MEMORY_ALLOC));
    
    // (if height is positive the bmp is bottom-up, read it reversed)
    if(bmih.biHeight > 0)
      for(int n = Height-1; n >= 0; n--)
         bmpfile.read(Raster+BytesPerRow*n, BytesPerRow);
    else bmpfile.read(Raster, BytesPerRow*Height);     
    
    // so, we always have a up-bottom raster (that is negative height for windows):
    pbmi->bmiHeader.biHeight = -Height;
    
    bmpfile.close();
    return SUCCESS;
}

// CRaster::SaveBMP (FileName);
//   - Save a BMP file onto the hard disk
//   * supports non-RLE-compressed files 24 bits-per-pixel
int CRaster::SaveBMP(HWND hwnd, const char *szFile)
{
    // Open File
    std::ofstream bmpfile(szFile, std::ios::out | std::ios::binary);
    
    if(!bmpfile.is_open())
      throw WinException(ResString(ERROR_SAVING_FILE));
      
    // Save bitmap file header et file info header
    bmfh.bfSize = 0;
    bmfh.bfReserved1 = bmfh.bfReserved2 = 0;
    bmfh.bfOffBits = 54 + ((BPP == 24) ? 0 : (1<<BPP)*4);
    
    bmpfile.write((char*)&bmfh, sizeof(bmfh));
    bmpfile.write((char*)&(pbmi->bmiHeader), sizeof(pbmi->bmiHeader));
    
    // Save Raster
    int RowAlignmentInFile = ((4 - ((Width*BPP+7)/8)%4)%4); // Bytes
    
    for(int row = 0; row < Height; row++)
       {
       char *pPixel;
       pPixel = (char*)Raster + BytesPerRow *
              (((pbmi->bmiHeader.biHeight) > 0 && !_resized) ? Height-row-1 : row);
       
       bmpfile.write(pPixel, Width*BPP/8);
       
       for(int m = 0; m < RowAlignmentInFile; m++)
          bmpfile.put((char)0);
       }
       
    bmpfile.close();
    return SUCCESS;
}

// CRaster::EraseBMP (FileName);
void CRaster::EraseBMP()
{
    Width = Height = BPP = BytesPerRow = 0;
    _resized = false;
    
    bmfh.bfType = 0;
    bmfh.bfSize = 0;
    bmfh.bfReserved1 = 0;
    bmfh.bfReserved2 = 0;
    bmfh.bfOffBits = 0;

    if(Raster != NULL) free(Raster);
    if(Palette != NULL) delete[] Palette;
    if(pbmi != NULL) delete[] pbmi;

    Raster = NULL;
    Palette = NULL;
    pbmi = NULL;    
}

// CRaster::GDIPaint (hdc,x,y);
// * Paints Raster to a Windows DC.
int CRaster::GDIPaint(HDC hdc, int x, int y) const
{
    // Paint the image to the device.
    return ::SetDIBitsToDevice(hdc,x,y,Width,Height,0,0,0,Height,(LPVOID)Raster,pbmi,0);    
}

// CRaster::SaveBMP (FileName);
//   - Save a BMP file onto the hard disk
//   * supports non-RLE-compressed files 8 & 24 bits-per-pixel
int CRaster::ResizeBMP(int newWidth, int newHeight)
{
    if(newWidth < 0)
      throw WinException(ResString(ERROR_SCALE_WIDTH));

    if(fabs(Height) != fabs(newHeight) || Width != newWidth)
      {
      bool oldHeightPos = (Height > 0);
      int oldHeight = (Height > 0) ? Height : -Height;
      int oldWidth = Width;
      int oldBytesPerRow = BytesPerRow;

      // Assign some short variables:
      bool HeightPos = (newHeight > 0);
      Height = (newHeight > 0) ? newHeight : -newHeight;
      Width = newWidth;
      BytesPerRow = Width * BPP / 8;
      BytesPerRow += (4-BytesPerRow%4) % 4; // int alignment

      pbmi->bmiHeader.biHeight = Height;
      pbmi->bmiHeader.biWidth = Width;

      // Reallocate Raster
      if(Raster != NULL)
        {
        if(BytesPerRow*Height != 0)
          {
          int m, n;
          char *newRaster = (char*) malloc(sizeof(char)*BytesPerRow*Height);
          
          if(!newRaster)
             throw WinException(ResString(ERROR_MEMORY_ALLOC));
          
          // Fill-in with blanks
          memset(newRaster, (char)PIXEL_WHITE, sizeof(char)*BytesPerRow*Height);
          
          for(n = 0; n < RASTER_MIN(Height, oldHeight); n++)
             {
             char *pPixel, *newpPixel;
             
             pPixel = (char*)Raster + oldBytesPerRow * 
                    (oldHeightPos ? n : oldHeight-n-1);
             newpPixel = (char*)newRaster + BytesPerRow * 
                       (HeightPos ? Height-n-1 : n);
             
             for(m = 0; m < RASTER_MIN(Width, oldWidth)*BPP/8; m++)
                *(newpPixel+m) = *(pPixel+m);
             }
                    
          free(Raster);
          Raster = newRaster;
          _resized = true;
          }
        else // Put Raster to NULL
          {
          if(Raster != NULL) delete[] Raster;
          Raster = NULL;
          return SUCCESS;  
          }
        }
      else // Raster == NULL
        {
        if(BytesPerRow*Height != 0)
          {
          if(!(Raster = (char*) malloc(sizeof(char)*BytesPerRow*Height)))
            throw WinException(ResString(ERROR_MEMORY_ALLOC));
          
          // Fill-in with blanks  
          memset(Raster, (char)PIXEL_WHITE, sizeof(char)*BytesPerRow*Height);        
          }
        }
      }
    
    return SUCCESS;      
}
