#include <fstream>
#include "Multithread.h"
#include "CImage.h"


CImage::CImage() :
  m_Width(0)  ,
  m_Height(0) ,
  m_pPixels(NULL),
  m_Size(0)
{
}

CImage::CImage(int w, int h) :
  m_Width(w),
  m_Height(h),
  m_pPixels(new CColor[w*h]),
  m_Size(w*h)
{
  POP_ASSERT(m_Width > 0, "Invalid width");
  POP_ASSERT(m_Height > 0, "Invalid height");
}





// The following structure must be packed that way.
#pragma pack( 1 )

// This structure is one of the bmp headers.
struct bmpinfoheader
{
  unsigned long biSize;
  long biWidth;
  long biHeight;
  unsigned short biPlanes;
  unsigned short biBitCount;
  unsigned long biCompression;
  unsigned long biSizeImage;
  long biXPelsPerMeter;
  long biYPelsPerMeter;
  unsigned long biClrUsed;
  unsigned long biClrImportant;
};

// This structure is another bmp header.
struct bmpfileheader
{
  unsigned short bfType;
  unsigned long bfSize;
  unsigned short bfReserved1;
  unsigned short bfReserved2;
  unsigned long bfOffBits;
};

#pragma pack( )




// It uses the BMP file format.  It returns -1 if the file couldn't
// be opened and -2 if an error occured while writing to the file.
int CImage::SaveBMP(const std::string& sFileName) const
{
  POP_ASSERT(m_Width > 0, "Invalid width");
  POP_ASSERT(m_Height > 0, "Invalid height");
  POP_ASSERT(m_pPixels.get(), "NULL pixels pointer");

  bmpinfoheader bmih;  // Structure containing a header
  bmpfileheader bmfh;  // Structure containing a header

  unsigned int extrabytes, bytesize; 

  // Used to add bytes so that width is a multiple of 4
  extrabytes = (4 - (m_Width * 3) % 4) % 4;

  // Size of the image
  bytesize = (m_Width * 3 + extrabytes) * m_Height;

  // Info needed by BMP header
  bmfh.bfType = ('M' << 8) + 'B';
  bmfh.bfSize = 0;
  bmfh.bfReserved1 = 0;
  bmfh.bfReserved2 = 0;
  bmfh.bfOffBits = sizeof(bmpfileheader) + sizeof(bmpinfoheader);

  bmih.biSize = sizeof(bmpinfoheader);
  bmih.biWidth = m_Width;
  bmih.biHeight = m_Height;
  bmih.biPlanes = 1; // Must be that
  bmih.biBitCount = 24; // 3 * sizeof(char), in bits
  bmih.biCompression = 0L; // Correspond to BI_RGB
  bmih.biSizeImage = bytesize;
  bmih.biXPelsPerMeter = 0;
  bmih.biYPelsPerMeter = 0;
  bmih.biClrUsed = 0;
  bmih.biClrImportant = 0;


  // File writing
  std::ofstream outFile;

  outFile.open(sFileName.c_str(), std::ios::binary);
  if (outFile.fail())
    {
      return -1;
    }

  // Writing of the headers
  outFile.write( (char *) &bmfh, sizeof(bmpfileheader));
  outFile.write( (char *) &bmih, sizeof(bmpinfoheader));


  // We allocate memory for a phony pointer to complete line so they
  // hold a multiple of 4 bytes
  auto_array_ptr<char> pTemp(new char[extrabytes]);

  char tmpColor[3];

  // (b, g, r) colors are written line by line, from the lower left corner
  // to the upper right corner

  for (int j = m_Height; j > 0; --j)
    {
      for (int i = 0; i < m_Width; ++i)
        {
          tmpColor[0] = char (255 * (*this)(i, j-1).blue());
          tmpColor[1] = char (255 * (*this)(i, j-1).green());
          tmpColor[2] = char (255 * (*this)(i, j-1).red());

          outFile.write( (char *) &tmpColor, 3*sizeof(char));
        }
      outFile.write( (char *) &pTemp, extrabytes*sizeof(char));
    }

  if (outFile.fail())
    return -2;

  outFile.close();

  return 0;
}




// Allocate memory for an image
void CImage::Allocate(int w, int h)
{
  int newSize = w*h;
  if (m_Size < newSize)
    {
      m_pPixels.reset(new CColor[newSize]);
      m_Size = newSize;
    }

  m_Width  = w;
  m_Height = h;
}




// It uses the BMP file format.  It returns -1 if the file couldn't
// be opened and -2 if an error occured while reading to the file.
int CImage::LoadBMP(const std::string &sFileName)
{
  m_Width = 0;
  m_Height = 0;

  bmpfileheader bmfh;
  bmpinfoheader bmih;
  std::ifstream inFile;
  int i, j;
  unsigned char tmpColor;

  // Test file opening
  inFile.open(sFileName.c_str(), std::ios::binary);
  if (inFile.fail())
    return -1;

  // Read file headers
  inFile.read( (char *) &bmfh, sizeof(bmpfileheader));
  inFile.read( (char *) &bmih, sizeof(bmpinfoheader));

  // Check file type
  if (bmfh.bfType != ('M' << 8) + 'B')
    {
      inFile.close();
      return -2;
    }

  Allocate(bmih.biWidth, bmih.biHeight);

  // Check for color table
  if (bmih.biBitCount == 24)
    {
      for (j = bmih.biHeight; j > 0; --j)
        {
          for (i = 0; i < bmih.biWidth; ++i)
            {
              inFile.read( (char *) &tmpColor, sizeof(char));
              (*this)(i, (j-1)).blue(tmpColor / 255.0f);
              inFile.read( (char *) &tmpColor, sizeof(char));
              (*this)(i, (j-1)).green(tmpColor / 255.0f);
              inFile.read( (char *) &tmpColor, sizeof(char));
              (*this)(i, (j-1)).red(tmpColor / 255.0f);
            }

          // Skip end of line offset
          inFile.seekg( (4 - ((bmih.biWidth * 3) % 4)) % 4, std::ios::cur);
        }
    }
  else if (bmih.biBitCount == 32)
    {
      for (j = bmih.biHeight; j > 0; --j)
        {
          for (i = 0; i < bmih.biWidth; ++i)
            {
              inFile.read( (char *) &tmpColor, sizeof(char));
              (*this)(i, (j-1)).blue(tmpColor / 255.0f);
              inFile.read( (char *) &tmpColor, sizeof(char));
              (*this)(i, (j-1)).green(tmpColor / 255.0f);
              inFile.read( (char *) &tmpColor, sizeof(char));
              (*this)(i, (j-1)).red(tmpColor / 255.0f);

              inFile.seekg(sizeof(char), std::ios::cur);
            }
        }
    }
  else // There is a color table
    {
      // Color count is 2^(BitPerPixel) if the other value is zero
      int nbColors = bmih.biClrUsed ? bmih.biClrUsed : (1 << bmih.biBitCount);

      auto_array_ptr<unsigned char> colorTable(new unsigned char[4*nbColors]);

      // Reading color table
      for (i = 0; i < nbColors; ++i)
        {
          inFile.read( (char *) &colorTable[4*i + 2], sizeof(char));
          inFile.read( (char *) &colorTable[4*i + 1], sizeof(char));
          inFile.read( (char *) &colorTable[4*i + 0], sizeof(char));
          inFile.read( (char *) &colorTable[4*i + 3], sizeof(char));
        }

      // Check for compression
      if (!bmih.biCompression) // No compression
        {
          switch (bmih.biBitCount)
            {
            case (1):
              for (j = m_Height; j > 0; --j)
                {
                  for (i = 0; i < m_Width; ++i)
                    {
                      if (!(i % 8))
                        inFile.read( (char *) &tmpColor, sizeof(char));

                      int index = 4*((tmpColor>>(7-(i%8)))&1);
                      (*this)(i, j-1).red(colorTable[index+0]/255.0f);
                      (*this)(i, j-1).green(colorTable[index+1]/255.0f);
                      (*this)(i, j-1).blue(colorTable[index+2]/255.0f);
                    }

                  inFile.seekg( (4 - ((m_Width + 7) / 8) % 4) % 4,
                                std::ios::cur);
                }
              break;

            case (4):
              for (j = m_Height; j > 0; --j)
                {
                  for (i = 0; i < m_Width; ++i)
                    {
                      int index;
                      if (!(i % 2))
                        {
                          inFile.read( (char *) &tmpColor, sizeof(char));
                          index = 4*((tmpColor>>4)&15);
                        }
                      else
                        {
                          index = 4*(tmpColor & 15);
                        }

                      (*this)(i, j-1).red  (colorTable[index+0]/255.0f);
                      (*this)(i, j-1).green(colorTable[index+1]/255.0f);
                      (*this)(i, j-1).blue (colorTable[index+2]/255.0f);
                    }

                  inFile.seekg( (4 - ((m_Width + 1) / 2) % 4) % 4,
                                std::ios::cur);
                }
              break;

            case (8):
              for (j = m_Height; j > 0; --j)
                {
                  for (i = 0; i < m_Width; ++i)
                    {
                      inFile.read( (char *) &tmpColor, sizeof(char));

                      int index = 4*tmpColor;
                      (*this)(i, j-1).red(colorTable[index+0]/255.0f);
                      (*this)(i, j-1).green(colorTable[index+1]/255.0f);
                      (*this)(i, j-1).blue(colorTable[index+2]/255.0f);
                    }
                  
                  inFile.seekg((4 - (m_Width % 4)) % 4, std::ios::cur);
                }
              break;

            default:
              return -3;
            }
        }
      else // S'il y a de la compression
        return -4;
    }

  return 0;
}




// Show the image in a window
void CImage::Show(bool wait) const
{
  if (wait)
    {
      ExecuteShow();
    }
  else
    {
      THREAD thread = CreateNewThread(ShowProcedure,
                                      const_cast<CImage *> (this));
      DetachThread(thread);
    }
}




// Thread function to show a window and continue
void * CImage::ShowProcedure(void *data)
{
  CImage* pImage = (CImage *)data;
  pImage->ExecuteShow();
  return NULL;
}
