/*   Cataract - Static web photo gallery generator
 *   Copyright (C) 2008 Tomas Bzatek <tbzatek@users.sourceforge.net>
 *
 *   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; either version 2
 *   of the License, or any later version.
 *
 *   This program 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 this program; if not, write to the Free Software
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <exiv2/image.hpp>
#include <exiv2/exif.hpp>

#include <wand/magick-wand.h>

#include <config.h>

#include "jpeg-utils.h"
#include "gallery-utils.h"



/*
 *   get_exif: retrieve EXIF informations from a JPEG image
 */
TExifData *
get_exif (const gchar *filename)
{
  TExifData *data;

  data = (TExifData*) g_malloc0 (sizeof (TExifData));

  try
  {
     Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open (filename);
     g_assert (image.get() != 0);
     image->readMetadata();

     Exiv2::ExifData &exifData = image->exifData();
     if (! exifData.empty())  {
       /*  EXIF::Aperture  */
       try {
         float val = exifData["Exif.Photo.FNumber"].toFloat();
         if (val >= 0)
           data->aperture = g_strdup_printf ("f/%.1f", val);
       } catch (...) { }

       /*  EXIF::Camera model  */
       try {
         data->camera_model = g_strdup (exifData["Exif.Image.Model"].toString().c_str());
       } catch (...) { }

       /*  EXIF::DateTime  */
       try {
         const char *val = NULL;
         try {
           val = exifData["Exif.Photo.DateTimeOriginal"].toString().c_str();
         } catch (...) { }
         if (! val || strlen (val) == 0)
           try {
             val = exifData["Exif.Image.DateTime"].toString().c_str();
           } catch (...) { }

         if (val && strlen (val) > 0) {
           static struct tm tt;
           static char conv[1024];

           if (sscanf (val, "%d:%d:%d %d:%d:%d", &tt.tm_year, &tt.tm_mon, &tt.tm_mday, &tt.tm_hour, &tt.tm_min, &tt.tm_sec) == 6)
             {
               tt.tm_year -= 1900;
               tt.tm_mon--;
               mktime (&tt);
               if (strftime (&conv[0], sizeof (conv), "%c", &tt))
                   data->datetime = g_strdup (&conv[0]);
             }
         }
       } catch (...) { }

       /*  EXIF::Shutter speed  */
       try {
         float val = exifData["Exif.Photo.ExposureTime"].toFloat();
         if (val > 0) {
           if (val < 0.5)
             data->exposure = g_strdup_printf ("1/%.0f s", 1/val);
           else
             data->exposure = g_strdup_printf ("%.1f s", val);
         }
       } catch (...) { }

       /*  EXIF::Flash  */
       try {
         long int val = exifData["Exif.Photo.Flash"].toLong();
         if (val > 0 && (val & 1) == 1)
           data->flash = g_strdup ("Flash fired");
         else
           data->flash = g_strdup ("--");
       } catch (...) { }

       /*  EXIF::Focal length  */
       try {
         float val = exifData["Exif.Photo.FocalLength"].toFloat();
         if (val >= 0)
           data->focal_length = g_strdup_printf ("%.0f mm", val);
       } catch (...) { }

       /*  EXIF::ISO  */
       try {
         long int val = exifData["Exif.Photo.ISOSpeedRatings"].toLong();
         if (val > 0)
           data->iso = g_strdup_printf ("%ld", val);
       } catch (...) { }

       /*  EXIF::Software  */
       try {
         data->exif_software = g_strdup (exifData["Exif.Image.Software"].toString().c_str());
       } catch (...) { }

       /*  EXIF::Image description  */
       try {
         data->exif_imgdesc = g_strdup (exifData["Exif.Image.ImageDescription"].toString().c_str());
       } catch (...) { }

       /*  EXIF::Artist  */
       try {
         data->exif_artist = g_strdup (exifData["Exif.Image.Artist"].toString().c_str());
       } catch (...) { }

       /*  EXIF::Copyright  */
       try {
         data->exif_copyright = g_strdup (exifData["Exif.Image.Copyright"].toString().c_str());
       } catch (...) { }

       /*  EXIF::User comment  */
       try {
         data->exif_usercomment = g_strdup (exifData["Exif.Photo.UserComment"].toString().c_str());
       } catch (...) { }
     }

     Exiv2::IptcData &iptcData = image->iptcData();
     if (! iptcData.empty()) {
       /*  IPTC::Object name  */
       try {
         data->iptc_objectname = g_strdup (iptcData["Iptc.Application2.ObjectName"].toString().c_str());
       } catch (...) { }

       /*  IPTC::Copyright  */
       try {
         data->iptc_copyright = g_strdup (iptcData["Iptc.Application2.Copyright"].toString().c_str());
       } catch (...) { }

       /*  IPTC::Credit  */
       try {
         data->iptc_credit = g_strdup (iptcData["Iptc.Application2.Credit"].toString().c_str());
       } catch (...) { }

       /*  IPTC::Caption  */
       try {
         data->iptc_caption = g_strdup (iptcData["Iptc.Application2.Caption"].toString().c_str());
       } catch (...) { }

       /*  IPTC::Author  */
       try {
         data->iptc_author = g_strdup (iptcData["Iptc.Application2.Byline"].toString().c_str());
       } catch (...) { }
     }

     /*  JPEG::Comment  */
     try {
       data->jpeg_comment = g_strdup (image->comment().c_str());
     } catch (...) { }
  }
  catch (Exiv2::AnyError& e)
  {
    log_error ("get_exif: Caught Exiv2 exception: '%s'\n", e.what());
    free_exif_data (data);
    return NULL;
  }

  return data;
}

/*
 *   free_exif_struct: free allocated structure
 */
void
free_exif_data (TExifData *data)
{
  if (data) {
    g_free (data->aperture);
    g_free (data->camera_model);
    g_free (data->datetime);
    g_free (data->exposure);
    g_free (data->flash);
    g_free (data->focal_length);
    g_free (data->focal_length_35mm);
    g_free (data->iso);

    g_free (data->exif_software);
    g_free (data->exif_imgdesc);
    g_free (data->exif_artist);
    g_free (data->exif_copyright);
    g_free (data->exif_usercomment);

    g_free (data->iptc_objectname);
    g_free (data->iptc_copyright);
    g_free (data->iptc_credit);
    g_free (data->iptc_caption);
    g_free (data->iptc_author);

    g_free (data->jpeg_comment);

    g_free (data);
  }
}


/*
 *   resize_image: resize image pointed by src and save result to dst
 */
gboolean
resize_image (const gchar *src, const gchar *dst,
              unsigned long size_x, unsigned long size_y,
              int quality,
              gboolean thumbnail,
              ThumbnailSquareType squared_thumbnail_type)
{
  MagickWand *magick_wand;
  ExceptionType severity;
  unsigned long w;
  unsigned long h;
  unsigned long amount;
  gchar *description;

  /*  Read an image.  */
  magick_wand = NewMagickWand();
  if (MagickReadImage (magick_wand, src) == MagickFalse) {
    description = MagickGetException (magick_wand, &severity);
    log_error ("Error reading image: %s %s %ld %s\n", GetMagickModule(), description);
    MagickRelinquishMemory (description);
    return FALSE;
  }

  /*  Don't resize if smaller than desired size  */
  if (MagickGetImageWidth (magick_wand) > size_x ||
      MagickGetImageHeight (magick_wand) > size_y)
  {
    /*  Process thumbnail if required  */
    if (thumbnail) {
      switch (squared_thumbnail_type) {
        case THUMBNAIL_SQUARE_TYPE_SIMPLE:
          w = MagickGetImageWidth (magick_wand);
          h = MagickGetImageHeight (magick_wand);
          amount = MAX (w, h) * SQUARED_SIMPLE_SHAVE_AMOUNT / 100;
          amount = MIN (w - 2*amount, h - 2*amount);
          MagickCropImage (magick_wand, amount, amount, (w - amount) / 2, (h - amount) / 2);
          break;
        default:
          break;
      }
      MagickThumbnailImage (magick_wand, size_x, size_y);
    }
    else
      MagickResizeImage (magick_wand, size_x, size_y, LanczosFilter, 1.0);
  }

  MagickSetImageCompressionQuality (magick_wand, quality);

  /*  Write the image and destroy it.  */
  if (MagickWriteImage (magick_wand, dst) == MagickFalse) {
    description = MagickGetException (magick_wand, &severity);
    log_error ("Error writing image: %s %s %ld %s\n", GetMagickModule(), description);
    MagickRelinquishMemory (description);
    return FALSE;
  }

  magick_wand = DestroyMagickWand (magick_wand);

  return TRUE;
}


/*
 *   get_image_sizes: retrieve image dimensions
 */
void
get_image_sizes (const gchar *img,
                 unsigned long *width, unsigned long *height)
{
  MagickWand *magick_wand;
  ExceptionType severity;
  gchar *description;

  *width = -1;
  *height = -1;

  /*  Read an image.  */
  magick_wand = NewMagickWand();
  if (MagickPingImage (magick_wand, img) == MagickFalse) {
    description = MagickGetException (magick_wand, &severity);
    log_error ("Error reading image info: %s %s %ld %s\n", GetMagickModule(), description);
    MagickRelinquishMemory(description);
    return;
  }
  *width = MagickGetImageWidth (magick_wand);
  *height = MagickGetImageHeight (magick_wand);

  magick_wand = DestroyMagickWand (magick_wand);
}


/*
 *   calculate_sizes: calculate maximal image sizes within specified limits keeping aspect ratio
 */
void
calculate_sizes (const unsigned long max_width, const unsigned long max_height,
                 unsigned long *width, unsigned long *height)
{
  if (max_width > *width && max_height > *height)
	  return;

  double max_ratio = (double) max_width / (double) max_height;
  double real_ratio = (double) *width / (double) *height;

  if (*width > *height && max_ratio <= real_ratio) {
    *height = (unsigned long) (max_width / real_ratio);
    *width = max_width;
  } else {
    *width = (unsigned long) (max_height * real_ratio);
    *height = max_height;
  }
}


/*
 *   modify_exif: - strip thumbnail stored in EXIF table
 *                - add copyright to Exif::Image::Copyright and Iptc::Application2::Copyright
 */
void
modify_exif (const gchar *filename, gboolean strip_thumbnail, const gchar *add_copyright)
{
  gboolean modified;

  if (! strip_thumbnail && add_copyright == NULL)
    return;

  modified = FALSE;
  try {
    Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open (filename);
    g_assert (image.get() != 0);

    image->readMetadata();
    Exiv2::ExifData &exifData = image->exifData();
    if (add_copyright) {
      exifData["Exif.Image.Copyright"] = add_copyright;
      image->iptcData()["Iptc.Application2.Copyright"] = add_copyright;
      modified = TRUE;
    }

    if (strip_thumbnail && ! exifData.empty())  {
#ifdef HAVE_EXIFTHUMB
      Exiv2::ExifThumb exifThumb(image->exifData());
      std::string thumbExt = exifThumb.extension();
#else
      std::string thumbExt = exifData.thumbnailExtension();
#endif

      if (! thumbExt.empty()) {
#ifdef HAVE_EXIFTHUMB
        exifThumb.erase();
#else
        exifData.eraseThumbnail();
#endif
        modified = TRUE;
      }
    }

    if (modified)
      image->writeMetadata();
  }
  catch (Exiv2::AnyError& e)
  {
    log_error ("modify_exif: Caught Exiv2 exception: '%s'\n", e.what());
  }
}
