/*
 *  sligraphics.cc
 *
 *  This file is part of NEST.
 *
 *  Copyright (C) 2004 The NEST Initiative
 *
 *  NEST 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.
 *
 *  NEST 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 NEST.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include <cctype>     // for isspace
#include <cstdio>
#include <iostream>

#include "arraydatum.h"
#include "stringdatum.h"
#include "integerdatum.h"
#include "aggregatedatum.h"
#include "numericdatum.h"
#include "fdstream.h"
#include "sligraphics.h"


/*   BeginDocumentation
Name:readPGM - read in grey-level image in PGM Format.

Synopsis:string readPGM -> int    int    int   arraytype
fname  readPGM -> width height maxval [grayvals]

Description:this function reads an image file in the PGM format and 
returns the width, height, maximum gray value and the 
image itself (as a linear array).
On Unix systems, man pgm should give you a description
of the PGM (Portable GrayMap) image format.

Parameters:fname      - name of file to be read
[grayvals] - one-dim. array containing the pixel gray values,
             starting from the upper left corner of the image, 
   	  continuing rowwise (normal englissh reading order).
maxval     - the maximum gray value
width      - width of image in pixels (no. of columns)
height     - height of image (no. of rows)

Examples:(FancyImage.pgm) readPGM -> 16 24 255 [grayvals]
    
This reads the image FancyImage.pgm, and tells you that it has 
16 columns, 24 rows, and a maximum gray value of 255. The pixel
gray values are stored in the array.

Author:Schmuker, Gewaltig

FirstVersion:9.1.2003

SeeAlso:writePGM
*/
void SLIgraphics::ReadPGMFunction::execute(SLIInterpreter *i) const
{
  // call: filename readPGM -> width height maxval image(array)
  
  if(i->OStack.load()<1)
    {
      i->raiseerror(i->StackUnderflowError);
      return;
    }

  StringDatum *sd= dynamic_cast<StringDatum *>(i->OStack.top().datum());
  
  if(sd ==NULL)
    {
      i->raiseerror(i->ArgumentTypeError);
      return;
    }
  std::istream *in=NULL;  
  vector<long> image;
  int width=0, height=0, maxval=0;  // for the image parameters: width, height, maxval

  try
    {
      in = openPGMFile(sd);
      char magic[2];
      readMagicNumber(in, magic);
      initRead(in, width, height, maxval);
      readImage(in, magic, image, width, height, maxval);
      delete in;
    }
  catch(string const &s)
    {
      delete in;
      i->message(SLIInterpreter::M_ERROR, "readPGM","Error reading image.");
      i->message(SLIInterpreter::M_ERROR, "readPGM",s.c_str());
      i->raiseerror(i->BadIOError);
      return;
    }
  
  i->EStack.pop();
  i->OStack.pop();
  i->OStack.push(ArrayDatum(image));
  i->OStack.push(maxval);
  i->OStack.push(height);
  i->OStack.push(width);
}

std::istream *  SLIgraphics::ReadPGMFunction::openPGMFile(StringDatum *filename) const
{
  //opens pgm file for reading and returns pointer to the istream
  std::istream *in = new ifdstream(filename->c_str());
  if(in->good())
    {
      return in;
    }
  else 
    {
      throw string("File open error.");
    }
} 

void  SLIgraphics::ReadPGMFunction::readMagicNumber(std::istream * in, 
					       char * magic) const
{
  // reads in the magic number which determines the file format
  try
    {
      *in >> magic;
    }
  catch(std::exception& e) 
    {
      throw string("Magic number read error: ")+ e.what();
    }
}

void  SLIgraphics::ReadPGMFunction::initRead(std::istream * in, 
					int &width,
					int &height,
					int &maxval) const
{
  //reads the width, height, and max. gray value in this order
  char temp[256];
  try
    {
      //throw away whitespaces after magic number
      //otherwise, >> gets confused about the newline before the numbers
      char trash;
      while(std::isspace(trash=in->get()))
	continue;
      in->putback(trash);
      //skip comments
      do
      {
	in->getline(temp, 255);
      } 
      while (temp[0]=='#');
      //width and height are now in temp, so parse it
      sscanf(temp, "%d %d", &width, &height);
      *in >> maxval;
    }
  catch(std::exception& e) 
    {
      throw string("Read init error: ")+ e.what();
    }
}

void  SLIgraphics::ReadPGMFunction::readImage(std::istream *in, 
					 char magic[2], 
					 vector<long> &image, 
					 int width,
					 int height,
					 int maxval) const
{
  // this reads the gray value array
  image.clear();
  image.reserve(width*height);
  
  try{
    if(string(magic)==string("P2"))  //ASCII PGM
      { 
	int  tmp;
	while((*in >> tmp) && !(in->eof()))
	  {
	    image.push_back((long) tmp);
	  }
      }
    else if(string(magic)==string("P5")  || 
	    string(magic)==string("P6"))    //Raw PGM (resp. PPM)
      { 
	if(maxval > 255)
	  {
	    throw string("read: maxval too large for format RawPGM(P5).");
	  }
	char tmp;
	long tmp2;
	in->read(&tmp, 1); //throw away LF after maxval
	//TODO: Protect this from reading too much data like trailing 
	//newlines: use for instead of while
	while(in->read(&tmp, 1) && !(in->eof())){
	  tmp2=(unsigned char) tmp;
	  image.push_back((long) tmp2);
	}	  
      }
    else
      {
	throw string("image read error:") + string(magic) + string(": Unsupported file type.");
      }
  }
  catch(std::exception& e){
    throw string("image read error: ") + e.what();
  }
}

/*   BeginDocumentation
Name:writePGM - write out a grey-level image in PGM format

Synopsis:string arraytype   int    int   int   writePGM
fname  [grayvals] maxval height width writePGM

Description:This writes an array of integers as grey-level image
using the PGM (PortableGrayMap) format. 
On Unix systems, man 5 pgm should give you a description of
the PGM image format. 

Parameters:fname      - name of file to be written
[grayvals] - one-dim. array containing the pixel gray values,
             starting at the upper left corner and continuing
	     rowwise (normal english reading order).
maxval     - the maximum gray value
width      - width of image in pixels (no. of columns)
height     - height of image (no. of rows)

Remarks:So far, only the plain ASCII variant of the PGM Format is 
used. In the PGM manual, this is referred to as "P2".

Examples:(FancyImage.pgm) [grayvals] 255 24 16 writePGM
This writes an image named FancyImage.pgm with the gray values 
from the array, having 16 columns and 24 rows.

Author:Schmuker, Gewaltig

FirstVersion:9.1.2003

SeeAlso:readPGM
*/	     
void SLIgraphics::WritePGMFunction::execute(SLIInterpreter *i) const
{
  //TODO: fix argument order!!! Should be the same as when getting 
  //the parameters read by readPGM.First make sure that your script is 
  //properly working! 
  
  // call: filename image(array) maxval height width writePGM
  if(i->OStack.load()<5)
    {
      i->raiseerror(i->StackUnderflowError);
      return;
    }
  
  IntegerDatum * w = dynamic_cast<IntegerDatum*>(i->OStack.pick(0).datum());
  IntegerDatum * h = dynamic_cast<IntegerDatum*>(i->OStack.pick(1).datum());
  IntegerDatum * m = dynamic_cast<IntegerDatum*>(i->OStack.pick(2).datum());
  ArrayDatum * image  = dynamic_cast<ArrayDatum*>(i->OStack.pick(3).datum());
  StringDatum * filename= dynamic_cast<StringDatum*>(i->OStack.pick(4).datum());
  
  long width  = (long) w->get();
  long height = (long) h->get();
  long maxval = (long) m->get();
  
  std::ostream * out=NULL;
  
  try
    {
      out = new ofdstream(filename->c_str());
      
      if(!out->good()) 
	throw string("Error when opening file for writing.");
      
      if ((long) image->size() != width * height) 
	throw string("Array size does not match given dimensions.");
      
      //Plain ASCII PGM format
      *out << "P2" << std::endl;  //Magic Number
      *out << "# CREATOR: SLI/Synod. The NEST cooperation 2003." << std::endl;
      *out << width << " " << height << std::endl;
      *out << maxval << std::endl;
      for (unsigned int i=0; i < image->size(); i++){
	*out << image->get(i);

	// write newline after 20 written numbers or
	// one pixel row, which ever comes first
	if (width > 20) 
	  {
	    if ((i+1) % 20 == 0) *out << std::endl;
	    else *out << " ";
	  }
	else 
	  {
	    if ((i+1)% width == 0) *out << std::endl;
	    else *out << " ";
	  }
      }

      *out << std::endl;  //make sure file ends in a newline
      delete out;
    }
  catch(std::exception& e) {
    throw string("exception: ") + e.what();
  }
  catch(string const &s)
    {
      delete out;
      i->message(SLIInterpreter::M_ERROR, "writePGM","Error writing image.");
      i->message(SLIInterpreter::M_ERROR, "writePGM",s.c_str());
      i->raiseerror(i->BadIOError);
      return;
    }

  // clean up
  i->EStack.pop();
  i->OStack.pop();
  i->OStack.pop();
  i->OStack.pop();
  i->OStack.pop();
  i->OStack.pop();
}



void SLIgraphics::init(SLIInterpreter *i)
{
  i->createcommand("readPGM",&readpgmfunction);
  i->createcommand("writePGM",&writepgmfunction);
}

const string SLIgraphics::name() const
{
  return string("SLIgraphics");
}

const string SLIgraphics::commandstring() const
{
  return "M_DEBUG (SLIgraphics) (Initialising Graphics IO) message";
}