#include "config.h"

/*  These libgimp includes are not needed here at all, but this is a
 *  convenient place to make sure the public libgimp headers are
 *  C++-clean. The C++ compiler will choke on stuff like naming
 *  a struct member or parameter "private".
 */
#include "libgimp/gimp.h"
#include "libgimp/gimpui.h"

#include "openexr-wrapper.h"

#include <ImfInputFile.h>
#include <ImfChannelList.h>
#include <ImfRgbaFile.h>
#include <ImfRgbaYca.h>
#include <ImfStandardAttributes.h>

#include <string>

using namespace Imf;
using namespace Imf::RgbaYca;
using namespace Imath;

struct _EXRLoader
{
  _EXRLoader(const char* filename) :
    refcount_(1),
    file_(filename),
    data_window_(file_.header().dataWindow()),
    channels_(file_.header().channels())
  {
    const Channel* chan;

    if (channels_.findChannel("R") ||
        channels_.findChannel("G") ||
        channels_.findChannel("B"))
      {
        format_string_ = "RGB";
        image_type_ = IMAGE_TYPE_RGB;

        if ((chan = channels_.findChannel("R")))
          pt_ = chan->type;
        else if ((chan = channels_.findChannel("G")))
          pt_ = chan->type;
        else
          pt_ = channels_.findChannel("B")->type;
      }
    else if (channels_.findChannel("Y") &&
             (channels_.findChannel("RY") ||
              channels_.findChannel("BY")))
      {
        format_string_ = "RGB";
        image_type_ = IMAGE_TYPE_RGB;

        pt_ = channels_.findChannel("Y")->type;

        // FIXME: no chroma handling for now.
        throw;
      }
    else if (channels_.findChannel("Y"))
      {
        format_string_ = "Y";
        image_type_ = IMAGE_TYPE_GRAY;

        pt_ = channels_.findChannel("Y")->type;
      }
    else
      {
        throw;
      }

    if (channels_.findChannel("A"))
      {
        format_string_.append("A");
        has_alpha_ = true;
      }
    else
      {
        has_alpha_ = false;
      }

    switch (pt_)
      {
      case UINT:
        format_string_.append(" u32");
        bpc_ = 4;
        break;
      case HALF:
        format_string_.append(" half");
        bpc_ = 2;
        break;
      case FLOAT:
      default:
        format_string_.append(" float");
        bpc_ = 4;
      }
  }

  int readPixelRow(char* pixels,
                   int bpp,
                   int row)
  {
    const int actual_row = data_window_.min.y + row;
    FrameBuffer fb;
    // This is necessary because OpenEXR expects the buffer to begin at
    // (0, 0). Though it probably results in some unmapped address,
    // hopefully OpenEXR will not make use of it. :/
    char* base = pixels - (data_window_.min.x * bpp);

    switch (image_type_)
      {
      case IMAGE_TYPE_GRAY:
        fb.insert("Y", Slice(pt_, base, bpp, 0, 1, 1, 0.5));
        if (hasAlpha())
          {
            fb.insert("A", Slice(pt_, base + bpc_, bpp, 0, 1, 1, 1.0));
          }
        break;

      case IMAGE_TYPE_RGB:
      default:
        fb.insert("R", Slice(pt_, base + (bpc_ * 0), bpp, 0, 1, 1, 0.0));
        fb.insert("G", Slice(pt_, base + (bpc_ * 1), bpp, 0, 1, 1, 0.0));
        fb.insert("B", Slice(pt_, base + (bpc_ * 2), bpp, 0, 1, 1, 0.0));
        if (hasAlpha())
          {
            fb.insert("A", Slice(pt_, base + (bpc_ * 3), bpp, 0, 1, 1, 1.0));
          }
      }

    file_.setFrameBuffer(fb);
    file_.readPixels(actual_row);

    return 0;
  }

  int getWidth() const {
    return data_window_.max.x - data_window_.min.x + 1;
  }

  int getHeight() const {
    return data_window_.max.y - data_window_.min.y + 1;
  }

  EXRPrecision getPrecision() const {
    EXRPrecision prec;

    switch (pt_)
      {
      case UINT:
        prec = PREC_UINT;
        break;
      case HALF:
        prec = PREC_HALF;
        break;
      case FLOAT:
      default:
        prec = PREC_FLOAT;
      }

    return prec;
  }

  EXRImageType getImageType() const {
    return image_type_;
  }

  int hasAlpha() const {
    return has_alpha_ ? 1 : 0;
  }

  cmsHPROFILE getICCProfile() const {
    Chromaticities chromaticities;
    float whiteLuminance = 1.0;

    cmsHPROFILE profile = NULL;

    if (hasChromaticities (file_.header ()))
      chromaticities = Imf::chromaticities (file_.header ());
    else
      return NULL;

    if (Imf::hasWhiteLuminance (file_.header ()))
      whiteLuminance = Imf::whiteLuminance (file_.header ());
    else
      return NULL;

#if 0
    std::cout << "hasChromaticities: "
              << hasChromaticities (file_.header ())
              << std::endl;
    std::cout << "hasWhiteLuminance: "
              << hasWhiteLuminance (file_.header ())
              << std::endl;
    std::cout << chromaticities.red << std::endl;
    std::cout << chromaticities.green << std::endl;
    std::cout << chromaticities.blue << std::endl;
    std::cout << chromaticities.white << std::endl;
    std::cout << std::endl;
#endif

    // TODO: maybe factor this out into libgimpcolor/gimpcolorprofile.h ?
    cmsCIExyY whitePoint = { chromaticities.white.x,
                             chromaticities.white.y,
                             whiteLuminance };
    cmsCIExyYTRIPLE CameraPrimaries = { { chromaticities.red.x,
                                          chromaticities.red.y,
                                          whiteLuminance },
                                        { chromaticities.green.x,
                                          chromaticities.green.y,
                                          whiteLuminance },
                                        { chromaticities.blue.x,
                                          chromaticities.blue.y,
                                          whiteLuminance } };

    double Parameters[2] = { 1.0, 0.0 };
    cmsToneCurve *Gamma[3];
    Gamma[0] = Gamma[1] = Gamma[2] = cmsBuildParametricToneCurve(0,
                                                                 1,
                                                                 Parameters);
    profile = cmsCreateRGBProfile (&whitePoint, &CameraPrimaries, Gamma);
    cmsFreeToneCurve (Gamma[0]);
    if (profile == NULL) return NULL;

    cmsSetProfileVersion (profile, 2.1);
    cmsMLU *mlu0 = cmsMLUalloc (NULL, 1);
    cmsMLUsetASCII (mlu0, "en", "US", "(GIMP internal)");
    cmsMLU *mlu1 = cmsMLUalloc(NULL, 1);
    cmsMLUsetASCII (mlu1, "en", "US", "color profile from EXR chromaticities");
    cmsMLU *mlu2 = cmsMLUalloc(NULL, 1);
    cmsMLUsetASCII (mlu2, "en", "US", "color profile from EXR chromaticities");
    cmsWriteTag (profile, cmsSigDeviceMfgDescTag, mlu0);
    cmsWriteTag (profile, cmsSigDeviceModelDescTag, mlu1);
    cmsWriteTag (profile, cmsSigProfileDescriptionTag, mlu2);
    cmsMLUfree (mlu0);
    cmsMLUfree (mlu1);
    cmsMLUfree (mlu2);

    return profile;
  }

  size_t refcount_;
  InputFile file_;
  const Box2i data_window_;
  const ChannelList& channels_;
  PixelType pt_;
  int bpc_;
  EXRImageType image_type_;
  bool has_alpha_;
  std::string format_string_;
};

EXRLoader*
exr_loader_new (const char *filename)
{
  EXRLoader* file;

  // Don't let any exceptions propagate to the C layer.
  try
    {
      file = new EXRLoader(filename);
    }
  catch (...)
    {
      file = NULL;
    }

  return file;
}

EXRLoader*
exr_loader_ref (EXRLoader *loader)
{
  ++loader->refcount_;
  return loader;
}

void
exr_loader_unref (EXRLoader *loader)
{
  if (--loader->refcount_ == 0)
    {
      delete loader;
    }
}

int
exr_loader_get_width (EXRLoader *loader)
{
  int width;
  // Don't let any exceptions propagate to the C layer.
  try
    {
      width = loader->getWidth();
    }
  catch (...)
    {
      width = -1;
    }

  return width;
}

int
exr_loader_get_height (EXRLoader *loader)
{
  int height;
  // Don't let any exceptions propagate to the C layer.
  try
    {
      height = loader->getHeight();
    }
  catch (...)
    {
      height = -1;
    }

  return height;
}

EXRImageType
exr_loader_get_image_type (EXRLoader *loader)
{
  // This does not throw.
  return loader->getImageType();
}

EXRPrecision
exr_loader_get_precision (EXRLoader *loader)
{
  // This does not throw.
  return loader->getPrecision();
}

int
exr_loader_has_alpha (EXRLoader *loader)
{
  // This does not throw.
  return loader->hasAlpha();
}

cmsHPROFILE
exr_loader_get_icc_profile (EXRLoader *loader)
{
  return loader->getICCProfile ();
}

int
exr_loader_read_pixel_row (EXRLoader *loader,
                           char *pixels,
                           int bpp,
                           int row)
{
  int retval = -1;
  // Don't let any exceptions propagate to the C layer.
  try
    {
      retval = loader->readPixelRow(pixels, bpp, row);
    }
  catch (...)
    {
      retval = -1;
    }

  return retval;
}
