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

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

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

namespace djImageIoPng {

Load::Load() :
  _f(0), _png(0), _pngInfo(0), _pngInfoEnd(0)
{}

Load::~Load()
{
  imageClose();
}

bool Load::open(const Str & fileName, ImageIoInfo * info)
{
  DJ_DBG_FNC("djImageIoPng::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;
}

static bool _open(
  FILE * f, png_structp png, png_infop * pngInfo, png_infop * pngInfoEnd
) {
  if (setjmp(png_jmpbuf(png))) return false;
  
  *pngInfo = png_create_info_struct(png);
  if (! *pngInfo) return false;
  *pngInfoEnd = png_create_info_struct(png);
  if (! *pngInfoEnd) return false;
  
  uint8_t tmp [8];
  if (fread(tmp, 8, 1, f) != 1) return false;
  if (png_sig_cmp(tmp, 0, 8)) return false;
  png_init_io(png, f);
  png_set_sig_bytes(png, 8);
  png_read_info(png, *pngInfo);
  
  if (png_get_interlace_type(png, *pngInfo) != PNG_INTERLACE_NONE)
    return false;
  png_set_expand(png);
  png_set_gray_1_2_4_to_8(png);
  png_set_palette_to_rgb(png);
  png_set_tRNS_to_alpha(png);
    
  return true;
}

bool Load::imageOpen(const Str & fileName, ImageIoInfo * info)
{
  DJ_DBG_FNC("djImageIoPng::Load::imageOpen");
  DJ_DBG_PRN("file name = " << fileName);
  
  imageClose();
  
  // Initialize libpng.
  //
  _png = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, pngError, pngWarning);
  if (! _png) return false;
  
  // Open file.
  //
  _f = fopen(fileName.toAscii(), "rb");
  if (! _f) return false;
  if (! _open(_f, _png, &_pngInfo, &_pngInfoEnd)) return false;

  // Setup information.
  //
  _info.size(
    Vec2i(
      png_get_image_width(_png, _pngInfo),
      png_get_image_height(_png, _pngInfo)
    )
  );
  info->size(_info.size());

  int channels = png_get_channels(_png, _pngInfo);
  if (png_get_color_type(_png, _pngInfo) == PNG_COLOR_TYPE_PALETTE)
    channels = 3;
  if (png_get_valid(_png, _pngInfo, PNG_INFO_tRNS))
    ++channels;
  switch (channels) {
    case 1: _info.format(PIXEL_L); break;
    case 2: _info.format(PIXEL_LA); break;
    case 3: _info.format(PIXEL_RGB); break;
    case 4: _info.format(PIXEL_RGBA); break;
    default: return false;
  }
  switch (png_get_bit_depth(_png, _pngInfo)) {
    case 1:
    case 2:
    case 4:
    case 8: _info.type(PIXEL_U8); break;
    case 16: _info.type(PIXEL_U16); break;
    default: return false;
  }
  info->pixel(_info.pixel());
  
  return true;
}

void Load::imageClose()
{
  if (_png || _pngInfo || _pngInfoEnd) {
    png_destroy_read_struct(
      _png ? &_png : 0,
      _pngInfo ? &_pngInfo : 0,
      _pngInfoEnd ? &_pngInfoEnd : 0
    );
    _png = 0;
    _pngInfo = 0;
    _pngInfoEnd = 0;
  }
  if (_f) {
    fclose(_f);
    _f = 0; 
  }
}

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

static bool _scanline(png_structp png, uint8_t * out)
{
  if (setjmp(png_jmpbuf(png))) return false;
  png_read_row(png, out, 0);
  return true;
}

static bool _end(png_structp png, png_infop pngInfo)
{
  if (setjmp(png_jmpbuf(png))) return false;
  png_read_end(png, pngInfo);
  return true;
}

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

  // Read.
  //
  PixelData scanline(Vec2i(_info.width(), 1), _info.pixel());
  int y = 0, fileY = 0;
  for (; y < out->height(); ++y, fileY += proxyScale) {
    //DJ_DBG_PRN("y = " << y << " (" << fileY << ")");
    
    if (! _scanline(_png, scanline.data())) break;

    // Convert.
    //
    pixelCopy(
      scanline.data(), out->data(0, out->height() - 1 - y),
      out->width(), scanline.pixel(), out->pixel(),
      proxyScale
    );
    
    // Skip scanlines.
    //
    const int skip = min(proxyScale - 1, _info.height() - (fileY + 1));
    if (skip) {
      int i = 0;
      for (i = 0; i < skip; ++i)
        if (! _scanline(_png, scanline.data())) break;
      if (i < skip) break;
    }
  }
  
  bool r = out->height() == y;
  r &= _end(_png, _pngInfoEnd);
  imageClose();
  return r;
}

}
