/*------------------------------------------------------------------------------
 src/lib/djImage/imageIoDpxLoad.cxx
 
 Copyright (c) 2004-2006 Darby Johnston.
 All rights reserved.
 Use is subject to license terms.
------------------------------------------------------------------------------*/

//#define DJ_DBG
#include "djImageIoDpx.h"
#include <djImageOpCore.h>
#include <djImageOpXform.h>

namespace dj {
namespace ImageIoDpx {

Load::Load() :
  _color(COLOR_AUTO),
  _printBlack(95 / 1023.0), _printWhite(685 / 1023.0), _printGamma(2.2),
    _printSoftClip(0 / 1023.0),
  _endian(MSB)
{}

const StrList Load::attrNameList() const { return loadAttrStr(); }

bool Load::attr(const Str & in, const StrList & data)
{
  StrList tmp = data;
  if (compareCase(attrNameList()[ATTR_COLOR], in)) {
    if (! inflateStr(tmp, &_color, colorStr())) return false;
  } else if (compareCase(attrNameList()[ATTR_PRINT_BLACK], in)) {
    if (! inflateStr(tmp, &_printBlack)) return false;
  } else if (compareCase(attrNameList()[ATTR_PRINT_WHITE], in)) {
    if (! inflateStr(tmp, &_printWhite)) return false;
  } else if (compareCase(attrNameList()[ATTR_PRINT_GAMMA], in)) {
    if (! inflateStr(tmp, &_printGamma)) return false;
  } else if (compareCase(attrNameList()[ATTR_PRINT_SOFT_CLIP], in)) {
    if (! inflateStr(tmp, &_printSoftClip)) return false;
  } else if (compareCase(attrNameList()[ATTR_LUT], in)) {
    if (! inflateStr(tmp, &_lutFileName)) return false;
  } else
    return false;
  return true;
}

const StrList Load::attr(const Str & in) const
{
  StrList out;
  if (compareCase(attrNameList()[ATTR_COLOR], in))
    out += deflateStr(_color, colorStr());
  else if (compareCase(attrNameList()[ATTR_PRINT_BLACK], in))
    out += deflateStr(_printBlack);
  else if (compareCase(attrNameList()[ATTR_PRINT_WHITE], in))
    out += deflateStr(_printWhite);
  else if (compareCase(attrNameList()[ATTR_PRINT_GAMMA], in))
    out += deflateStr(_printGamma);
  else if (compareCase(attrNameList()[ATTR_PRINT_SOFT_CLIP], in))
    out += deflateStr(_printSoftClip);
  else if (compareCase(attrNameList()[ATTR_LUT], in))
    out += deflateStr(_lutFileName);
  return out;
}

bool Load::open(const Str & fileName, ImageIoInfo * info)
{
  DJ_DBG_FNC("dj::ImageIoDpx::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("dj::ImageIoDpx::Load::imageOpen");
  DJ_DBG_PRN("file name = " << fileName);
  
  // Open file.
  //
  if (! _f.open(fileName, FileIo::READ)) return false;
  
  // Magic.
  //
  char magic [] = "    ";
  if (! _f.get(magic, 4)) return false;
  DJ_DBG_PRN("magic = " << magic);
  if (0 == memCompare(magic, "SDPX", 4)) _endian = MSB;
  else if (0 == memCompare(magic, "XPDS", 4)) _endian = LSB;
  else return false;
  _f.endian(_endian != endian());
  
  // Read header.
  //
  ImageTagList tagList;
  if (! _header.get(&_f, &tagList)) return false;
  //dbg(_header);
  
  // Setup information.
  //
  info->size(Vec2i(_header.image.size[0], _header.image.size[1]));
  PIXEL_FORMAT format = PIXEL_FORMAT(0);
  int type = -1;
  _channels = INTERLEAVE;
  switch (_header.image.elemSize) {
  
    case 1:
      switch (_header.imageElem[0].descriptor) {
        case DPX_DESCRIPTOR_L: format = PIXEL_L; break;
        case DPX_DESCRIPTOR_RGB: format = PIXEL_RGB; break;
        case DPX_DESCRIPTOR_RGBA: format = PIXEL_RGBA; break;
        default: return false;
      }
      break;
      
    case 2:
      if (
        DPX_DESCRIPTOR_L == _header.imageElem[0].descriptor &&
        DPX_DESCRIPTOR_A == _header.imageElem[1].descriptor
      ) {
        format = PIXEL_LA;
        _channels = PLANAR;
      } else
        return false;
      break;
      
    case 3:
      if (
        DPX_DESCRIPTOR_R == _header.imageElem[0].descriptor &&
        DPX_DESCRIPTOR_G == _header.imageElem[1].descriptor &&
        DPX_DESCRIPTOR_B == _header.imageElem[2].descriptor
      ) {
        format = PIXEL_RGB;
        _channels = PLANAR;
      } else
        return false;
      break;
      
    case 4:
      if (
        DPX_DESCRIPTOR_R == _header.imageElem[0].descriptor &&
        DPX_DESCRIPTOR_G == _header.imageElem[1].descriptor &&
        DPX_DESCRIPTOR_B == _header.imageElem[2].descriptor &&
        DPX_DESCRIPTOR_A == _header.imageElem[3].descriptor
      ) {
        format = PIXEL_RGBA;
        _channels = PLANAR;
      } else
        return false;
      break;
    default: return false;
  }
  _pixel.format(format);
  
  switch (_header.imageElem[0].bitDepth) {
    case 8:
    case 10:
    case 12:
    case 16: break;
  }
  switch (_header.imageElem[0].bitDepth) {
    case 8: type = PIXEL_U8; break;
    case 10:
    case 12:
    case 16: type = PIXEL_U16; break;
    case 32: break;
  }
  if (-1 == type) return false;
  _pixel.type(PIXEL_TYPE(type));
  
  DJ_DBG_PRN("pixel = " << _pixel);
  DJ_DBG_PRN("channels = " << channelsStr()[_channels]);
  
  info->pixel(_pixel);
  info->tagList(tagList);

  // XXX Only uncompressed data for now.
  //
  for (int i = 0; i < int(_header.image.elemSize); ++i)
    if (_header.imageElem[i].encoding != 0) return false;

  return true;
}

bool Load::imageBegin(int frame, ImageIoInfo * info)
{
  DJ_DBG_FNC("dj::ImageIoDpx::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("dj::ImageIoDpx::Load::imageGet");
  DJ_DBG_PRN("out = " << *out);
  DJ_DBG_PRN("proxy = " << proxyScale);
  
  const int w = _header.image.size[0], h = _header.image.size[1];
  bool flipX = false, flipY = false;
  switch (_header.image.orient) {
    case DPX_ORIENT_LEFT_RIGHT_TOP_BOTTOM: flipY = true; break;
    case DPX_ORIENT_RIGHT_LEFT_TOP_BOTTOM: flipX = true; break;
    case DPX_ORIENT_LEFT_RIGHT_BOTTOM_TOP: break;
    case DPX_ORIENT_RIGHT_LEFT_BOTTOM_TOP: flipX = flipY = true; break;
    case DPX_ORIENT_TOP_BOTTOM_LEFT_RIGHT:
    case DPX_ORIENT_TOP_BOTTOM_RIGHT_LEFT:
    case DPX_ORIENT_BOTTOM_TOP_LEFT_RIGHT:
    case DPX_ORIENT_BOTTOM_TOP_RIGHT_LEFT: break;
  }
  const bool planar = _channels == PLANAR;
  const int planes = planar ? pixelSize(_pixel.format()) : 1;
  const PIXEL_FORMAT format = planar ? PIXEL_L : _pixel.format();
  const PIXEL_TYPE type = out->type();
  const int channels = pixelSize(format);
  const int bitDepth = _header.imageElem[0].bitDepth;
  const bool endian = _endian != dj::endian() && bitDepth != 8;
  DJ_DBG_PRN("planar = " << planar);
  DJ_DBG_PRN("planes = " << planes);
  DJ_DBG_PRN("channels = " << channels);
  DJ_DBG_PRN("bit depth = " << bitDepth);
  DJ_DBG_PRN("endian = " << endian);
  
  bool buffer = false;
  if (planar || proxyScale > 1 || flipX) buffer = true;
  DJ_DBG_PRN("buffer = " << buffer);
  
  // Read.
  //
  uint8_t * fileScanline = 0;
  int c = 0;
  for (; c < planes; ++c) {
    DJ_DBG_PRN("c = " << c);
  
    // Temporary buffers.
    //
    PixelData scanline;
    if (buffer) scanline.set(Vec2i(w, 1), Pixel(format, type));
    const int fileScanlineSize = dpxScanlineSize(
      w, channels, bitDepth,
      0 == _header.imageElem[c].packing, _header.imageElem[c].linePadding
    );
    if (fileScanline) {
      delete [] fileScanline;
      fileScanline = 0;
    }
    if (fileScanlineSize) {
#     ifndef DJ_MMAP
      fileScanline = new uint8_t [fileScanlineSize];
#     endif
    }
    DJ_DBG_PRN("scanline = " << scanline);
    DJ_DBG_PRN("file scanline size = " << fileScanlineSize);
  
    // Setup LUT.
    //
    int lutSize = 0;
    switch (bitDepth) {
      case 8: lutSize = 256; break;
      case 10: lutSize = 1024; break;
      case 12: lutSize = 4096; break;
      case 16: lutSize = 65536; break;
    }
    Image lut(Vec2i(lutSize, 1), Pixel(PIXEL_L, type));
    if (
      (
        COLOR_AUTO == _color &&
        DPX_TRANSFER_PRINT == _header.imageElem[c].transfer
      ) ||
      COLOR_PRINT == _color
    ) {
    
      DJ_DBG_PRN("lut = print");
      Image tmp(Vec2i(lutSize, 1), Pixel(PIXEL_L, PIXEL_F32));
      dpxLutUnprint(
        reinterpret_cast<PixelF32 *>(tmp.data()), lutSize,
        _printBlack, _printWhite, _printGamma, _printSoftClip
      );
      pixelCopy(tmp, &lut);
      
    } else {
  
      DJ_DBG_PRN("lut = linear");
      pixelGradient(lut.data(), lut.width(), lut.pixel());
    }
    DJ_DBG_PRN("lut = " << lut);

    // Seek to beginning of image element.
    //
    if (! _f.seek(_header.imageElem[c].dataOffset, FileIo::SET))
      break;
      
    int y = 0, fileY = 0;
    for (; y < out->height(); ++y, fileY += proxyScale) {
      //DJ_DBG_PRN("y = " << y << " (" << fileY << ")");
      
      const uint8_t * inP = fileScanline;
      uint8_t * outP = out->data(0, flipY ? (out->height() - 1 - y) : y);
      uint8_t * p = buffer ? scanline.data() : outP;

      // Read.
      //
#     ifdef DJ_MMAP
      inP = _f.mmapP();
      if (! _f.seek(fileScanlineSize, FileIo::CURRENT))
        break;
#     else
      if (! _f.get(fileScanline, fileScanlineSize))
        break;
#     endif

      // Convert.
      //
      bool packing = true;
      switch (bitDepth) {
        case 10:
        case 12: 
          if (1 == _header.imageElem[c].packing)
            packing = false;
          break;
      }
      if (packing)
        dpxScanlineUnpack(
          inP, p, lut.data(), w * channels, type, bitDepth, endian
        );
      else
        dpxScanlineUnfill(
          inP, p, lut.data(), w * channels, type, bitDepth, endian
        );
      if (buffer)
        pixelCopy(
          scanline.data(), outP + c * pixelSize(type),
          out->width(), scanline.pixel(), out->pixel(),
          proxyScale, flipX, planar ? 1 : 0
        );
        
      // Skip scanlines.
      //
      const int skip = min(proxyScale - 1, h - (fileY + 1));
      if (skip)
        if (! _f.seek(fileScanlineSize * skip, FileIo::CURRENT)) break;
    }
    if (y < out->height()) break;
  }
  if (fileScanline) delete [] fileScanline;
  
  _f.close();
  return planes == c;
}

}
}
