/*
 * Copyright (C) 2006 - 2023 René Rebe, ExactCODE GmbH
 * 
 * This program 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; version 2. A copy of the GNU General
 * Public License can be found in the file LICENSE.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANT-
 * ABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
 * Public License for more details.
 *
 * Alternatively, commercial licensing options are available from the
 * copyright holder ExactCODE GmbH Germany.
 */

/*
 * C++ PNM library.
 *
 * I former times we used to use the netbpm library here, but as
 * it can not handle in memory or otherwise transferred files, allowing
 * access via the C FILE* exclusively (???!!!) we had to write our
 * own parser here ...
 *
 */

#include <string.h> // memcpy
#include <iostream>
#include <string>
#include <sstream>

#ifdef _MSC_VER
#include <vector>
#endif

#include "pnm.hh"
#include "Endianess.hh"
using namespace Exact;

int getNextHeaderNumber (std::istream* stream)
{
  for (bool whitespace = true; whitespace && stream;)
    {
      int c = stream->peek();
      switch (c) {
      case ' ':
	stream->get(); // consume silently
	break;
      case '\n':
      case '\r':
	stream->get(); // consume silently
	// comment line?
	while (stream->peek() == '#') {
	  std::string str;
	  std::getline(*stream, str); // consume comment line
	}
	break;
      default:
	whitespace = false;
      }
    }
  
  int i;
  *stream >> i;
  return i;
}

int PNMCodec::readImage (std::istream* stream, Image& image, const std::string& decompress)
{
  // check signature
  if (stream->peek () != 'P')
    return false;
  stream->get(); // consume P
  
  image.bps = 0;
  char mode = stream->peek();
  switch (mode) {
  case '1':
  case '4':
    image.bps = 1;
  case '2':
  case '5':
    image.spp = 1;
    break;
  case '3':
  case '6':
    image.spp = 3;
    break;
  default:
    stream->unget(); // P
    return false;
  }
  stream->get(); // consume format number
  
  image.w = getNextHeaderNumber (stream);
  image.h = getNextHeaderNumber (stream);
  
  int maxval = 1;
  if (image.bps != 1) {
    maxval = getNextHeaderNumber (stream);
  }
  
  image.bps = 1;
  while ((1 << image.bps) < maxval)
    ++image.bps;
  
  // not stored in the format :-(
  image.setResolution(0, 0);
  
  // allocate data, if necessary
  image.resize (image.w, image.h);
  
  // consume the left over spaces and newline 'till the data begins
  {
    std::string str;
    std::getline (*stream, str);
  }
  
  if (mode <= '3') // ascii / plain text
    {
      Image::iterator it = image.begin ();
      for (int y = 0; y < image.h; ++y)
	{
	  for (int x = 0; x < image.w; ++x)
	    {
	      if (image.spp == 1) {
		int i;
		
		// some apps, like Gimp don't white-space 1 bit files
		if (mode == '1') {
		  for (i = ' '; isspace(i) && i != std::istream::traits_type::eof();)
		    i = stream->get();
		  if (i == std::istream::traits_type::eof()) {
		    // premature eof
		    std::cerr << "Premature EOF" << std::endl;
		    return false;
		  }
		  
		  i = i == '0' ? 255 : 0;
		} else {
		  *stream >> i;
		  i = i * (255 / maxval);
		}
		
		it.setL (i);
	      }
	      else {
		uint16_t r, g, b;
		*stream >> r >> g >> b;
		
		it.setRGB (r, g, b);
	      }
	      
	      it.set (it);
	      ++it;
	    }
	}
    }
  else // binary data
    {
      const int stride = image.stride();
      const int bps = image.bps;
      
      for (int y = 0; y < image.h; ++y)
	{
	  uint8_t* dest = image.getRawData() + y * stride;
	  stream->read ((char*)dest, stride);
	  if (stream->eof()) {
	    std::cerr << "Premature EOF" << std::endl;
	    return false;
	  }
	  
	  if (bps == 1) {
	    uint8_t* xor_ptr = dest;
	    for (int x = 0; x < image.w; x += 8)
	      *xor_ptr++ ^= 0xff;
	  } else if (bps == 16) {
	    uint16_t* swap_ptr = (uint16_t*)dest;
	    for (int x = 0; x < stride/2; ++x, ++swap_ptr)
	      *swap_ptr = ByteSwap<NativeEndianTraits,BigEndianTraits, uint16_t>::Swap (*swap_ptr);
	  }
	}
    }
  
  return true;
}

bool PNMCodec::writeImage (std::ostream* stream, Image& image, int quality,
			   const std::string& compress)
{
  // ok writing should be easy ,-) just dump the header
  // and the data thereafter ,-)
  
  int format = 0;
  
  if (image.spp == 1 && image.bps == 1)
    format = 1;
  else if (image.spp == 1)
    format = 2;
  else if (image.spp == 3)
    format = 3;
  else {
    std::cerr << "NYI PNM sample format." << std::endl;
    return false;
  }
  
  std::string c (compress);
  std::transform (c.begin(), c.end(), c.begin(), tolower);
  if (c == "plain")
    c = "ascii";

  if (c != "ascii")
    format += 3;
  
  *stream << "P" << format << std::endl;
  *stream << "# exactcode.com/exactimage" << std::endl;

  *stream << image.w << " " << image.h << std::endl;
  
  // maxval
  const int maxval = (1 << image.bps) - 1;
  const int divval = image.bps < 8 ? 255 / maxval : 1;
  
  if (image.bps > 1)
    *stream << maxval << std::endl;
  
  Image::iterator it = image.begin ();
  if (c == "ascii")
    {
      const bool whitespace = image.bps > 1;
      for (int y = 0; y < image.h; ++y)
	{
	  it = it.at(0, y);
	  for (int x = 0; x < image.w; ++x, ++it)
	    {
	      if (x != 0 && whitespace)
		*stream << " ";
	      
	      *it;
	      if (image.spp == 1) {
		unsigned i = it.getL();

		// only mode 1 is defined with 1 == black, ...
		if (format == 1) i = 255 - i;
		*stream << i / divval;
	      }
	      else {
		uint16_t r = 0, g = 0, b = 0;
		it.getRGB (&r, &g, &b);
		*stream << (int)r << " " << (int)g << " " << (int)b;
	      }
	    }
	  *stream << std::endl;
	}
    }
  else
    {
      const int bps = image.bps;
      const int stride = image.stride();
      const int stridefill = image.stridefill();
#ifndef _MSC_VER
      uint8_t ptr[stridefill];
#else
      std::vector<uint8_t> ptr(stridefill);
#endif
      for (int y = 0; y < image.h; ++y)
	{
	  memcpy (&ptr[0], image.getRawData() + y * stride, stridefill);
	  
	  // is this publically defined somewhere???
	  if (bps == 1) {
	    uint8_t* xor_ptr = &ptr[0];
	    for (int x = 0; x < image.w; x += 8)
	      *xor_ptr++ ^= 0xff;
	  } else if (bps == 16) {
	    uint16_t* swap_ptr = (uint16_t*)&ptr[0];
	    for (int x = 0; x < stridefill / 2; ++x, ++swap_ptr)
	      *swap_ptr = ByteSwap<BigEndianTraits, NativeEndianTraits, uint16_t>::Swap (*swap_ptr);
	  }
	  
	  stream->write((char*)&ptr[0], stridefill);
	}
    }
  
  return true;
}

PNMCodec pnm_loader;
