/*
 * Implementation of the image operations utilities
 * Author: Martin Dietze
 *
 * $Date$
 * $Revision$
 *
 */

#include "WImage/MagickInter.hh"

#include <cstring>
#include <iostream>
#include <cerrno>
#include <Magick++.h>

//using MagickLib::Quantum;
//using namespace MagickLib;

#ifdef WIN32
# include <io.h>
# define _open open
#else
# include <unistd.h>
#endif
#include <fcntl.h>

#if !(defined __WATCOMC__ && __WATCOMC__ < 1230)
using namespace std;
#endif

#define MAX_PPM_DEPTH 8

static std::string tempFilePrefixFromEnv (std::string paddern,
					  std::string delim);
static int tempFileName (char *templ, const char *extension, unsigned int seed);

static std::string
tempFileName (const std::string &extension)
{
  std::string theExtension = "." + extension;
  static char buf[64] = { '\0' };
  pid_t pid = getpid();
  int rc = -1;
  std::string paddern = "wavelet-tmp-%05d-XXXXXX";
  std::string paddernWithPrefix = tempFilePrefixFromEnv (paddern, "/");
  unsigned seed = (unsigned)pid;

  if (paddernWithPrefix.size () > 0)
    {
      /* if we found a matching environment variable, then use it. */
      snprintf (buf, sizeof buf - 1, paddernWithPrefix.c_str (), (int)pid);
      rc = tempFileName (buf, theExtension.c_str (), seed);
    }
  else
    {
      /* we found no matching environment variable, we try /tmp/ first. */
      paddernWithPrefix = "/tmp/" + paddern;
      snprintf (buf, sizeof buf - 1, paddernWithPrefix.c_str (), (int)pid);
      rc = tempFileName (buf, theExtension.c_str (), seed);
      if (rc < 0)
	{
	  snprintf (buf, sizeof buf - 1, paddern.c_str (), (int)pid);
	  rc = tempFileName (buf, theExtension.c_str (), seed);
	}
    }


  if (rc < 0)
    {
      char ctemp[64];
      snprintf(ctemp, sizeof ctemp, "%d", rc);
      throw std::invalid_argument ("Could not create temp file, rc: "
        + std::string(ctemp) + ", system error is: "
	+ std::string (strerror (errno)).c_str ()
	+ ", could it be that the current directory is not writable?");
    }
  return std::string (buf);
}

static void
scaleMagickImage (Magick::Image &img, int rows, int cols)
{
  img.scale (Magick::Geometry ((unsigned)cols, (unsigned)rows));
}

static bool
isTransparent (const ColorImage &img, int y, int x, coeff *transparentColors)
{
  for (int c = 0; c < img.colors (); ++c)
    {
      if (!tools_epsilons (img.channel(c).at (y, x),
			   transparentColors != NULL? transparentColors[c]: -1.0,
			   COEFF_EPSILON))
	{
	  return false;
	}
    }
  return true;
}

static coeff
fixCoeff (coeff value, int depth)
{
  return (value < 0? 0: (value > 255.0? 255.0: value)) / depth;
}

static Magick::ColorRGB
colorRGB (const ColorImage &img, int y, int x, int actualColorDepth)
{
  static Magick::ColorRGB color;
  color.alphaQuantum (0);
  color.red (fixCoeff (img.channel(0).at (y, x), actualColorDepth));
  color.green (fixCoeff (img.channel(1).at (y, x), actualColorDepth));
  color.blue (fixCoeff (img.channel(2).at (y, x), actualColorDepth));
  return color;
}

static Magick::ColorGray
colorGray (const ColorImage &img, int y, int x, int actualColorDepth)
{
  static Magick::ColorGray color;
  color.alphaQuantum (0);
  color.shade (fixCoeff (img.channel(0).at (y, x), actualColorDepth));
  return color;
}

static void
fixTransparency (Magick::Image &image,
		 const MagickInter::TransparencyBackground
		 &transparencyBackground)
{
  static Magick::ColorRGB color;
  color.alphaQuantum (MaxRGB);
  color.red (transparencyBackground == MagickInter::WHITE? 1.0: 0.0);
  color.green (transparencyBackground == MagickInter::WHITE? 1.0: 0.0);
  color.blue (transparencyBackground == MagickInter::WHITE? 1.0: 0.0);
  for (unsigned r = 0; r < image.rows (); ++r)
    {
      for (unsigned c = 0; c < image.columns (); ++c)
	{
	  Magick::Color colorInFrame = image.pixelColor (c, r);

	  if (colorInFrame.alphaQuantum () != 0)
	    {
	      image.pixelColor (c, r, color);
	    }

	}
    }
}

Magick::Image
MagickInter::magickImageFromColorImageWithTransparency (ColorImage &img,
							bool withTransparency,
							coeff *transparentColors,
							int colorBytes)
{
  const Magick::Geometry geometry (img.cols (), img.rows ());
  const Magick::Color background (0, 0, 0, MaxRGB);
  Magick::Image image (geometry, background);
  int actualColorDepth = colorBytes > 0? TWOPOW (8 * colorBytes): TWOPOW (image.depth ());


  for (int y = 0; y < img.rows (); ++y)
    {
      for (int x = 0; x < img.cols (); ++x)
	{
	  if (!withTransparency || !isTransparent(img, y, x, transparentColors))
	    {
	      if (img.colors () == 3)
		{
		  image.pixelColor (x, y, colorRGB (img, y, x, actualColorDepth));
		}
	      else
		{
		  image.pixelColor (x, y, colorGray (img, y, x, actualColorDepth));
		}
	    }
	}
    }

  if (colorBytes > 0)
    {
      image.depth (8 * colorBytes);
      if (img.colors () == 3)
	{
	  image.quantizeColorSpace (Magick::RGBColorspace);
	}
      else
	{
	  image.quantizeColorSpace (Magick::GRAYColorspace);
	}
      image.quantizeColors (actualColorDepth);
      image.quantize ();
    }

  return image;
}

Magick::Image
MagickInter::magickImageFromColorImage (ColorImage &img)
{
  std::string tmp = tempFileName (img.colors () == 1? "pgm": "ppm");
  try
    {
      img.write (tmp.c_str ());
    }
  catch (const std::ios_base::failure &f)
    {
      (void)remove (tmp.c_str ());
      throw std::ios_base::failure (tmp + ": " + f.what ());
    }
  catch (const std::exception &e)
    {
      (void)remove (tmp.c_str ());
      throw std::invalid_argument (tmp + ": " + e.what ());
    }
  Magick::Image result (tmp);
  (void)remove (tmp.c_str ());
  return result;
}

std::auto_ptr<ColorImage>
MagickInter::colorImageFromMagickImage (Magick::Image &img)
{
  std::string tmp = tempFileName ("ppm");
  try
    {
      img.modifyImage ();
      img.depth (MAX_PPM_DEPTH);
      img.write (tmp);
      std::auto_ptr<ColorImage> result (NEW (ColorImage ()));
      result->read (tmp.c_str ());
      (void)remove (tmp.c_str ());
      return result;
    }
  catch (const std::ios_base::failure &f)
    {
      (void)remove (tmp.c_str ());
      throw std::ios_base::failure (tmp + ": " + f.what ());
    }
  catch (const std::exception &e)
    {
      (void)remove (tmp.c_str ());
      throw std::invalid_argument (tmp + ": " + e.what ());
    }
  (void)remove (tmp.c_str ());
  throw std::invalid_argument (tmp + ": some other / unknown error.");
}

std::auto_ptr<ColorImage>
MagickInter::obtainColorImage (const std::string &inFile,
			       const TransparencyBackground
			       &transparencyBackground)
{
  std::auto_ptr<ColorImage> img (NEW (ColorImage ()));

  try
    {
      img->read (inFile.c_str ());
    }
  catch (...)
    {
      std::string tmp = tempFileName ("ppm");
      try
	{
	  Magick::Image fallBack (inFile);
	  if (fallBack.matte () && transparencyBackground != NONE)
	    {
	      fixTransparency(fallBack, transparencyBackground);
	    }
	  try
	    {
	      fallBack.modifyImage ();
	      fallBack.depth (MAX_PPM_DEPTH);
	      fallBack.write (tmp);
	    }
          catch (const std::ios_base::failure &f)
            {
	      (void)remove (tmp.c_str ());
              throw std::ios_base::failure (tmp + ": " + f.what ());
            }
          catch (const std::exception &e)
            {
	      (void)remove (tmp.c_str ());
              throw std::invalid_argument (tmp + ": " + e.what ());
            }
	  catch (...)
	    {
	      (void)remove (tmp.c_str ());
	      throw;
	    }
	  try
	    {
	      img->read (tmp.c_str ());
	      (void)remove (tmp.c_str ());
	    }
	  catch (...)
	    {
	      (void)remove (tmp.c_str ());
	      throw;
	    }
	}
      catch (const std::exception &e)
	{
	  throw std::ios_base::failure (e.what ());
	}
    }

  return img;
}

void
MagickInter::writeColorImageWithTransparency (ColorImage &img,
					      const std::string &outFile,
					      int quality,
					      bool withTransparency,
					      coeff *transparentColors,
					      int colorBytes)
{
  try
    {
      Magick::Image tmp =
	MagickInter::magickImageFromColorImageWithTransparency (img,
								withTransparency,
								transparentColors,
								colorBytes);
      tmp.modifyImage ();
      if (quality > 0)
	{
	  tmp.quality (quality);
	}
      tmp.write (outFile);
    }
  catch (const std::ios_base::failure &f)
    {
      throw std::ios_base::failure (outFile + ": " + f.what ());
    }
  catch (const std::exception &e)
    {
      throw std::invalid_argument (outFile + ": " + e.what ());
    }
}

void
MagickInter::writeColorImage (ColorImage &img, const std::string &outFile, int quality)
{
  try
    {
      Magick::Image tmp = MagickInter::magickImageFromColorImage (img);
      tmp.modifyImage ();
      if (quality > 0)
	{
	  tmp.quality (quality);
	}
      tmp.write (outFile);
    }
  catch (const std::ios_base::failure &f)
    {
      throw std::ios_base::failure (outFile + ": " + f.what ());
    }
  catch (const std::exception &e)
    {
      throw std::invalid_argument (outFile + ": " + e.what ());
    }
}

void
MagickInter::scaleAndWriteColorImage (ColorImage &img, int rows, int cols,
				      const std::string &outFile, int quality)
{
  try
    {
      Magick::Image resized = magickImageFromColorImage (img);
      resized.modifyImage ();
      if (quality > 0)
	{
	  //std::cout << "quality: " << quality << std::endl;
	  resized.quality (quality);
	}
      scaleMagickImage (resized, rows, cols);
      resized.write (outFile);
    }
  catch (const std::ios_base::failure &f)
    {
      throw std::ios_base::failure (outFile + ": " + f.what ());
    }
  catch (const std::exception &e)
    {
      throw std::invalid_argument (outFile + ": " + e.what ());
    }
}

// this is copy-and-pasted (gasp) from cxxutil since we don't want this
// lib to depend on it...
static int
tempFileName (char *templ, const char *extension, unsigned int seed)
{
  static char letters[]
    = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";

  int templen = strlen (templ);
  int letterlen = strlen (letters);
  int extenlen = strlen (extension);

  if (templen < 6)
    {
      return -1;
    }

  char *p = templ + templen - 6;

  if (strcmp (p, "XXXXXX") != 0)
    {
      return -1;
    }

  if (seed == 0)
    {
      srand ((unsigned)time (NULL));
    }
  else
    {
      srand (seed);
    }
  int fd;
  char *fname = NEW (char [templen + extenlen + 1]);
  strcpy (fname, templ);
  strcat (fname, extension);
  p = fname + templen - 6;

  for (int run = 0; run < 512; run++)
    {
      for (int i = 0; i < 6; i++)
        {
          p[i] = letters[rand () % letterlen];
        }

      if ((fd = open (fname, O_CREAT | O_EXCL, S_IRWXU)) > 0)
        {
          break;
        }
    }

  if (fd < 0)
    {
      DELETE (fname);
      return -1;
    }

  close (fd);
  (void)remove (fname);
  strncpy (templ, fname, templen + extenlen);
  DELETE (fname);

  return 0;
}


static std::string
tempFilePrefixFromEnv (std::string paddern, std::string delim = "/")
{
  std::string rv;

  if (::getenv ("TMP") != NULL)
    {
      rv = ::getenv ("TMP") + delim + paddern;
    }
  else if (::getenv ("TEMP") != NULL)
    {
      rv = ::getenv ("TEMP") + delim + paddern;
    }
  else
    {
      rv = "";
    }
  return rv;
}
