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

//#define DJ_DBG
#include "io.h"
#include <djMath.h>

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

namespace djImageIoTiff {

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

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

bool Load::open(const Str & fileName, ImageIoInfo * info)
{
  DJ_DBG_FNC("djImageIoTiff::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("djImageIoTiff::Load::imageOpen");
  DJ_DBG_PRN("file name = " << fileName);
  
  // Open file.
  //
  if (_f) TIFFClose(_f);
  _f = TIFFOpen(fileName.toAscii(), "r");
  if (! _f) return false;
  
  // Read header.
  //
  TIFFGetFieldDefaulted(_f, TIFFTAG_IMAGEWIDTH, &_header.width);
  TIFFGetFieldDefaulted(_f, TIFFTAG_IMAGELENGTH, &_header.height);
  TIFFGetFieldDefaulted(_f, TIFFTAG_PHOTOMETRIC, &_header.photometric);
  TIFFGetFieldDefaulted(_f, TIFFTAG_SAMPLESPERPIXEL, &_header.samples);
  TIFFGetFieldDefaulted(_f, TIFFTAG_BITSPERSAMPLE, &_header.sampleDepth);
  TIFFGetFieldDefaulted(_f, TIFFTAG_SAMPLEFORMAT, &_header.sampleFormat);
  TIFFGetFieldDefaulted(
    _f, TIFFTAG_EXTRASAMPLES, &_header.extraSamplesSize, &_header.extraSamples
  );
  TIFFGetFieldDefaulted(_f, TIFFTAG_ORIENTATION, &_header.orient);
  TIFFGetFieldDefaulted(_f, TIFFTAG_COMPRESSION, &_header.compress);
  TIFFGetFieldDefaulted(_f, TIFFTAG_PLANARCONFIG, &_header.channels);
  TIFFGetFieldDefaulted(
    _f, TIFFTAG_COLORMAP,
    &_header.colormap[0], &_header.colormap[1], &_header.colormap[2]
  );
  dbg(_header);
  
  // Setup information.
  //
  info->size(Vec2i(_header.width, _header.height));
  
  switch (_header.photometric) {
    case PHOTOMETRIC_PALETTE: _pixel.format(PIXEL_RGB); break;
    case PHOTOMETRIC_MINISWHITE:
    case PHOTOMETRIC_MINISBLACK:
    case PHOTOMETRIC_RGB:
      switch (_header.samples) {
        case 1: _pixel.format(PIXEL_L); break;
        case 2: _pixel.format(PIXEL_LA); break;
        case 3: _pixel.format(PIXEL_RGB); break;
        case 4: _pixel.format(PIXEL_RGBA); break;
        default: return false;
      }
      break;
    default: return false;
  }
  switch (_header.sampleDepth) {
    case 8: _pixel.type(PIXEL_U8); break;
    case 16: _pixel.type(PIXEL_U16); break;
    case 32:
      if (SAMPLEFORMAT_IEEEFP == _header.sampleFormat) {
        _pixel.type(PIXEL_F32);
        break;
      }
    default: return false;
  }
  info->pixel(_pixel);
  
  // Tags.
  //
  ImageTagList tagList;
  char * tagP;
  if (TIFFGetField(_f, TIFFTAG_ARTIST, &tagP))
    tagList[imageTagStr()[IMAGE_TAG_CREATOR]] = deflateStr(Str(tagP));
  if (TIFFGetField(_f, TIFFTAG_COPYRIGHT, &tagP))
    tagList[imageTagStr()[IMAGE_TAG_COPYRIGHT]] = deflateStr(Str(tagP));
  if (TIFFGetField(_f, TIFFTAG_DATETIME, &tagP))
    tagList[imageTagStr()[IMAGE_TAG_TIME_STAMP]] = deflateStr(Str(tagP));
  if (TIFFGetField(_f, TIFFTAG_IMAGEDESCRIPTION, &tagP))
    tagList[imageTagStr()[IMAGE_TAG_DESCRIPTION]] = deflateStr(Str(tagP));
  DJ_DBG_PRN("tags = " << tagList);
  info->tagList(tagList);
  
  return true;
}

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

static void _scanlinePalette(
  uint8_t * in, int size, PIXEL_TYPE type,
  uint16_t * red, uint16_t * green, uint16_t * blue
) {
  switch (type) {
  
    case PIXEL_U8: {
      const PixelU8 * inP = reinterpret_cast<const PixelU8 *>(in) + size - 1;
      PixelU8 * outP = reinterpret_cast<PixelU8 *>(in) + (size - 1) * 3;
      for (int x = 0; x < size; ++x, outP -= 3) {
        const PixelU8 index = *inP--;
        outP[0] = red[index];
        outP[1] = green[index];
        outP[2] = blue[index];
      }
    } break;
    
    case PIXEL_U16: {
      const PixelU16 * inP = reinterpret_cast<const PixelU16 *>(in) + size - 1;
      PixelU16 * outP = reinterpret_cast<PixelU16 *>(in) + (size - 1) * 3;
      for (int x = 0; x < size; ++x, outP -= 3) {
        const PixelU16 index = *inP--;
        outP[0] = red[index];
        outP[1] = green[index];
        outP[2] = blue[index];
      }
    } break;
    
    case PIXEL_F32: break;
  }
}

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

  bool flipX = false, flipY = false;
  switch (_header.orient) {
    case ORIENTATION_TOPLEFT: flipY = true; break;
    case ORIENTATION_TOPRIGHT: flipX = flipY = true; break;
    case ORIENTATION_BOTRIGHT: flipX = true; break;
    case ORIENTATION_BOTLEFT: break;
  }
  const int w = _header.width, h = _header.height;
  const bool planar = PLANARCONFIG_SEPARATE == _header.channels;
  const int planes = planar ? pixelSize(_pixel.format()) : 1;
  const bool compress = _header.compress != COMPRESSION_NONE;
  DJ_DBG_PRN("planar = " << planar);
  DJ_DBG_PRN("planes = " << planes);
  DJ_DBG_PRN("compress = " << compress);

  // Read.
  //
  PixelData scanline(
    Vec2i(w, 1), Pixel(planar ? PIXEL_L : _pixel.format(), _pixel.type())
  );
  int c = 0;
  for (; c < planes; ++c) {
    DJ_DBG_PRN("c = " << c);

    int y = 0, fileY = 0;
    for (; y < out->height(); ++y, fileY += proxyScale) {
      //DJ_DBG_PRN("y = " << y << " (" << fileY << ")");
    
      if (TIFFReadScanline(_f, (tdata_t *)scanline.data(), fileY, c) == -1)
        break;
      
      // Convert.
      //
      if (PHOTOMETRIC_PALETTE == _header.photometric)
        _scanlinePalette(
          scanline.data(), scanline.width(), scanline.type(),
          _header.colormap[0], _header.colormap[1], _header.colormap[2]
        );
      pixelCopy(
        scanline.data(),
        out->data(0, flipY ? (out->height() - 1 - y) : y) +
          c * pixelSize(out->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 (compress) {
          int i = 0;
          for (i = 0; i < skip; ++i)
            if (
              TIFFReadScanline(
                _f, (tdata_t *)scanline.data(), fileY + 1 + i, c
              ) == -1
            )
          if (i < skip) break;
        }
    }
    if (y < out->height()) break;
  }
  
  TIFFClose(_f);
  _f = 0;
  return planes == c;
}

}
