/*   
*   imageio.cpp Copyright (C) 2009-2010 Paolo Medici
*
*   This library is free software; you can redistribute it and/or modify it under the terms of the 
*    GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of 
*    the License, or (at your option) any later version.
*
*   This library 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 Lesser General Public 
*    License for more details.
*
*  You should have received a copy of the GNU Lesser General Public License along with this library; if not, 
*   write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
*/

#include <fstream>
#include <iostream>

/** color format */
enum ColorFormat { 
  AUTO, ///< reserved
  GREY8, ///< 1 BYTE per PIXEL
  RGB24  ///< 3 BYTE per PIXEL
};

/** Image class */
class Image {
  
  /** The image format */
  ColorFormat m_format;
  
  /** The Image Geomtry */
  unsigned int m_width, m_height;
  
  /** Image stride */
  long m_stride;
  
  /** The inner buffer */
  unsigned char *m_buffer;
  
  private:
  
static void pnm_skip_comments(std::istream & i)
{
 while (isspace(i.peek()))
    {
    while (isspace(i.peek()))
        i.get();
    if (i.peek() == '#')
      while (i.peek()!='\r' && i.peek()!='\n')
           { i.get(); }
       }
}

static char pnm_read_header(std::istream &iss, unsigned int & width, unsigned int &height)
{
  char h, t; 
  int max_val;

  // check pnm header
  h = iss.get();
  t = iss.get();
  if(!((h=='P') && ((t=='5')||(t=='6'))))
    return '\0';

  pnm_skip_comments(iss);
  iss>>width;
  pnm_skip_comments(iss);
  iss>>height;
  pnm_skip_comments(iss);
  iss>>max_val;
  iss.get(); // TODO: use a getline fn 
  
  return t;
}

/// private costructor to avoid copy
  Image(const Image & prv) { }

  public:
    
    Image() : m_width(0), m_height(0), m_buffer(0) { }
    
    ~Image()
    {
      delete [] m_buffer;
    }
    
    /// destroy image and realloc buffer
    bool alloc(unsigned int width, unsigned int height, ColorFormat format)
    {
      delete [] m_buffer;
      // buffer size
      m_width = width; m_height = height; m_format = format;
      m_stride = m_width * bytes_per_pixel();
      m_buffer = new unsigned char [m_stride * m_height];
    }
    
    /** return the bytes per pixel */
    inline unsigned int bytes_per_pixel() const { return (m_format==GREY8)?1:(m_format==RGB24)?3:0; }
    /** return the image size in bytes */
    inline unsigned int memsize() const { return m_stride * m_height; }
    /** return the image size in pixels */
    inline unsigned int size() const { return m_width * m_height; }
    /** image width */
    inline unsigned int width() const { return m_width; }
    /** image height */
    inline unsigned int height() const { return m_height; }
    /** color format */
    inline ColorFormat format() const { return m_format; }

    /** data access */
    inline const unsigned char *data() const { return m_buffer; }
    inline unsigned char *data() { return m_buffer; } 

    /** data access */
    inline unsigned char operator [] (int i)  const { return m_buffer[i]; }
    inline unsigned char & operator [] (int i) { return m_buffer[i]; } 

    /** data access (column, row) */
    inline unsigned char operator () (int i, int j)  const { return m_buffer[i + j * m_stride ]; }
    inline unsigned char & operator () (int i, int j) { return m_buffer[i + j * m_stride ]; } 

/// Load a PGM/PPM image, reserve memory with new and return geometry
/// @param [in] file filename
/// @return true if image is loaded correctly
bool load(const char *file, ColorFormat format = AUTO )
{
  std::ifstream istr(file);
  
  if(!istr)
  {
    std::cerr << "cannot access " << file << std::endl;
    return false;
  }
  unsigned int width, height;
  char header = pnm_read_header(istr, width, height);
  
  if(header != '5' && header != '6')
  {
    std::cerr << file << " is not a PNM RawBits file: Format " << header << " and only 5 or 6 are allowed" << std::endl;
    return false;
  }
  
  if(header=='5' && ( (format == GREY8) || (format == AUTO) ) ) 
    {
    alloc(width, height, GREY8);

    istr.read(reinterpret_cast<char *>(m_buffer),  m_stride * m_height);

    return true;
    }
  else
  if(header=='6' && ( (format == RGB24) || (format == AUTO) ) )
    {
    alloc(width, height, RGB24);

    istr.read(reinterpret_cast<char *>(m_buffer),  m_stride * m_height);
    
    return true;
    }
  else
    return false;
}

/** Write a PGM/PPM file
 * @param filename a file 
 * @return true if file is created
 */
bool save(const char *filename) const
{
if(m_format == RGB24)
{
std::ofstream out(filename, std::ios::out | std::ios::binary);
if(out)
  {
  out << "P6\n" << m_width << ' ' << m_height << "\n255\n";
  out.write(reinterpret_cast<const char *>(m_buffer), m_width*m_height*3);
  return true;
  }
else
 return false;
}
else
if(m_format == GREY8)  
{
std::ofstream out(filename, std::ios::out | std::ios::binary);
if(out)
  {
  out << "P5\n" << m_width << ' ' << m_height << "\n255\n";
  out.write(reinterpret_cast<const char *>(m_buffer), m_width*m_height);
  return true;
  }
else
 return false;
}
}

};


////////////////////////////// Example of uses //////////////////////////


int main()
{
  Image img;
  
  if(!img.load("test.pgm"))
     std::cerr << "cannot load image" << std::endl;
  else
  {

  std::cout << img.width() << 'x' << img.height() << std::endl;

  // todo.. elaborate
  for(int i = 0;i<img.size(); ++i)
      img[i] = 255 - img[i];
  
  img.save("test2.pgm");
  
  }
  
  return 0;
}