/*****************************************************************************
 *
 *  Copyright (C) 2003 Cédric Brégardis <cedric.bregardis@free.fr>
 *
 *  This file is part of BRIQUOLO
 *
 *  BRIQUOLO is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  BRIQUOLO 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 BRIQUOLO; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *****************************************************************************/
#include <png.h>
#include "MOGL_Image.h"
#include <fstream>

using namespace std;

#define PNG_BYTES_TO_CHECK 8

MOGL_Image::MOGL_Image(bool p_AutoriserTrou)
 : _Image(NULL), _AutoriserTrou(p_AutoriserTrou), _R(0), _G(0), _B(0), _TypeImage(NON_DEFINI)
{
  _ModeGestionAlpha=ALPHA_FICHIER;
  _ModeGestionRGB=RGB_FICHIER;
}

MOGL_Image::~MOGL_Image()
{
  if (_Image)
  {
    delete [] _Image;
  }
}

bool MOGL_Image::ChargerBMP(const char * p_NomFichier)
{
  unsigned char octet;
  unsigned int dword;
  MOGL_Struct_Couleur coul;
  int deplacement;

  // On ouvre le fichier
  ifstream Fichier(p_NomFichier,ios::in | ios::binary);
  if (Fichier.fail())
  {     return false;
  }

  // On vérifie l'entete
  if (Fichier.read((char *)&octet,sizeof(octet)).fail())
  {     return false;
  }
  if (octet!='B')
  {     return false;
  }
  if (Fichier.read((char *)&octet,sizeof(octet)).fail())
  {     return false;
  }
  if (octet!='M')
  {     return false;
  }

  // On lit le décalage pour lire l'image
  if (Fichier.seekg(0x000A,ios::beg).fail())
  {     return false;
  }
  dword = _ReadUnsignedInt(Fichier);
  if (Fichier.fail())
  {     return false;
  }
  deplacement=dword;

  // Largeur de l'image
  if (Fichier.seekg(0x0012,ios::beg).fail())
  {     return false;
  }
  dword = _ReadUnsignedInt(Fichier);
  if (Fichier.fail())
  {     return false;
  }
  _TailleX=dword;


  // Hauteur de l'image
  if (Fichier.seekg(0x0016,ios::beg).fail())
  {     return false;
  }
  dword = _ReadUnsignedInt(Fichier);
  if (Fichier.fail())
  {     return false;
  }
  _TailleY=dword;

  // désallocation et allocation de la mémoire
  if (_Image!=NULL)
  {
    delete _Image;
  }

  _Image=new MOGL_Struct_Couleur[_TailleX*_TailleY];


  // On charge l'image
  if (Fichier.seekg(deplacement,ios::beg).fail())
  {
    return false;
  }

  for(unsigned int y=0;y<_TailleY;y++)
  {     
    for(unsigned int x=0;x<_TailleX;x++)
    { 
      if (Fichier.read((char *)&octet,sizeof(octet)).fail())
      {  
        return false;
      }
      coul.b=octet/255.0;
      if (Fichier.read((char *)&octet,sizeof(octet)).fail())
      { 
        return false;
      }
      coul.g=octet/255.0;
      if (Fichier.read((char *)&octet,sizeof(octet)).fail())
      {
        return false;
      }
      coul.r=octet/255.0;
      coul.a=1;
      _TraiterCouleur(coul);
      _Image[y*_TailleX+x]=coul;
    }
  }
  _TypeImage=BMP;
  _NomFichier=p_NomFichier;
  return true;
}

bool MOGL_Image::ChargerPNG(const char * p_NomFichier)
{
  png_byte tabVerif[PNG_BYTES_TO_CHECK];
  png_structp png_ptr;
  png_infop info_ptr;
  int nbLu;
    
  FILE * fichier = fopen(p_NomFichier, "rb");
  if (fichier == NULL)
  {
    // Erreur à l'ouverture du fichier
    return false;
  }
  
  nbLu=fread(tabVerif, 1, PNG_BYTES_TO_CHECK, fichier);
  if (nbLu!=PNG_BYTES_TO_CHECK)
  {
    fclose(fichier);
    return false;
  }
  
  if (png_sig_cmp(tabVerif, (png_size_t)0, PNG_BYTES_TO_CHECK))
  {
    // Ce n'est pas un fichier PNG
    return false;
  }

  // On initialise le png_struct
  png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
  
  if (png_ptr == NULL)
  {
    return false;
  }

  // on alloue la mémoire
  info_ptr = png_create_info_struct(png_ptr);
  if (info_ptr == NULL)
  {
    png_destroy_read_struct(&png_ptr, png_infopp_NULL, png_infopp_NULL);
    return false;
  }

  // On définie le setjmp (le saut effectué en cas d'erreur de la libpng)
  if (setjmp(png_jmpbuf(png_ptr)))
  {
    // On libère la mémoire
    png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
    return false;
  }
        
  // On initialise l'acces au fichier
  png_init_io(png_ptr, fichier);
  png_set_sig_bytes (png_ptr, 8);

  // On lit !
  png_uint_32 w,h;
  
  int bit_depth, interlace_type, compression_type, filter_type;
  int color_type;
  
  
  png_read_info(png_ptr, info_ptr);

  png_get_IHDR(png_ptr, info_ptr, &w, &h, &bit_depth, &color_type, &interlace_type,&compression_type, &filter_type);

/* Set up the data transformations you want.  Note that these are all
 * optional.  Only call them if you want/need them.  Many of the
 * transformations only work on specific types of images, and many
 * are mutually exclusive.
 */

  /* tell libpng to strip 16 bit/color files down to 8 bits/color */
  png_set_strip_16(png_ptr);

  /* Extract multiple pixels with bit depths of 1, 2, and 4 from a single
   * byte into separate bytes (useful for paletted and grayscale images).
   */
  png_set_packing(png_ptr);

  /* Expand paletted colors into true RGB triplets */
  if (color_type == PNG_COLOR_TYPE_PALETTE)
    png_set_palette_to_rgb(png_ptr);
  if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
  {
    png_set_gray_to_rgb(png_ptr);
  }
   
  /* Expand grayscale images to the full 8 bits from 1, 2, or 4 bits/pixel */
  if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
  {
    png_set_gray_1_2_4_to_8(png_ptr);
  }
  
  /* Expand paletted or RGB images with transparency to full alpha channels
   * so the data will be available as RGBA quartets.
   */
  if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
    png_set_tRNS_to_alpha(png_ptr);

  /* Add filler (or alpha) byte (before/after each RGB triplet) */
  png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER);

  /* Optional call to gamma correct and add the background to the palette
   * and update info structure.  REQUIRED if you are expecting libpng to
   * update the palette for you (ie you selected such a transform above).
   */
  png_read_update_info(png_ptr, info_ptr);

  _TailleX=png_get_image_width(png_ptr, info_ptr);
  _TailleY=png_get_image_height(png_ptr, info_ptr);


  /* Allocate the memory to hold the image using the fields of info_ptr. */

  /* The easiest way to read the image: */
  png_bytep * row_pointers=new png_bytep[_TailleY];

  unsigned int row;
  for (row = 0; row < _TailleY; row++)
  {
    row_pointers[row] = (png_bytep) png_malloc(png_ptr, png_get_rowbytes(png_ptr, info_ptr));
  }


  /* Now it's time to read the image.  One of these methods is REQUIRED */
  png_read_image(png_ptr, row_pointers);


  /* read rest of file, and get additional chunks in info_ptr - REQUIRED */
  png_read_end(png_ptr, NULL);

  // désallocation et allocation de la mémoire
  if (_Image!=NULL)
  {
    delete _Image;
  }
  _Image=new MOGL_Struct_Couleur[_TailleX*_TailleY];

  MOGL_Struct_Couleur coul;
  
  png_bytep pointeur;
  
  for(unsigned int y=0; y<_TailleY; y++)
  {
    pointeur=row_pointers[y];
    for(unsigned int x=0; x<_TailleX; x++)
    {
      coul.r=(*pointeur++)/255.0;
      coul.g=(*pointeur++)/255.0;
      coul.b=(*pointeur++)/255.0;
      coul.a=(*pointeur++)/255.0;
      _TraiterCouleur(coul);
      _Image[(_TailleY-y-1)*_TailleX+x]=coul;
    }
  }
  for (row = 0; row < _TailleY; row++)
  {
    png_free(png_ptr, row_pointers[row]);
  }

  png_destroy_read_struct(&png_ptr, &info_ptr, NULL);

  fclose(fichier);
  delete [] row_pointers;
  _TypeImage=PNG;
  _NomFichier=p_NomFichier;
  return true;
}

void MOGL_Image::_TraiterCouleur(MOGL_Struct_Couleur & p_Couleur)
{
  if(_AutoriserTrou && p_Couleur.r==_R && p_Couleur.g==_G && p_Couleur.b==_B)
  {
    p_Couleur.a=0;
  }
  else
  {
    switch (_ModeGestionAlpha)
    {
                  
      case ALPHA_UN:
      {
        p_Couleur.a=1;
        break;
      }
      case ALPHA_FICHIER:
      {
        // On n'y touche pas;
        break;
      }
      case ALPHA_MOYENNE_RGB :
      {
        p_Couleur.a=(p_Couleur.r+p_Couleur.g+p_Couleur.b)/3;
        break;
      }
    }
  }
  switch (_ModeGestionRGB)
  {
    case RGB_UN:
    {
      p_Couleur.r=p_Couleur.g=p_Couleur.b=1;
      break;
    }
    case RGB_FICHIER:
    {
      // On laisse tel quel
      break;
    }
    case RGB_MOYENNE_RGB :
    {
      p_Couleur.r=p_Couleur.g=p_Couleur.b=(p_Couleur.r+p_Couleur.g+p_Couleur.b)/3;
      break;
    }
  }
}


MOGL_Struct_Couleur & MOGL_Image::GetPixel(unsigned int p_PositionX, unsigned int p_PositionY) const
{
  return _Image[p_PositionY*_TailleX+p_PositionX];
}

void MOGL_Image::SetCouleurTrou(float p_R, float p_G, float p_B)
{
  _R=p_R;
  _G=p_G;
  _B=p_B;
}

void MOGL_Image::SetCouleurTrouOctet(unsigned char p_R, unsigned char p_G, unsigned char p_B)
{
  _R=p_R/255.0;
  _G=p_G/255.0;
  _B=p_B/255.0;
}

void MOGL_Image::SetGestionModeAlpha(MOGL_Enum_GestionAlpha p_ModeAlpha)
{
  _ModeGestionAlpha=p_ModeAlpha;
}

void MOGL_Image::SetGestionModeRGB(MOGL_Enum_GestionRGB p_ModeRGB)
{
  _ModeGestionRGB=p_ModeRGB;
}

bool MOGL_Image::GetAutoriserTrou() const
{
  return _AutoriserTrou;
}

bool MOGL_Image::Recharger()
{
  switch (_TypeImage)
  {
    case BMP:
    {
      return ChargerBMP(_NomFichier.c_str());
      break;
    }
    case PNG:
    {
      return ChargerPNG(_NomFichier.c_str());
      break;
    }
    default :
    {
      return false;
    }
  }
}

unsigned int MOGL_Image::_ReadUnsignedInt(ifstream & file)
{
    unsigned int val;
    file.read((char *) &val, sizeof (val));
#if SDL_BYTEORDER == SDL_LIL_ENDIAN
    return val;
#else
    SDL_Swap32(val);
    return val;
#endif
}
