// ExifWriter.h
// Copyright (c) 2009 The Foundry Visionmongers Ltd.  All Rights Reserved.

#include <libexif/exif-data.h>

#include "DDImage/FileWriter.h"
#include "DDImage/MetaData.h"

class ExifWriter : public DD::Image::FileWriter
{
  void addExifEntry(ExifData* exif, int ifd, ExifTag tag, const std::string& value);
  void addExifEntry(ExifData* exif, int ifd, ExifTag tag, int numer, int denom);

public:
  ExifWriter(DD::Image::Write * iop);
  ExifData* makeExifData();
};




template<class T>
static void StringToValue(T& val, const std::string& value)
{
  std::istringstream iss(value);
  iss >> val;
}

template<class T>
void SetRationalFromString(T& val, const std::string& value)
{
  std::string numer = value;
  std::string denom = "1";
  size_t slashPos = value.find('/');
  if (slashPos != std::string::npos) {
    numer = value.substr(0, slashPos);
    denom = value.substr(slashPos + 1);
  }
  val.numerator = atoi(numer.c_str());
  val.denominator = atoi(denom.c_str());
}

static void StringToValue(ExifRational& val, const std::string& value)
{
  SetRationalFromString(val, value);
}

static void StringToValue(ExifSRational& val, const std::string& value)
{
  SetRationalFromString(val, value);
}

template<class BYTETYPE>
void ExifSetByte(unsigned char* b, ExifByteOrder order, BYTETYPE byte)
{
  if(!b)
    return;
  b[0] = byte;
}
                 
#define mFnWriteData(DATATYPE, FUNCTION) \
{ \
DATATYPE data; \
StringToValue(data, value); \
FUNCTION(entry->data, exif_data_get_byte_order(exif), data); \
} \

ExifWriter::ExifWriter(DD::Image::Write * iop)
: DD::Image::FileWriter(iop)
{
}

void ExifWriter::addExifEntry(ExifData*          exif,
                                         int                ifd,
                                         ExifTag            tag,
                                         const std::string& value)
{
  ExifContent* cont = exif->ifd[ifd];
  ExifEntry* entry = exif_entry_new();
  exif_content_add_entry(cont, entry);
  exif_entry_initialize(entry, tag);
  if(entry->format == EXIF_FORMAT_BYTE)
    mFnWriteData(ExifByte, ExifSetByte<ExifByte>)
  else if(entry->format == EXIF_FORMAT_SHORT)
    mFnWriteData(ExifShort, exif_set_short)
  else if(entry->format == EXIF_FORMAT_LONG)
    mFnWriteData(ExifLong, exif_set_long)
  else if(entry->format == EXIF_FORMAT_RATIONAL)
    mFnWriteData(ExifRational, exif_set_rational)
  else if(entry->format == EXIF_FORMAT_SBYTE)
    mFnWriteData(ExifSByte, ExifSetByte<ExifSByte>)
  else if(entry->format == EXIF_FORMAT_SSHORT)
    mFnWriteData(ExifSShort, exif_set_sshort)
  else if(entry->format == EXIF_FORMAT_SLONG)
    mFnWriteData(ExifSLong, exif_set_slong)
  else if(entry->format == EXIF_FORMAT_SRATIONAL)
    mFnWriteData(ExifSRational, exif_set_srational)
  //float and double don't appear to actually be used. The comment in the libexif
  //exif_entry_get_value when an entry has either of these types is
  //What to do here? Line 1093, exif-entry.c
  //so for now, leave them commented out and write it out as ASCII.
  //else if(entry->format == EXIF_FORMAT_FLOAT)
  //  mFnWriteData(float, exif_set_float)
  //else if(entry->format == EXIF_FORMAT_DOUBLE)
  //  mFnWriteData(double, exif_set_double)
  else
  {
    entry->data = (unsigned char*)strdup(value.c_str());
    entry->format = EXIF_FORMAT_ASCII;
    entry->size = unsigned(value.length() + 1);
    entry->components = unsigned(value.length() + 1);
  }
  exif_entry_fix(entry);
  exif_entry_unref(entry);
}

ExifData* ExifWriter::makeExifData()
{
  using namespace DD::Image;
  const MetaData::Bundle& bundle = input0().fetchMetaData(0);

  ExifData* exif = exif_data_new();

  MetaData::Bundle::const_iterator it = bundle.begin();
  while (it != bundle.end()) 
  {
    const std::string& key = it->first;
    std::string value = MetaData::propertyToString(it->second);
    if (key.substr(0, 5) == "exif/") {
      int ifd = atoi(key.c_str() + 5);
      ExifTag tag = exif_tag_from_name(key.c_str() + 7);
      addExifEntry(exif, ifd, tag, value);
    }
    else if (key == MetaData::FOCAL_LENGTH) 
    {
      addExifEntry(exif, EXIF_IFD_EXIF, EXIF_TAG_FOCAL_LENGTH, value);
    }
    else if (key == MetaData::FNUMBER) 
    {
      addExifEntry(exif, EXIF_IFD_EXIF, EXIF_TAG_FNUMBER, value);
    }
    else if (key == MetaData::EXPOSURE) 
    {
      addExifEntry(exif, EXIF_IFD_EXIF, EXIF_TAG_EXPOSURE_TIME, value);
    }
    it++;
  }

  return exif;
}

#undef mFnWriteData
