/*------------------------------------------------------------------------------
 src/plugin/djImageIoExr/load.cxx
 
 Copyright (c) 2004-2006 Darby Johnston.
 All rights reserved.
 Use is subject to license terms.
------------------------------------------------------------------------------*/

//#define DJ_DBG
#include "io.h"
#include <half.h>
#include <ImathBox.h>
#include <ImfChannelList.h>
#include <ImfStandardAttributes.h>
#include <ImfInputFile.h>

extern "C" {
const dj::List<dj::ImageLoad *> djImageLoad()
{
  dj::List<dj::ImageLoad *> out;
  out += new djImageIoExr::Load;
  return out;
}
}

namespace djImageIoExr {

Load::Load() :
  _f(0)
{}

Load::~Load()
{
  if (_f) delete _f;
}

bool Load::open(const Str & fileName, ImageIoInfo * info)
{
  DJ_DBG_FNC("djImageIoExr::Load::open");
  DJ_DBG_PRN("file name = " << fileName);
  
  _fileInfo.set(fileName);
  if (
    ! imageOpen(_fileInfo.get(_fileInfo.isSeq() ? _fileInfo.start() : -1), info)
  ) return false;
  
  if (_fileInfo.isSeq()) info->time(_fileInfo.seq());
  
  return true;
}

bool Load::imageOpen(const Str & fileName, ImageIoInfo * info)
{
  DJ_DBG_FNC("djImageIoExr::Load::imageOpen");
  DJ_DBG_PRN("file name = " << fileName);
  
  bool out = false;
try {

  // Open file.
  //
  if (_f) delete _f;
  _f = new Imf::InputFile(fileName.toAscii());

  // Setup information.
  //
  const Imath::Box2i dw = _f->header().dataWindow();
  info->size(Vec2i(dw.max.x - dw.min.x + 1, dw.max.y - dw.min.y + 1));

  const Imf::ChannelList & channelList = _f->header().channels();
  Str channelListStr;
  _pixelType = Imf::HALF;
  for (
    Imf::ChannelList::ConstIterator i = channelList.begin();
    i != channelList.end(); 
    ++i
  ) {
    channelListStr += Str(i.name());
    _pixelType = max(i.channel().type, _pixelType);
  }
  DJ_DBG_PRN("channels = " << channelListStr);
  DJ_DBG_PRN("pixel type = " << _pixelType);

  bool l = false, rgb = false, a = false;
  for (StrIt it = channelListStr; it.isValid(); ++it)
    if ('Y' == it()) l = true;
    else if ('R' == it()) rgb = true;
    else if ('G' == it()) rgb = true;
    else if ('B' == it()) rgb = true;
    else if ('A' == it()) a = true;
  if (l) {
    _format = a ? PIXEL_LA : PIXEL_L;
  } else if (rgb) {
    _format = a ? PIXEL_RGBA : PIXEL_RGB;
  }
  info->pixel(Pixel(_format, PIXEL_F32));
  
  // Tags.
  //
  ImageTagList tagList;
  
  if (Imf::hasOwner(_f->header()))
    tagList[imageTagStr()[IMAGE_TAG_CREATOR]] =
      deflateStr(Imf::ownerAttribute(_f->header()).value().c_str());
  if (Imf::hasComments(_f->header()))
    tagList[imageTagStr()[IMAGE_TAG_DESCRIPTION]] =
      deflateStr(Imf::commentsAttribute(_f->header()).value().c_str());
  if (Imf::hasCapDate(_f->header()))
    tagList[imageTagStr()[IMAGE_TAG_TIME_STAMP]] =
      deflateStr(Imf::capDateAttribute(_f->header()).value().c_str());
  if (Imf::hasutcOffset(_f->header()))
    tagList[imageTagStr()[IMAGE_TAG_UTC_OFFSET]] =
      deflateStr(Imf::utcOffsetAttribute(_f->header()).value());
  
  if (Imf::hasLongitude(_f->header()))
    tagList[tagStr()[TAG_LONGITUDE]] =
      deflateStr(Imf::longitudeAttribute(_f->header()).value());
  if (Imf::hasLatitude(_f->header()))
    tagList[tagStr()[TAG_LATITUDE]] =
      deflateStr(Imf::latitudeAttribute(_f->header()).value());
  if (Imf::hasAltitude(_f->header()))
    tagList[tagStr()[TAG_ALTITUDE]] =
      deflateStr(Imf::altitudeAttribute(_f->header()).value());
  
  if (Imf::hasFocus(_f->header()))
    tagList[tagStr()[TAG_FOCUS]] =
      deflateStr(Imf::focusAttribute(_f->header()).value());
  if (Imf::hasExpTime(_f->header()))
    tagList[tagStr()[TAG_EXPOSURE]] =
      deflateStr(Imf::expTimeAttribute(_f->header()).value());
  if (Imf::hasAperture(_f->header()))
    tagList[tagStr()[TAG_APERTURE]] =
      deflateStr(Imf::apertureAttribute(_f->header()).value());
  if (Imf::hasIsoSpeed(_f->header()))
    tagList[tagStr()[TAG_ISO_SPEED]] =
      deflateStr(Imf::isoSpeedAttribute(_f->header()).value());

  if (Imf::hasChromaticities(_f->header())) {
    const Imf::Chromaticities tmp =
      Imf::chromaticitiesAttribute(_f->header()).value();
    tagList[tagStr()[TAG_CHROMATICITIES]] =
      deflateStr(tmp.red.x) + deflateStr(tmp.red.y) +
      deflateStr(tmp.green.x) + deflateStr(tmp.green.y) +
      deflateStr(tmp.blue.x) + deflateStr(tmp.blue.y) +
      deflateStr(tmp.white.x) + deflateStr(tmp.white.y);
  }
  if (Imf::hasWhiteLuminance(_f->header()))
    tagList[tagStr()[TAG_WHITE_LUMINANCE]] =
      deflateStr(Imf::whiteLuminanceAttribute(_f->header()).value());
  if (Imf::hasXDensity(_f->header()))
    tagList[tagStr()[TAG_X_DENSITY]] =
      deflateStr(Imf::xDensityAttribute(_f->header()).value());
  
  if (Imf::hasKeyCode(_f->header())) {
    const Imf::KeyCode tmp = Imf::keyCodeAttribute(_f->header()).value();
    tagList[imageTagStr()[IMAGE_TAG_KEY_CODE]] = deflateStr(
      keyCodeToStr(
        tmp.filmMfcCode(),
        tmp.filmType(),
        tmp.prefix(),
        tmp.count(),
        tmp.perfOffset()
      )
    );
  }
  if (Imf::hasTimeCode(_f->header())) {
    const Imf::TimeCode tmp = Imf::timeCodeAttribute(_f->header()).value();
    tagList[imageTagStr()[IMAGE_TAG_TIME_CODE]] =
      deflateStr(timeCodeToStr(tmp.timeAndFlags()));
  }
  
  DJ_DBG_PRN("tags = " << tagList);
  info->tagList(tagList);

  out = true;
} catch (const std::exception &) {}
  return out;
}

bool Load::imageBegin(int frame, ImageIoInfo * info)
{
  DJ_DBG_FNC("djImageIoExr::Load::imageBegin");
  DJ_DBG_PRN("frame = " << frame);
  
  if (
    ! imageOpen(_fileInfo.get(frame == -1 ? _fileInfo.start() : frame), info)
  ) return false;
  
  return true;
}

bool Load::imageGet(PixelData * out, int proxyScale)
{
  DJ_DBG_FNC("djImageIoExr::Load::imageGet");
  DJ_DBG_PRN("out = " << *out);
  DJ_DBG_PRN("proxy = " << proxyScale);

  bool r = false;
try {

  const Imath::Box2i dw = _f->header().dataWindow();
  const int w = dw.max.x - dw.min.x + 1;
  const int channels = pixelSize(_format);
  int bytes = 0;
  if (Imf::HALF == _pixelType) bytes = 2;
  else if (Imf::FLOAT == _pixelType) bytes = 4;
  const int strideX = channels * bytes;
  const int strideY = w * strideX;
  Str channelListStr;
  switch (_format) {
    case PIXEL_L: channelListStr = Str("Y"); break;
    case PIXEL_LA: channelListStr = Str("YA"); break;
    case PIXEL_RGB: channelListStr = Str("RGB"); break;
    case PIXEL_RGBA: channelListStr = Str("RGBA"); break;
  }
  DJ_DBG_PRN("channels = " << channels);
  DJ_DBG_PRN("bytes = " << bytes);
  DJ_DBG_PRN("stride = " << strideX << " " << strideY);

  // Read.
  //
  PixelData scanline(Vec2i(w, 1), Pixel(_format, PIXEL_F32));
  int y = 0, fileY = 0;
  for (; y < out->height(); ++y, fileY += proxyScale) {
    //DJ_DBG_PRN("y = " << y << " (" << fileY << ")");

    Imf::FrameBuffer frameBuffer;
    for (StrIt it = channelListStr; it.isValid(); ++it)
      frameBuffer.insert(
        Str(it()).toAscii(),
        Imf::Slice(
          _pixelType,
          reinterpret_cast<char *>(scanline.data()) +
            -(dw.min.y + fileY) * strideY +
            -dw.min.x * strideX +
            bytes * it.count(),
          strideX, strideY
        )
      );
    _f->setFrameBuffer(frameBuffer);
    _f->readPixels(dw.min.y + fileY);
      
    // Convert.
    //
    if (Imf::HALF == _pixelType) {
      int size = w * channels;
      const half * inP =
        reinterpret_cast<half *>(scanline.data()) + size - 1;
      PixelF32 * outP =
        reinterpret_cast<PixelF32 *>(scanline.data()) + size - 1;
      while (size--) *outP-- = *inP--;
    }
    pixelCopy(
      scanline.data(), out->data(0, out->height() - 1 - y),
      out->width(), scanline.pixel(), out->pixel(),
      proxyScale
    );
  }

  r = y == out->height();
} catch (const std::exception & in) {}
  delete _f;
  _f = 0;
  return r;
}

}
