/* Copyright (c) 2002-2012 Croteam Ltd. 
This program is free software; you can redistribute it and/or modify
it under the terms of version 2 of the GNU General Public License as published by
the Free Software Foundation


This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */

#include "Engine/StdH.h"

#include <Engine/Graphics/ImageInfo.h>
#include <Engine/Graphics/Color.h>

#include <Engine/Base/Stream.h>
#include <Engine/Base/Memory.h>
#include <Engine/Math/Functions.h>

extern void FlipBitmap( UBYTE *pubSrc, UBYTE *pubDst, PIX pixWidth, PIX pixHeight, INDEX iFlipType, BOOL bAlphaChannel);


// Order of CroTeam true color pixel components
#define COMPONENT_1 red
#define COMPONENT_2 green
#define COMPONENT_3 blue
#define COMPONENT_4 alpha

// and vice versa
#define RED_COMPONENT   0
#define GREEN_COMPONENT 1
#define BLUE_COMPONENT  2
#define ALPHA_COMPONENT 3


// PCX header structure
struct PCXHeader
{
  SBYTE MagicID;
  SBYTE Version;
  SBYTE Encoding;
  SBYTE PixelBits;
  SWORD Xmin, Ymin, Xmax, Ymax;
  SWORD Hres, Vres;
  UBYTE Palette[16*3];
  SBYTE Reserved;
  SBYTE Planes;
  UWORD BytesPerLine;
  SWORD PaletteInfo;
  SWORD	HscreenSize, VscreenSize;
  SBYTE Filler[54];
};

static __forceinline CTStream &operator>>(CTStream &strm, PCXHeader &t) {
  strm>>t.MagicID;
  strm>>t.Version;
  strm>>t.Encoding;
  strm>>t.PixelBits;
  strm>>t.Xmin;
  strm>>t.Ymin;
  strm>>t.Xmax;
  strm>>t.Ymax;
  strm>>t.Hres;
  strm>>t.Vres;
  strm.Read_t(t.Palette, sizeof (t.Palette));
  strm>>t.Reserved;
  strm>>t.Planes;
  strm>>t.BytesPerLine;
  strm>>t.PaletteInfo;
  strm>>t.HscreenSize;
  strm>>t.VscreenSize;
  strm.Read_t(t.Filler, sizeof (t.Filler));
}

static __forceinline CTStream &operator<<(CTStream &strm, const PCXHeader &t) {
  strm<<t.MagicID;
  strm<<t.Version;
  strm<<t.Encoding;
  strm<<t.PixelBits;
  strm<<t.Xmin;
  strm<<t.Ymin;
  strm<<t.Xmax;
  strm<<t.Ymax;
  strm<<t.Hres;
  strm<<t.Vres;
  strm.Write_t(t.Palette, sizeof (t.Palette));
  strm<<t.Reserved;
  strm<<t.Planes;
  strm<<t.BytesPerLine;
  strm<<t.PaletteInfo;
  strm<<t.HscreenSize;
  strm<<t.VscreenSize;
  strm.Write_t(t.Filler, sizeof (t.Filler));
}

// TARGA header structure
struct TGAHeader
{
  UBYTE IdLength;
  UBYTE ColorMapType;
  UBYTE ImageType;
  UBYTE ColorMapSpec[5];
  UWORD Xorigin;
  UWORD Yorigin;
  UWORD	Width;
  UWORD Height;
  UBYTE BitsPerPixel;
  UBYTE Descriptor;
};

static __forceinline CTStream &operator>>(CTStream &strm, TGAHeader &t) {
  strm>>t.IdLength;
  strm>>t.ColorMapType;
  strm>>t.ImageType;
  strm>>t.ColorMapSpec[5];
  strm>>t.Xorigin;
  strm>>t.Yorigin;
  strm>>t.Width;
  strm>>t.Height;
  strm>>t.BitsPerPixel;
  strm>>t.Descriptor;
  return(strm);
}

static __forceinline CTStream &operator<<(CTStream &strm, const TGAHeader &t) {
  strm<<t.IdLength;
  strm<<t.ColorMapType;
  strm<<t.ImageType;
  strm<<t.ColorMapSpec[5];
  strm<<t.Xorigin;
  strm<<t.Yorigin;
  strm<<t.Width;
  strm<<t.Height;
  strm<<t.BitsPerPixel;
  strm<<t.Descriptor;
  return(strm);
}


/******************************************************
 * Routines for manipulating CroTeam picture raw format
 */

CImageInfo::CImageInfo() {
  Detach();
}
CImageInfo::~CImageInfo() {
  Clear();
}

// reads image info raw format from file
void CImageInfo::Read_t( CTStream *inFile)   // throw char *
{
  Clear();

  // read image info header
  inFile->ExpectID_t( CChunkID("CTII"));
  if( inFile->GetSize_t() != 5*4) throw( "Invalid image info file.");

  SLONG tmp;
  *inFile >> tmp;
  ii_Width = (PIX) tmp;
  *inFile >> tmp;
  ii_Height = (PIX) tmp;
  *inFile >> tmp;
  ii_BitsPerPixel = (SLONG) tmp;

  // read image contents (all channels)
  ULONG pic_size = ii_Width*ii_Height * ii_BitsPerPixel/8;
  ii_Picture = (UBYTE*)AllocMemory( pic_size);
  inFile->ReadFullChunk_t( CChunkID("IPIC"), ii_Picture, pic_size);

  #if PLATFORM_BIGENDIAN
  STUBBED("Byte order");
  #endif
}

// writes image info raw format to file
void CImageInfo::Write_t( CTStream *outFile) const  // throw char *
{
  // write image info header
  outFile->WriteID_t( CChunkID("CTII"));
  outFile->WriteSize_t( 5*4);

  *outFile << (PIX)ii_Width;
  *outFile << (PIX)ii_Height;
  *outFile << (SLONG)ii_BitsPerPixel;

  // write image contents (all channels)
  ULONG pic_size = ii_Width*ii_Height * ii_BitsPerPixel/8;
  outFile->WriteFullChunk_t( CChunkID("IPIC"), ii_Picture, pic_size);

  #if PLATFORM_BIGENDIAN
  STUBBED("Byte order");
  #endif
}


// initializes structure members and attaches pointer to image
void CImageInfo::Attach( UBYTE *pPicture, PIX pixWidth, PIX pixHeight, SLONG slBitsPerPixel)
{
  // parameters must be meaningful
  ASSERT( (pPicture != NULL) && (pixWidth>0) && (pixHeight>0));
  ASSERT( (slBitsPerPixel == 24) || (slBitsPerPixel == 32));
  // do it ...
  ii_Picture = pPicture;
  ii_Width   = pixWidth;
  ii_Height  = pixHeight;
  ii_BitsPerPixel = slBitsPerPixel;
}

// clears the content of an image info structure but does not free allocated memory
void CImageInfo::Detach(void)
{
  ii_Picture = NULL;
  ii_Width   = 0;
  ii_Height  = 0;
  ii_BitsPerPixel = 0;
}


// clears the content of an image info structure and frees allocated memory (if any)
void CImageInfo::Clear()
{
  // if allocated, release picture memory
  if( ii_Picture != NULL) FreeMemory( ii_Picture);
  Detach();
}


// expand image edges
void CImageInfo::ExpandEdges( INDEX ctPasses/*=8192*/)
{
  // do nothing if image is too small or doesn't have an alpha channel
  if( ii_Width<3 || ii_Height<3 || ii_BitsPerPixel!=32) return;

  // allocate some memory for spare picture and wipe it clean
  SLONG slSize = ii_Width*ii_Height*ii_BitsPerPixel/8;
  ULONG *pulSrc = (ULONG*)ii_Picture;
  ULONG *pulDst = (ULONG*)AllocMemory(slSize);
  memcpy( pulDst, pulSrc, slSize);

  // loop while there are some more pixels to be processed or for specified number of passes
  for( INDEX iPass=0; iPass<ctPasses; iPass++)
  { 
    BOOL bAllPixelsVisible = TRUE;
    // loop thru rows
    for( PIX pixV=1; pixV<ii_Height-1; pixV++)
    { // loop thru pixels in row
      for( PIX pixU=1; pixU<ii_Width-1; pixU++)
      { // determine pixel location
        const PIX pixOffset = pixV*ii_Width + pixU;
        // do nothing if it is already visible
        COLOR col = ByteSwap(pulSrc[pixOffset]);
        if( ((col&CT_AMASK)>>CT_ASHIFT)>3) continue;
        bAllPixelsVisible = FALSE;
        // average all surrounding pixels that are visible
        ULONG ulRa=0, ulGa=0, ulBa=0;
        INDEX ctVisible=0;
        for( INDEX j=-1; j<=1; j++) {
          for( INDEX i=-1; i<=1; i++) {
            const PIX pixSurrOffset = pixOffset + j*ii_Width + i;
            col = ByteSwap(pulSrc[pixSurrOffset]);
            if( ((col&CT_AMASK)>>CT_ASHIFT)<4) continue; // skip non-visible pixels
            UBYTE ubR, ubG, ubB;
            ColorToRGB( col, ubR,ubG,ubB);
            ulRa+=ubR;  ulGa+=ubG;  ulBa += ubB;
            ctVisible++;
          }
        } // if there were some visible pixels around
        if( ctVisible>0) {
          // calc average
          ulRa/=ctVisible;  ulGa/=ctVisible;  ulBa/=ctVisible;
          col = RGBAToColor( ulRa,ulGa,ulBa,255);
          // put it to center pixel
          pulDst[pixOffset] = ByteSwap(col);
        }
      }
    } // copy resulting picture over source
    memcpy( pulSrc, pulDst, slSize);
    // done if all pixels are visible
    if( bAllPixelsVisible) break;
  }
  // free temp memory
  FreeMemory(pulDst);
}



// sets image info structure members with info form file of any supported graphic format
//  (CT RAW, PCX8, PCX24, TGA32 uncompressed), but does not load picture content nor palette
INDEX CImageInfo::GetGfxFileInfo_t( const CTFileName &strFileName) // throw char *
{
  CTFileStream GfxFile;
  TGAHeader TGAhdr;
  PCXHeader PCXhdr;

  // lets assume it's a TGA file
  GfxFile.Open_t( strFileName, CTStream::OM_READ);
  GfxFile>>TGAhdr;
  GfxFile.Close();

  // check for supported targa format
  if( (TGAhdr.ImageType==2 || TGAhdr.ImageType==10) && TGAhdr.BitsPerPixel>=24) {
    // targa it is, so clear image info and set new values
    Clear();
    ii_Width  = TGAhdr.Width;
    ii_Height = TGAhdr.Height;
    ii_BitsPerPixel = TGAhdr.BitsPerPixel;
    // we done here, no need to check further
    return TGA_FILE;
  }

  // we miss Targa, so lets check for supported PCX format
  GfxFile.Open_t( strFileName, CTStream::OM_READ);
  GfxFile>>PCXhdr;
  GfxFile.Close();

  // check for supported PCX format
  if( (PCXhdr.MagicID == 10) && (PCXhdr.PixelBits == 8)) {
    // PCX it is, so clear image info and set new values
    Clear();
    ii_Width = PCXhdr.Xmax - PCXhdr.Xmin + 1;
    ii_Height = PCXhdr.Ymax - PCXhdr.Ymin + 1;
    ii_BitsPerPixel = PCXhdr.PixelBits * PCXhdr.Planes;
    // we done here, no need to check further
    return PCX_FILE;
  }

  // we didn't found a supported gfx format, sorry ...
  return UNSUPPORTED_FILE;
}



/* TGA *********************************************************************************
 * Routines that load and save true color (24 or 32 bit per pixel) uncompressed targa file
 */



void CImageInfo::LoadTGA_t( const CTFileName &strFileName) // throw char *
{
  TGAHeader *pTGAHdr;
  UBYTE *pTGABuffer, *pTGAImage;
  SLONG slFileSize;
  CTFileStream TGAFile;

  Clear();

  // determine file size
  TGAFile.Open_t( strFileName, CTStream::OM_READ);
  slFileSize = TGAFile.GetStreamSize();

  // load entire TGA file to memory, as is, and close it (no further usage)
  pTGABuffer = (UBYTE*)AllocMemory( slFileSize);
STUBBED("Byte swapping TGA data");

  TGAFile.Read_t( pTGABuffer, slFileSize);
  TGAFile.Close();

  // TGA header starts at the begining of the TGA file
  pTGAHdr = (struct TGAHeader*)pTGABuffer;
  // TGA image bytes definition follows up
  pTGAImage = pTGABuffer + sizeof(struct TGAHeader) + pTGAHdr->IdLength;

  // detremine picture size dimensions
  ii_Width        = (SLONG)pTGAHdr->Width;
  ii_Height       = (SLONG)pTGAHdr->Height;
  ii_BitsPerPixel = (SLONG)pTGAHdr->BitsPerPixel;
  SLONG slBytesPerPixel = ii_BitsPerPixel/8;
  PIX pixBitmapSize     = ii_Width*ii_Height;
  BOOL bAlphaChannel    = (slBytesPerPixel==4);

  // check for supported file types
  ASSERT( slBytesPerPixel==3 || slBytesPerPixel==4);
  if( slBytesPerPixel!=3 && slBytesPerPixel!=4) throw( TRANS("Unsupported BitsPerPixel in TGA format."));

  // allocate memory for image content
  ii_Picture = (UBYTE*)AllocMemory( ii_Width*ii_Height *slBytesPerPixel);
  UBYTE *pubSrc = pTGAImage;  // need 'walking' pointers
  UBYTE *pubDst = ii_Picture;

  // determine TGA image type
  if( pTGAHdr->ImageType==10) {
    // RLE encoded
    UBYTE ubControl;
    INDEX iBlockSize;
    BOOL  bRepeat;
    PIX pixCurrentSize=0;
    // loop thru blocks
    while( pixCurrentSize<pixBitmapSize)
    { // readout control byte
      ubControl  = *pubSrc++;
      bRepeat    =  ubControl&0x80;
      iBlockSize = (ubControl&0x7F) +1;
      // repeat or copy color values
      for( INDEX i=0; i<iBlockSize; i++) {
        *pubDst++ = pubSrc[0]; 
        *pubDst++ = pubSrc[1]; 
        *pubDst++ = pubSrc[2]; 
        if( bAlphaChannel) *pubDst++ = pubSrc[3];
        if( !bRepeat) pubSrc += slBytesPerPixel;
      }
      // advance for next block if repeated
      if( bRepeat) pubSrc += slBytesPerPixel;
      // update image size
      pixCurrentSize += iBlockSize;
    }
    // mark that image was encoded to ImageInfo buffer
    pTGAImage = ii_Picture; 
  } 
  // not true-colored?
  else if( pTGAHdr->ImageType!=2) {
    // whoops!
    ASSERTALWAYS("Unsupported TGA format.");
    throw( TRANS("Unsupported TGA format."));
  }

  // determine image flipping
  INDEX iFlipType;
  switch( (pTGAHdr->Descriptor&0x30)>>4) {
  case 0:  iFlipType = 1;  break; // vertical flipping
  case 1:  iFlipType = 3;  break; // diagonal flipping
  case 3:  iFlipType = 2;  break; // horizontal flipping
  default: iFlipType = 0;  break; // no flipping (just copying)
  }
  // do flipping
  FlipBitmap( pTGAImage, ii_Picture, ii_Width, ii_Height, iFlipType, bAlphaChannel);

  // convert TGA pixel format to CroTeam
  pubSrc = ii_Picture;  // need 'walking' pointer again
  for( INDEX iPix=0; iPix<pixBitmapSize; iPix++)
  { // flip bytes
    Swap( pubSrc[0], pubSrc[2]);  // R & B channels
    pubSrc += slBytesPerPixel; 
  }

  // free temorary allocated memory for TGA image format
  FreeMemory( pTGABuffer);
}


// save TGA routine
void CImageInfo::SaveTGA_t( const CTFileName &strFileName) const // throw char *
{
  TGAHeader *pTGAHdr;
  UBYTE *pTGABuffer, *pTGAImage;
  SLONG slFileSize;
  PIX pixBitmapSize = ii_Width*ii_Height;
  CTFileStream TGAFile;

  // determine and check image info format
  SLONG slBytesPerPixel = ii_BitsPerPixel/8;
  ASSERT( slBytesPerPixel==3 || slBytesPerPixel==4);
  if( slBytesPerPixel!=3 && slBytesPerPixel!=4) throw( TRANS( "Unsupported BitsPerPixel in ImageInfo header."));

  // determine TGA file size and allocate memory
  slFileSize = sizeof(struct TGAHeader) + pixBitmapSize *slBytesPerPixel;
  pTGABuffer = (UBYTE*)AllocMemory( slFileSize);
  pTGAHdr    = (struct TGAHeader*)pTGABuffer;
  pTGAImage  = pTGABuffer + sizeof(struct TGAHeader);

  // set TGA picture size dimensions
  memset( pTGABuffer, 0x0, sizeof(struct TGAHeader));
  pTGAHdr->Width        = (UWORD)ii_Width;
  pTGAHdr->Height       = (UWORD)ii_Height;
  pTGAHdr->BitsPerPixel = (UBYTE)ii_BitsPerPixel;
  pTGAHdr->ImageType    = 2;

  // flip image vertically
  BOOL bAlphaChannel = (slBytesPerPixel==4);
  FlipBitmap( ii_Picture, pTGAImage, ii_Width, ii_Height, 1, bAlphaChannel);

  // convert CroTeam's pixel format to TGA format
  UBYTE *pubTmp = pTGAImage;  // need 'walking' pointer
  for( INDEX iPix=0; iPix<pixBitmapSize; iPix++)
  { // flip bytes
    Swap( pubTmp[0], pubTmp[2]);  // R & B channels
    pubTmp += slBytesPerPixel; 
  }

  // save entire TGA memory to file and close it
  TGAFile.Create_t( strFileName);
  TGAFile.Write_t( pTGABuffer, slFileSize);
  TGAFile.Close();

  // free temorary allocated memory for TGA image format
  FreeMemory( pTGABuffer);
}


/* PCX ***********************************************************************
 * This routine reads file with given file name and if it is valid PCX file it
 * loads it into given ImageInfo structure in CroTeam true-color format.
 * (and, if the one exists, loads the palette)
 */
void CImageInfo::LoadPCX_t( const CTFileName &strFileName) // throw char *
{
  PCXHeader *pPCXHdr;
  UBYTE *pPCXBuffer, *pPCXImage, *pPCXDecodedImage, *pTmp;
  UBYTE data, counter;
  SLONG pic_size, PCX_size, slFileSize;
  CTFileStream PCXFile;

  Clear();

  // inconvinent way to determine file size
  PCXFile.Open_t( strFileName, CTStream::OM_READ);
  slFileSize = PCXFile.GetStreamSize();

  // load entire PCX file to memory, as is, and close it (no further usage)
  pPCXBuffer = (UBYTE*)AllocMemory( slFileSize);
STUBBED("Byte swapping PCX data");
  PCXFile.Read_t( pPCXBuffer, slFileSize);
  PCXFile.Close();

  // PCX header starts at the begining of the PCX file
  pPCXHdr = (struct PCXHeader*)pPCXBuffer;
  // PCX image bytes definition follows up
  pPCXImage = pPCXBuffer + sizeof( struct PCXHeader);

  // detremine picture size dimensions
  ii_Width  = (SLONG)(pPCXHdr->Xmax - pPCXHdr->Xmin +1);
  ii_Height = (SLONG)(pPCXHdr->Ymax - pPCXHdr->Ymin +1);
  ii_BitsPerPixel = (SLONG)pPCXHdr->Planes*8;
  pic_size = ii_Width * ii_Height * ii_BitsPerPixel/8;

  // allocate memory for image content
  ii_Picture = (UBYTE*)AllocMemory( pic_size);

  // allocate memory for decoded PCX file that hasn't been converted to CT RAW Image format
  PCX_size = (SLONG)(pPCXHdr->BytesPerLine * ii_Height * ii_BitsPerPixel/8);
  pPCXDecodedImage = (UBYTE*)AllocMemory( PCX_size);
  pTmp = pPCXDecodedImage;  // save pointer for latter usage

  // decode PCX file
  for( INDEX i=0; i<PCX_size; )   // i is incremented by counter value  at the and of the loop
  {
    // read one byte from PCX image in memory
    data = *pPCXImage++;
    // check byte-run mark
    if( (data & 0xC0) == 0xC0) {
      counter = data & 0x3F;              // determine repeat value
      data = *pPCXImage++;                // read repeated data
      // put several bytes of PCX image to decoded image area in memory
      for( INDEX j=0; j<counter; j++)
        *pPCXDecodedImage++ = data;
    } else {
      // put just one byte from PCX image to decoded image area in memory
      counter = 1;
      *pPCXDecodedImage++ = data;
    }

    // increment encoded image counter
    i += counter;
  }
  pPCXDecodedImage = pTmp;  // reset pointer

  // convert decoded PCX image to CroTeam RAW Image Info format
  SLONG slBytesPerPixel = ii_BitsPerPixel/8;
  for( INDEX y=0; y<ii_Height; y++)
  {
    SLONG slYSrcOfs = y * ii_Width * slBytesPerPixel;
    SLONG slYDstOfs = y * pPCXHdr->BytesPerLine * slBytesPerPixel;
    // channel looper
    for( INDEX p=0; p<slBytesPerPixel; p++)
    {
      SLONG slPOffset = p * pPCXHdr->BytesPerLine;
      // byte looper
      for( INDEX x=0; x<ii_Width; x++)
        *(ii_Picture + slYSrcOfs + x*slBytesPerPixel + p) =
        *(pPCXDecodedImage + slYDstOfs + slPOffset + x);
    }
  }

  // free temorary allocated memory for PCX encoded and decoded image
  FreeMemory( pPCXBuffer);
  FreeMemory( pPCXDecodedImage);
}


// check for the supported gfx format file and invokes corresponding routine to load it
void CImageInfo::LoadAnyGfxFormat_t( const CTFileName &strFileName) // throw char *
{
  INDEX iFileFormat = GetGfxFileInfo_t( strFileName);
  if( iFileFormat == PCX_FILE) LoadPCX_t( strFileName);
  if( iFileFormat == TGA_FILE) LoadTGA_t( strFileName);
  if( iFileFormat == UNSUPPORTED_FILE) throw( "Gfx format not supported.");
}