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

//#define DJ_DBG
#include "djImageIoSgi.h"

namespace dj {
namespace ImageIoSgi {

Save::Save() :
  _compress(COMPRESS_NONE)
{}

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

bool Save::attr(const Str & in, const StrList & data)
{
  StrList tmp = data;
  if (compareCase(attrNameList()[ATTR_COMPRESS], in)) {
    if (! inflateStr(tmp, &_compress, compressStr())) return false;
  } else
    return false;
  return true;
}

const StrList Save::attr(const Str & in) const
{
  StrList out;
  if (compareCase(attrNameList()[ATTR_COMPRESS], in))
    out += deflateStr(_compress, compressStr());
  return out;
}

bool Save::open(const Str & fileName, const ImageIoInfo & info)
{
  DJ_DBG_FNC("dj::ImageIoSgi::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::ImageIoSgi::Save::imageOpen");
  DJ_DBG_PRN("file name = " << fileName);

  // Open file.
  //
  _f.endian(endian() != MSB);
  if (! _f.open(fileName, FileIo::WRITE)) return false;
  
  // Setup header.
  //
  _header.width = _info.width();
  _header.height = _info.height();
  _header.channels = pixelSize(_info.format());
  _header.dimension = 0;
  switch (_info.format()) {
    case PIXEL_L: _header.dimension = _info.height() == 1 ? 1 : 2; break;
    case PIXEL_LA:
    case PIXEL_RGB:
    case PIXEL_RGBA: _header.dimension = 3; break;
  }
  _header.bytes = 0;
  switch (_info.type()) {
    case PIXEL_U8:
    case PIXEL_U16: _header.bytes = pixelSize(_info.type()); break;
    case PIXEL_F32: break;
  }
  switch (_info.type()) {
    case PIXEL_U8:
      _header.pixelMin = pixelU8Min;
      _header.pixelMax = pixelU8Max;
      break;
    case PIXEL_U16:
      _header.pixelMin = pixelU16Min;
      _header.pixelMax = pixelU16Max;
      break;
    case PIXEL_F32: break;
  }
  _header.storage = COMPRESS_RLE == _compress;
  
  // Write header.
  //
  uint16_t magic = 474;
  if (! _f.setU16(magic)) return false;
  if (! _f.setU8(_header.storage)) return false;
  if (! _f.setU8(_header.bytes)) return false;
  if (! _f.setU16(_header.dimension)) return false;
  if (! _f.setU16(_header.width)) return false;
  if (! _f.setU16(_header.height)) return false;
  if (! _f.setU16(_header.channels)) return false;
  if (! _f.setU32(_header.pixelMin)) return false;
  if (! _f.setU32(_header.pixelMax)) return false;
  if (! _f.seek(492, FileIo::CURRENT)) return false;
  
  return true;
}

bool Save::imageBegin(const Image & in, int frame)
{
  DJ_DBG_FNC("dj::ImageIoSgi::Save::imageBegin");
  DJ_DBG_PRN("in = " << in);
  DJ_DBG_PRN("frame = " << frame);
  
  if (! imageOpen(_fileInfo.get(frame == -1 ? _fileInfo.start() : frame), in))
    return false;
  
  // Initialize RLE tables.
  //
  if (_header.storage) {
    const int tableSize = _header.height * _header.channels;
    DJ_DBG_PRN("rle table size = " << tableSize);
    if (tableSize) {
    
      if (_header.rleOffsets) delete [] _header.rleOffsets;
      _header.rleOffsets = new uint32_t [tableSize];
      if (! _f.seek(tableSize * 4, FileIo::CURRENT)) return false;
      
      if (_header.rleSizes) delete [] _header.rleSizes;
      _header.rleSizes = new uint32_t [tableSize];
      if (! _f.seek(tableSize * 4, FileIo::CURRENT)) return false;
    }
  }
  
  return true;
}

bool Save::imageSet(const PixelData & in)
{
  DJ_DBG_FNC("dj::ImageIoSgi::Save::imageSet");
  DJ_DBG_PRN("in = " << in);
  
  const bool compress = 1 == _header.storage;
  
  // Convert.
  //
  PixelData framebuf(_info);
  framebuf.zero();
  pixelCopy(in, &framebuf);
  
  // Write.
  //
  PixelData scanline(Vec2i(_header.width, 1), Pixel(PIXEL_L, _info.type()));
  int c = 0;
  for (; c < _header.channels; ++c) {
    DJ_DBG_PRN("c = " << c);
    
    int y = 0;
    for (; y < _header.height; ++y) {
      //DJ_DBG_PRN("y = " << y);

      // Convert.
      //
      pixelCopy(
        framebuf.data(0, y) + c * pixelSize(framebuf.type()), scanline.data(),
        _header.width, framebuf.pixel(), scanline.pixel(),
        1, false, -1
      );
      
      // Write.
      //
      if (! compress) {
      
        if (! _f.set(scanline.data(), _header.width, _header.bytes)) break;
      
      } else {
      
        //DJ_DBG_PRN("rle");
        
        uint32_t size = 0, offset = _f.position();
        if (1 == _header.bytes) {
        
          const PixelU8 * p =
            reinterpret_cast<const PixelU8 *>(scanline.data());
          int i = 0;
          while (i < _header.width) {
            //DJ_DBG_PRN("index = " << i);
            const int max = min(127, _header.width - i);
            int count = 0;
            const PixelU8 * tmpP = p;
            for (; count < max; ++count, ++tmpP)
              if (*tmpP != *p) break;
            if (count > 1) {
              if (! _f.setU8(count & 127)) break;
              if (! _f.setU8(p, 1)) break;
            } else {
              for (; count < max; ++count, ++tmpP)
                if (*tmpP == *p) break;
              if (! _f.setU8((count & 127) | (1 << 7))) break;
              if (! _f.setU8(p, count)) break;
            }
            //DJ_DBG_PRN("count = " << count);
            i += count;
            p += count;
          }
          if (i < _header.width) break;
          if (! _f.setU8(0)) break;
        
        } else if (2 == _header.bytes) {
        
          const PixelU16 * p =
            reinterpret_cast<const PixelU16 *>(scanline.data());
          int i = 0;
          while (i < _header.width) {
            //DJ_DBG_PRN("index = " << i);
            const int max = min(127, _header.width - i);
            int count = 0;
            const PixelU16 * tmpP = p;
            for (; count < max; ++count, ++tmpP)
              if (*tmpP != *p) break;
            if (count > 1) {
              if (! _f.setU16(count & 127)) break;
              if (! _f.setU16(p, 1)) break;
            } else {
              for (; count < max; ++count, ++tmpP)
                if (*tmpP == *p) break;
              if (! _f.setU16((count & 127) | (1 << 7))) break;
              if (! _f.setU16(p, count)) break;
            }
            //DJ_DBG_PRN("count = " << count);
            i += count;
            p += count;
          }
          if (i < _header.width) break;
          if (! _f.setU16(0)) break;
        }
        
        size = _f.position() - offset;
        //DJ_DBG_PRN("offset = " << int(offset));
        //DJ_DBG_PRN("size = " << int(size));
        _header.rleOffsets[y + c * _header.height] = offset;
        _header.rleSizes[y + c * _header.height] = size;
      }
    }
    if (y < _header.height) break;
  }
  
  // Update header information.
  //
  if (compress && _header.channels == c) {
    c = 0;
  do {
    if (! _f.seek(512, FileIo::SET)) break;
    const int tableSize = _header.height * _header.channels;
    if (! _f.setU32(_header.rleOffsets, tableSize)) break;
    if (! _f.setU32(_header.rleSizes, tableSize)) break;
    c = _header.channels;
  } while (0);
  }
  
  _f.close();
  return _header.channels == c;
}

}
}
