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

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

namespace dj {
namespace ImageIoDpx {
  
Save::Save() :
  _color(COLOR_PRINT),
  _printBlack(95 / 1023.0), _printWhite(685 / 1023.0), _printGamma(2.2),
  _bitDepth(U10), _channels(INTERLEAVE), _pack(false),
    _endian(MSB)
{}

const StrList Save::attrNameList() const { return saveAttrStr(); }

bool Save::attr(const Str & in, const StrList & data)
{
  DJ_DBG_FNC("dj::ImageIoDpx::attr");
  DJ_DBG_PRN("in = " << in);
  DJ_DBG_PRN("data = " << 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_LUT], in)) {
    if (! inflateStr(tmp, &_lutFileName)) return false;
  } else if (compareCase(attrNameList()[ATTR_BIT_DEPTH], in)) {
    if (! inflateStr(tmp, &_bitDepth, bitDepthStr())) return false;
  } else if (compareCase(attrNameList()[ATTR_CHANNELS], in)) {
    if (! inflateStr(tmp, &_channels, channelsStr())) return false;
  } else if (compareCase(attrNameList()[ATTR_PACK], in)) {
    if (! inflateStr(tmp, &_pack)) return false;
  } else if (compareCase(attrNameList()[ATTR_ENDIAN], in)) {
    if (! inflateStr(tmp, &_endian, endianStr())) return false;
  } else
    return false;
  return true;
}

const StrList Save::attr(const Str & in) const
{
  DJ_DBG_FNC("dj::ImageIoDpx::attr");
  DJ_DBG_PRN("in = " << in);
  
  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_LUT], in))
    out += deflateStr(_lutFileName);
  else if (compareCase(attrNameList()[ATTR_BIT_DEPTH], in))
    out += deflateStr(_bitDepth, bitDepthStr());
  else if (compareCase(attrNameList()[ATTR_CHANNELS], in))
    out += deflateStr(_channels, channelsStr());
  else if (compareCase(attrNameList()[ATTR_PACK], in))
    out += deflateStr(_pack);
  else if (compareCase(attrNameList()[ATTR_ENDIAN], in))
    out += deflateStr(_endian, endianStr());
  return out;
}

bool Save::open(const Str & fileName, const ImageIoInfo & info)
{
  DJ_DBG_FNC("dj::ImageIoDpx::Save::open");
  DJ_DBG_PRN("file name = " << fileName);
  
  _fileInfo.set(fileName);
  _info.size(info.size());
  _info.format(info.format());
  switch (info.type()) {
    case PIXEL_U8: _info.type(PIXEL_U8); break;
    case PIXEL_U16:
    case PIXEL_F32: _info.type(PIXEL_U16); break;
  }
  
  return true;
}

bool Save::imageOpen(const Str & fileName, const Image & in)
{
  DJ_DBG_FNC("dj::ImageIoDpx::Save::imageOpen");
  DJ_DBG_PRN("file name = " << fileName);
  
  // Open file.
  //
  _f.endian(_endian != endian());
  if (! _f.open(fileName, FileIo::WRITE)) return false;
  
  // Write header.
  //
  if (! _f.set(_endian == MSB ? "SDPX" : "XPDS", 4)) return false;
  if (
    ! _header.set(
      &_f, _info, in.tagList(), _color, _bitDepth, _channels, _pack
    )
  ) return false;
  
  return true;
}

bool Save::imageBegin(const Image & in, int frame)
{
  DJ_DBG_FNC("dj::ImageIoDpx::Save::imageBegin");
  DJ_DBG_PRN("in = " << in);
  DJ_DBG_PRN("frame = " << frame);
  
  if (! imageOpen(_fileInfo.get(frame == -1 ? _fileInfo.start() : frame), in))
    return false;
  
  return true;
}

bool Save::imageSet(const PixelData & in)
{
  DJ_DBG_FNC("dj::ImageIoDpx::Save::imageSet");
  DJ_DBG_PRN("in = " << in);

  const int w = _info.width(), h = _info.height();
  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 || PIXEL_LA == _info.format();
  const int planes = planar ? pixelSize(_info.format()) : 1;
  const PIXEL_FORMAT format = planar ? PIXEL_L : _info.format();
  const PIXEL_TYPE type = _info.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);
  
  // Convert.
  //
  PixelData framebuf(_info);
  framebuf.zero();
  pixelCopy(in, &framebuf);
  
  // Write.
  //
  uint8_t * fileScanline = 0;
  int c = 0;
  for (; c < planes; ++c) {
    DJ_DBG_PRN("c = " << c);

    // Temporary buffers.
    //
    PixelData scanline(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)
      fileScanline = new uint8_t [fileScanlineSize];
    DJ_DBG_PRN("scanline = " << scanline);
    DJ_DBG_PRN("file scanline size = " << fileScanlineSize);
    
    // Setup LUT.
    //
    int lutSize = 0;
    PIXEL_TYPE lutType = PIXEL_TYPE(0);
    switch (type) {
      case PIXEL_U8: lutSize = pixelU8Range; break;
      case PIXEL_U16: lutSize = pixelU16Range; break;
      case PIXEL_F32: break;
    }
    switch (bitDepth) {
      case 8: lutType = PIXEL_U8; break;
      case 10:
      case 12:
      case 16: lutType = PIXEL_U16; break;
    }
    Image lut(Vec2i(lutSize, 1), Pixel(PIXEL_L, lutType));
    switch (_color) {
    
      case COLOR_LINEAR:
        DJ_DBG_PRN("lut = linear");
        pixelGradient(lut.data(), lut.width(), lut.pixel());
        break;
      
      case COLOR_AUTO:
      case COLOR_PRINT: {
        DJ_DBG_PRN("lut = print");
        Image tmp(Vec2i(lutSize, 1), Pixel(PIXEL_L, PIXEL_F32));
        dpxLutPrint(
          reinterpret_cast<PixelF32 *>(tmp.data()), lutSize,
          _printBlack, _printWhite, _printGamma
        );
        pixelCopy(tmp, &lut);
      } break;
    }
    DJ_DBG_PRN("lut = " << lut);
    
    _header.imageElem[c].dataOffset = _f.position();
    
    int y = 0;
    for (; y < h; ++y) {
      //DJ_DBG_PRN("y = " << y);
    
      const uint8_t * inP = framebuf.data(0, flipY ? (h - 1 - y) : y);
      
      // Convert.
      //
      pixelCopy(
        inP + c * pixelSize(framebuf.type()), scanline.data(),
        w, framebuf.pixel(), scanline.pixel(),
        1, flipX, planar ? -1 : 0
      );
      bool packing = true;
      switch (bitDepth) {
        case 10:
        case 12: 
          if (1 == _header.imageElem[c].packing)
            packing = false;
          break;
      }
      if (packing)
        dpxScanlinePack(
          scanline.data(), fileScanline, lut.data(),
          w * channels, type, bitDepth, endian
        );
      else
        dpxScanlineFill(
          scanline.data(), fileScanline, lut.data(),
          w * channels, type, bitDepth, endian
        );
        
      // Write.
      //
      if (! _f.set(fileScanline, fileScanlineSize))
        break;
    }
    if (y < h) break;
  }
  if (fileScanline) delete [] fileScanline;
  
  // Update header information.
  //
  if (planes == c) {
    c = 0;
  do {
    _header.imageOffset = _header.imageElem[0].dataOffset;
    _header.fileSize = _f.position();
    DJ_DBG_PRN("image offset = " << int(_header.imageOffset));
    DJ_DBG_PRN("file size = " << int(_header.fileSize));
    if (! _f.seek(4, FileIo::SET)) break;
    if (! _f.setU32(_header.imageOffset)) break;
    if (! _f.seek(8, FileIo::CURRENT)) break;
    if (! _f.setU32(_header.fileSize)) break;  
    if (! _f.seek(808, FileIo::SET)) break;
    for (int i = 0; i < _header.image.elemSize; ++i) {
      DJ_DBG_PRN("data offset = " << int(_header.imageElem[i].dataOffset));
      if (! _f.setU32(_header.imageElem[i].dataOffset)) break;
      if (! _f.seek(68, FileIo::CURRENT)) break;
    }
    c = planes;
  } while (0);
  }
  
  _f.close();
  return planes == c;
}

}
}
