/*------------------------------------------------------------------------------
 src/plugin/djImageIoJpeg/save.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::ImageSave *> djImageSave()
{
  dj::List<dj::ImageSave *> out;
  out += new djImageIoJpeg::Save;
  return out;
}
}

namespace djImageIoJpeg {

Save::Save() :
  _quality(100),
  _f(0), _jpegInit(false)
{}

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

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

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

const StrList Save::attr(const Str & in) const
{
  StrList out;
  if (compareCase(saveAttrStr()[ATTR_QUALITY], in))
    out += deflateStr(_quality);
  return out;
}

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

static bool _open(
  FILE * f, jpeg_compress_struct * jpeg,
  const PixelDataInfo & info, const ImageTagList & tagList,
  int quality,
  Error * error
) {
  if (setjmp(error->jump)) return false;

  jpeg_stdio_dest(jpeg, f);
  
  jpeg->image_width = info.width();
  jpeg->image_height = info.height();
  if (PIXEL_L == info.format()) {
    jpeg->input_components = 1;
    jpeg->in_color_space = JCS_GRAYSCALE;
  } else if (PIXEL_RGB == info.format()) {
    jpeg->input_components = 3;
    jpeg->in_color_space = JCS_RGB;
  }
  
  jpeg_set_defaults(jpeg);
  jpeg_set_quality(jpeg, quality, TRUE);
  jpeg_start_compress(jpeg, TRUE);
  
  Str description;
  StrList data = tagList[imageTagStr()[IMAGE_TAG_DESCRIPTION]];
  if (inflateStr(data, &description))
    jpeg_write_marker(
      jpeg, JPEG_COM, (JOCTET *)description.toAscii(), description.size()
    );
  
  return true;
}

bool Save::imageOpen(const Str & fileName, const Image & in)
{
  DJ_DBG_FNC("djImageIoJpeg::Save::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_compress(&_jpeg);
  _jpegInit = true;
  
  // Open file.
  //
  _f = fopen(fileName.toAscii(), "wb");
  if (! _f) return false;
  if (! _open(_f, &_jpeg, _info, in.tagList(), _quality, &_error)) return false;
  
  return true;
}

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

bool Save::imageBegin(const Image & in, int frame)
{
  DJ_DBG_FNC("djImageIoJpeg::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;
}

static bool _scanline(
  jpeg_compress_struct * jpeg, const uint8_t * in, Error * error
) {
  if (setjmp(error->jump)) return false;
  JSAMPROW p [] = { (JSAMPLE *)in };
  if (! jpeg_write_scanlines(jpeg, p, 1)) return false;
  return true;
}

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

bool Save::imageSet(const PixelData & in)
{
  DJ_DBG_FNC("djImageIoJpeg::Save::imageSet");
  DJ_DBG_PRN("in = " << in);
  
  // Convert.
  //
  PixelData framebuf(_info);
  framebuf.zero();
  pixelCopy(in, &framebuf);
  
  // Write.
  //
  int y = 0;
  for (; y < _info.height(); ++y)
    if (
      ! _scanline(&_jpeg, framebuf.data(0, _info.height() - 1 - y), &_error)
    ) break;
  
  bool r = _info.height() == y;
  r &= _end(&_jpeg, &_error);
  imageClose();
  return r;
}

}
