/*------------------------------------------------------------------------------
 src/plugin/djImageIoJpeg/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 djImageIoJpeg::Load;
  return out;
}
}

namespace djImageIoJpeg {

Load::Load() :
  _f(0), _jpegInit(false)
{}

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

bool Load::open(const Str & fileName, ImageIoInfo * info)
{
  DJ_DBG_FNC("djImageIoJpeg::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, jpeg_decompress_struct * jpeg, Error * error)
{
  if (setjmp(error->jump)) return false;
  
  // Read header.
  //
  jpeg_stdio_src(jpeg, f);
  jpeg_save_markers(jpeg, JPEG_COM, 0xFFFF);
  if (! jpeg_read_header(jpeg, TRUE)) return false;
  if (! jpeg_start_decompress(jpeg)) return false;
  
  return true;
}

bool Load::imageOpen(const Str & fileName, ImageIoInfo * info)
{
  DJ_DBG_FNC("djImageIoJpeg::Load::imageOpen");
  DJ_DBG_PRN("file name = " << fileName);
  
  imageClose();
  
  // Initialize libjpeg.
  //
  _jpeg.err = jpeg_std_error(&_error.pub);
  _error.pub.error_exit = jpegError;
  _error.pub.emit_message = jpegWarning;
  jpeg_create_decompress(&_jpeg);
  _jpegInit = true;
  
  // Open file.
  //
  _f = fopen(fileName.toAscii(), "rb");
  if (! _f) return false;
  if (! _open(_f, &_jpeg, &_error)) return false;

  // Setup information.
  //
  info->size(Vec2i(_jpeg.output_width, _jpeg.output_height));
  
  _format = PIXEL_FORMAT(0);
  switch (_jpeg.out_color_components) {
    case 1: _format = PIXEL_L; break;
    case 3: _format = PIXEL_RGB; break;
    default: return false;
  }
  info->pixel(Pixel(_format, PIXEL_U8));
  
  // Tags.
  //
  ImageTagList tagList;
  const jpeg_saved_marker_ptr marker = _jpeg.marker_list;
  if (marker)
    tagList[imageTagStr()[IMAGE_TAG_DESCRIPTION]] =
      deflateStr(Str((const char *)marker->data, marker->data_length));
  info->tagList(tagList);

  return true;
}

void Load::imageClose()
{
  if (_jpegInit) {
    jpeg_destroy_decompress(&_jpeg);
    _jpegInit = false;
  }
  if (_f) {
    fclose(_f);
    _f = 0;
  }
}

bool Load::imageBegin(int frame, ImageIoInfo * info)
{
  DJ_DBG_FNC("djImageIoJpeg::Load::imageBegin");
  DJ_DBG_PRN("frame = " << frame);

  if (
    ! imageOpen(_fileInfo.get(frame == -1 ? _fileInfo.start() : frame), info)
  ) return false;
  
  return true;
}

static bool _scanline(
  jpeg_decompress_struct * jpeg, uint8_t * out, Error * error
) {
  if (setjmp(error->jump)) return 0;
  JSAMPROW p [] = { (JSAMPLE *)out };
  if (! jpeg_read_scanlines(jpeg, p, 1)) return false;
  return true;
}

static bool _end(jpeg_decompress_struct * jpeg, Error * error)
{
  if (setjmp(error->jump)) return false;
  jpeg_finish_decompress(jpeg);
  return true;
}

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

  // Read.
  //
  PixelData scanline(Vec2i(_jpeg.output_width, 1), Pixel(_format, PIXEL_U8));
  int y = 0, fileY = 0;
  for (; y < out->height(); ++y, fileY += proxyScale) {
    //DJ_DBG_PRN("y = " << y << " (" << fileY << ")");
    
    if (! _scanline(&_jpeg, scanline.data(), &_error)) 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, int(_jpeg.output_width) - (fileY + 1));
    if (skip) {
      int i = 0;
      for (i = 0; i < skip; ++i)
        if (! _scanline(&_jpeg, scanline.data(), &_error)) break;
      if (i < skip) break;
    }
  }
  
  bool r = out->height() == y;
  r &= _end(&_jpeg, &_error);
  imageClose();
  return r;
}

}
