#include <iostream>
#include <fstream>
#include <memory>
#include <exception>
#include <stdexcept>
#include <cstring>

using namespace std;

#include "pgma_io.h"

// Functions from John Burkardt are used to implement the class.
// See http://people.sc.fsu.edu/~burkardt/
bool pgma_read_data ( ifstream &file_in, int xsize, int ysize, int *garray );
bool pgma_read_header ( ifstream &file_in, int *xsize, int *ysize, int *maxg );
bool pgma_write_data ( ofstream &file_out, int xsize, int ysize, int *garray );
bool pgma_write_header ( ofstream &file_out, const char *file_out_name, int xsize, int ysize, int maxg );
bool pgma_read( const char *file_in_name, int *xsize, int *ysize, int *maxg, int **garrary );
bool pgma_write( const char *file_out_name, int xsize, int ysize, int *garray );

// class implementation:

PGM_Image::PGM_Image() {
}

PGM_Image::PGM_Image(const std::string& fileName) {
	int maxg;
	int* pd(0);
	bool res(pgma_read(fileName.c_str(), &xsize, &ysize, &maxg, &pd));
	auto_ptr<int> apd(pd);
	if (res)
		throw std::invalid_argument("Error while reading input file");
	if (maxg>255)
		throw std::invalid_argument("Input file contains at least one pixel with a value >255");
	int s(size());
	for (int i(0); i<s; ++i)
		data.push_back(pd[i]);
}

PGM_Image::iterator PGM_Image::begin() {
	return data.begin();
}
PGM_Image::const_iterator PGM_Image::begin() const {
	return data.begin();
}
PGM_Image::iterator PGM_Image::end() {
	return data.end();
}
PGM_Image::const_iterator PGM_Image::end() const {
	return data.end();
}
PGM_Image::size_type PGM_Image::size() const {
	return xsize*ysize;
}

PGM_Image::reference PGM_Image::operator[](size_type i) {
	return data[i];
}
PGM_Image::const_reference PGM_Image::operator[](size_type i) const {
	return data[i];
}

void PGM_Image::saveAs(const std::string& fileName) const {
	int s(size());
	auto_ptr<int> d(new int[s]);
	for (int i(0); i<s; ++i)
		d.get()[i]=data[i];
	if (pgma_write(fileName.c_str(), xsize, ysize, d.get())) {
		throw std::invalid_argument("Error while writing output file");;
	}
}

bool PGM_Image::operator==(const PGM_Image& right) {
	return data==right.data && xsize==right.xsize && ysize==right.ysize;
}

//****************************************************************************80

bool pgma_read ( const char *file_in_name, int *xsize, int *ysize, int *maxg, int **g )

//****************************************************************************80
//
//  Purpose:
//
//    PGMA_READ reads the header and data from an ASCII portable gray map file.
//
//  Licensing:
//
//    This code is distributed under the GNU LGPL license. 
//
//  Modified:
// 
//    28 February 2003
// 
//  Author:
// 
//    John Burkardt
//
//  Parameters:
//
//    Input, char *FILE_IN_NAME, the name of the file.
//
//    Output, int *XSIZE, *YSIZE, the number of rows and columns of data.
//
//    Output, int *MAXG, the maximum gray value.
//
//    Output, int **G, the array of XSIZE by YSIZE data values.
//
//    Output, bool PGMA_READ, is
//    true, if an error was detected, or
//    false, if the file was read.
//
{
  bool error;
  ifstream file_in;
  int numbytes;

  file_in.open ( file_in_name );

  if ( !file_in )
  {
    cout << "\n";
    cout << "PGMA_READ - Fatal error!\n";
    cout << "  Cannot open the input file \"" << file_in_name << "\".\n";
    return true;
  }
//
//  Read the header.
//
  error = pgma_read_header ( file_in, xsize, ysize, maxg );

  if ( error )
  {
    cout << "\n";
    cout << "PGMA_READ - Fatal error!\n";
    cout << "  PGMA_READ_HEADER failed.\n";
    return true;
  }
//
//  Allocate storage for the data.
//
  numbytes = (*xsize) * (*ysize) * sizeof ( int );

  *g = new int[numbytes];
//
//  Read the data.
//
  error = pgma_read_data ( file_in, *xsize, *ysize, *g );

  if ( error )
  {
    cout << "\n";
    cout << "PGMA_READ - Fatal error!\n";
    cout << "  PGMA_READ_DATA failed.\n";
    return true;
  }
//
//  Close the file.
//
  file_in.close ( );

  return false;
}
//****************************************************************************80

bool pgma_read_data ( ifstream &file_in, int xsize, int ysize, int *g )

//****************************************************************************80
//
//  Purpose:
//
//    PGMA_READ_DATA reads the data in an ASCII portable gray map file.
//
//  Licensing:
//
//    This code is distributed under the GNU LGPL license. 
//
//  Modified:
//
//    07 August 2003
//
//  Author:
//
//    John Burkardt
//
//  Parameters:
//
//    Input, ifstream &FILE_IN, a pointer to the file containing the ASCII
//    portable gray map data.
//
//    Input, int XSIZE, YSIZE, the number of rows and columns of data.
//
//    Output, int *G, the array of XSIZE by YSIZE data values.
//
//    Output, bool PGMA_READ_DATA, is
//    true, if an error was detected, or
//    false, if the data was read.
//
{
  int i;
  int j;

  for ( j = 0; j < ysize; j++ )
  {
    for ( i = 0; i < xsize; i++ )
    {
      file_in >> *g;
      if ( file_in.eof() )
      {
        return true;
      }
      g = g + 1;
    }
  }

  return false;
}
//****************************************************************************80

bool pgma_read_header ( ifstream &file_in, int *xsize, int *ysize, int *maxg )

//****************************************************************************80
//
//  Purpose:
//
//    PGMA_READ_HEADER reads the header of an ASCII portable gray map file.
//
//  Licensing:
//
//    This code is distributed under the GNU LGPL license. 
//
//  Modified:
//
//    28 February 2003
//
//  Author:
//
//    John Burkardt
//
//  Parameters:
//
//    Input, ifstream &FILE_IN, a pointer to the file.
//
//    Output, int *XSIZE, *YSIZE, the number of rows and columns of data.
//
//    Output, int *MAXG, the maximum gray value.
//
//    Output, bool PGMA_READ_HEADER, is
//    true, if an error was detected, or
//    false, if the header was read.
//
{
  int count;
  char line[255];
  char *next;
  int step;
  int width;
  char word[255];

  step = 0;

  while ( 1 )
  {
    file_in.getline ( line, sizeof ( line ) );

    if ( file_in.eof() )
    {
      cout << "\n";
      cout << "PGMA_READ_HEADER - Fatal error!\n";
      cout << "  End of file.\n";
      return true;
    }

    next = line;

    if ( line[0] == '#' )
    {
      continue;
    }

    if ( step == 0 )
    {
      count = sscanf ( next, "%s%n", word, &width );
      if ( count == EOF )
      {
        continue;
      }
      next = next + width;
      if ( strcmp ( word, "P2" ) != 0 && strcmp ( word, "p2" ) != 0 )
      {
        cout << "\n";
        cout << "PGMA_READ_HEADER - Fatal error.\n";
        cout << "  Bad magic number = \"" << word << "\".\n";
        return true;
      }
      step = 1;
    }

    if ( step == 1 )
    {

      count = sscanf ( next, "%d%n", xsize, &width );
      next = next + width;
      if ( count == EOF )
      {
        continue;
      }
      step = 2;
    }

    if ( step == 2 )
    {
      count = sscanf ( next, "%d%n", ysize, &width );
      next = next + width;
      if ( count == EOF )
      {
        continue;
      }
      step = 3;
    }

    if ( step == 3 )
    {
      count = sscanf ( next, "%d%n", maxg, &width );
      next = next + width;
      if ( count == EOF )
      {
        continue;
      }
      break;
    }

  }

  return false;
}

//****************************************************************************80

bool pgma_write ( const char *file_out_name, int xsize, int ysize, int *g )

//****************************************************************************80
//
//  Purpose:
//
//    PGMA_WRITE writes the header and data for an ASCII portable gray map file.
// 
//  Example:
//
//    P2
//    # feep.pgm
//    24 7
//    15
//    0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
//    0  3  3  3  3  0  0  7  7  7  7  0  0 11 11 11 11  0  0 15 15 15 15  0
//    0  3  0  0  0  0  0  7  0  0  0  0  0 11  0  0  0  0  0 15  0  0 15  0
//    0  3  3  3  0  0  0  7  7  7  0  0  0 11 11 11  0  0  0 15 15 15 15  0
//    0  3  0  0  0  0  0  7  0  0  0  0  0 11  0  0  0  0  0 15  0  0  0  0
//    0  3  0  0  0  0  0  7  7  7  7  0  0 11 11 11 11  0  0 15  0  0  0  0
//    0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
//
//  Licensing:
//
//    This code is distributed under the GNU LGPL license. 
//
//  Modified:
// 
//    28 February 2003
// 
//  Author:
// 
//    John Burkardt
//
//  Parameters:
//
//    Input, char *FILE_OUT_NAME, the name of the file.
//
//    Input, int XSIZE, YSIZE, the number of rows and columns of data.
//
//    Input, int *G, the array of XSIZE by YSIZE data values.
//
//    Output, bool PGMA_WRITE, is
//    true, if an error was detected, or
//    false, if the file was written.
//
{
  bool error;
  ofstream file_out;
  int i;
  int *indexg;
  int j;
  int maxg;
//
//  Open the output file.
//
  file_out.open ( file_out_name );

  if ( !file_out )
  {
    cout << "\n";
    cout << "PGMA_WRITE - Fatal error!\n";
    cout << "  Cannot open the output file \"" << file_out_name << "\".\n";
    return true;
  }
//
//  Compute the maximum.
//
  maxg = 0;
  indexg = g;

  for ( j = 0; j < ysize; j++ )
  {
    for ( i = 0; i < xsize; i++ )
    {
      if ( maxg < *indexg )
      {
        maxg = *indexg;
      }
      indexg = indexg + 1;

    }
  }
//
//  Write the header.
//
  error = pgma_write_header ( file_out, file_out_name, xsize, ysize, maxg );

  if ( error )
  {
    cout << "\n";
    cout << "PGMA_WRITE - Fatal error!\n";
    cout << "  PGMA_WRITE_HEADER failed.\n";
    return true;
  }
//
//  Write the data.
//
  error = pgma_write_data ( file_out, xsize, ysize, g );

  if ( error )
  {
    cout << "\n";
    cout << "PGMA_WRITE - Fatal error!\n";
    cout << "  PGMA_WRITE_DATA failed.\n";
    return true;
  }
//
//  Close the file.
//
  file_out.close ( );

  return false;
}
//****************************************************************************80

bool pgma_write_data ( ofstream &file_out, int xsize, int ysize, int *g )

//****************************************************************************80
//
//  Purpose:
//
//    PGMA_WRITE_DATA writes the data for an ASCII portable gray map file.
//
//  Licensing:
//
//    This code is distributed under the GNU LGPL license. 
//
//  Modified:
//
//    28 February 2003
//
//  Author:
//
//    John Burkardt
//
//  Parameters:
//
//    Input, ofstream &FILE_OUT, a pointer to the file.
//
//    Input, int XSIZE, YSIZE, the number of rows and columns of data.
//
//    Input, int *G, the array of XSIZE by YSIZE data.
//
//    Output, bool PGMA_WRITE_DATA, is
//    true, if an error was detected, or
//    false, if the data was written.
//
{
  int i;
  int *indexg;
  int j;
  int numval;

  indexg = g;
  numval = 0;

  for ( j = 0; j < ysize; j++ )
  {
    for ( i = 0; i < xsize; i++ )
    {
      file_out << *indexg << " ";
      numval = numval + 1;
      indexg = indexg + 1;

      if ( numval % 12 == 0 || i == xsize - 1 || numval == xsize * ysize )
      {
        file_out << "\n";
      }
      else
      {
        file_out << " ";
      }

    }
  }
  return false;
}
//****************************************************************************80

bool pgma_write_header ( ofstream &file_out, const char *file_out_name, int xsize, 
  int ysize, int maxg )

//****************************************************************************80
//
//  Purpose:
//
//    PGMA_WRITE_HEADER writes the header of an ASCII portable gray map file.
//
//  Licensing:
//
//    This code is distributed under the GNU LGPL license. 
//
//  Modified:
//
//    02 March 2003
//
//  Author:
//
//    John Burkardt
//
//  Parameters:
//
//    Input, ofstream &FILE_OUT, a pointer to the file.
//
//    Input, char *FILE_OUT_NAME, the name of the file.
//
//    Input, int XSIZE, YSIZE, the number of rows and columns of data.
//
//    Input, int MAXG, the maximum gray value.
//
//    Output, bool PGMA_WRITE_HEADER, is
//    true, if an error was detected, or
//    false, if the header was written.
//
{
  file_out << "P2\n";
  file_out << "# " << file_out_name << " created by PGMA_IO::PGMA_WRITE.\n";
  file_out << xsize << "  " << ysize << "\n";
  file_out << maxg << "\n";

  return false;
}
