/*
 * libopenraw - jfifcontainer.cpp
 *
 * Copyright (C) 2006-2020 Hubert Figuière
 *
 * This library is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation, either version 3 of
 * the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library.  If not, see
 * <http://www.gnu.org/licenses/>.
 */


#include <setjmp.h>
#include <fcntl.h>
#include <stddef.h>
#include <string.h>
#include <memory>

/*
 * The extern "C" below is REQUIRED for libjpeg-mmx-dev
 * as found on debian because some people have this installed.
 */
extern "C" {
#include <jpeglib.h>
}

#include <libopenraw/debug.h>

#include "bitmapdata.hpp"
#include "io/stream.hpp"
#include "io/streamclone.hpp"
#include "trace.hpp"
#include "jfifcontainer.hpp"
#include "ifdfilecontainer.hpp"

namespace OpenRaw {

using namespace Debug;

namespace Internals {

namespace {

/* libjpeg callbacks j_ is the prefix for these callbacks */
void j_init_source(::j_decompress_ptr cinfo);
::boolean j_fill_input_buffer(::j_decompress_ptr cinfo);
void j_skip_input_data(::j_decompress_ptr cinfo,
                                long num_bytes);
void j_term_source(::j_decompress_ptr cinfo);
void j_error_exit(::j_common_ptr cinfo);

}

/** private source struct for libjpeg
 */
#define BUF_SIZE 1024

typedef struct {
  struct jpeg_source_mgr pub; /**< the public libjpeg struct */
  JfifContainer * self;       /**< pointer to the owner */
  off_t offset;
  JOCTET* buf;
} jpeg_src_t;

JfifContainer::JfifContainer(const IO::Stream::Ptr &_file, off_t _offset)
  : RawContainer(_file, _offset),
    m_cinfo(), m_jerr(),
    m_headerLoaded(false), m_exif_offset(0)
{
  setEndian(ENDIAN_BIG);
  /* this is a hack because jpeg_create_decompress is
   * implemented as a Macro
   */

  m_cinfo.err = jpeg_std_error(&m_jerr);
  m_jerr.error_exit = &j_error_exit;
  jpeg_create_decompress(&m_cinfo);

  /* inspired by jdatasrc.c */

  jpeg_src_t *src = (jpeg_src_t *)
    (*m_cinfo.mem->alloc_small)((j_common_ptr)&m_cinfo,
                                JPOOL_PERMANENT,
                                sizeof(jpeg_src_t));
  m_cinfo.src = (jpeg_source_mgr*)src;
  src->pub.init_source = j_init_source;
  src->pub.fill_input_buffer = j_fill_input_buffer;
  src->pub.skip_input_data = j_skip_input_data;
  src->pub.resync_to_restart = jpeg_resync_to_restart;
  src->pub.term_source = j_term_source;
  src->self = this;
  src->pub.bytes_in_buffer = 0;
  src->pub.next_input_byte = nullptr;
  src->buf = (JOCTET*)(*m_cinfo.mem->alloc_small)
    ((j_common_ptr)&m_cinfo,
     JPOOL_PERMANENT,
     BUF_SIZE * sizeof(JOCTET));
}

JfifContainer::~JfifContainer()
{
  jpeg_destroy_decompress(&m_cinfo);
}


bool JfifContainer::getDimensions(uint32_t &x, uint32_t &y)
{
  if(!m_headerLoaded) {
    if (_loadHeader() == 0) {
      LOGDBG1("load header failed\n");
      return false;
    }
  }
  x = m_cinfo.output_width;
  y = m_cinfo.output_height;
  return true;
}


bool JfifContainer::getDecompressedData(BitmapData &data)
{
  if(!m_headerLoaded) {
    if (_loadHeader() == 0) {
      LOGDBG1("load header failed\n");
      return false;
    }
  }
  if (::setjmp(m_jpegjmp) != 0) {
    return false;
  }
  jpeg_start_decompress(&m_cinfo);
  int row_size = m_cinfo.output_width * m_cinfo.output_components;
  char *dataPtr
    = (char*)data.allocData(row_size * m_cinfo.output_height);
  char *currentPtr = dataPtr;
  JSAMPARRAY buffer
    = (*m_cinfo.mem->alloc_sarray)((j_common_ptr)&m_cinfo,
                                   JPOOL_IMAGE, row_size,
                                   1);
  while (m_cinfo.output_scanline < m_cinfo.output_height) {
    jpeg_read_scanlines(&m_cinfo, buffer, 1);
    memcpy(currentPtr, buffer, row_size);
    currentPtr += row_size;
  }
  data.setDimensions(m_cinfo.output_width, m_cinfo.output_height);

  jpeg_finish_decompress(&m_cinfo);
  return true;
}


int JfifContainer::_loadHeader()
{

  m_file->seek(0, SEEK_SET);

  if (::setjmp(m_jpegjmp) == 0) {
    int ret = jpeg_read_header(&m_cinfo, TRUE);

    jpeg_calc_output_dimensions(&m_cinfo);
    m_headerLoaded = (ret == 1);
    return ret;
  }
  return 0;
}

namespace {

__attribute__((noreturn))
void j_error_exit(j_common_ptr cinfo)
{
  (*cinfo->err->output_message) (cinfo);
  JfifContainer *self = ((jpeg_src_t *)(((j_decompress_ptr)cinfo)->src))->self;
  ::longjmp(self->jpegjmp(), 1);
}

void j_init_source(j_decompress_ptr)
{
}


boolean j_fill_input_buffer(j_decompress_ptr cinfo)
{
  jpeg_src_t *src = (jpeg_src_t*)cinfo->src;
  JfifContainer *self = src->self;
  int n = self->file()->read(src->buf, BUF_SIZE * sizeof(*src->buf));
  if (n >= 0) {
    src->pub.next_input_byte = src->buf;
    src->pub.bytes_in_buffer = n;
  }
  else {
    src->pub.next_input_byte = nullptr;
    src->pub.bytes_in_buffer = 0;
  }
  return TRUE;
}


void j_skip_input_data(j_decompress_ptr cinfo,
                                      long num_bytes)
{
  jpeg_src_t *src = (jpeg_src_t*)cinfo->src;
  if (num_bytes > 0) {
    while ((size_t)num_bytes > src->pub.bytes_in_buffer) {
      num_bytes -= src->pub.bytes_in_buffer;
      j_fill_input_buffer(cinfo);
    }
    src->pub.next_input_byte += (size_t) num_bytes;
    src->pub.bytes_in_buffer -= (size_t) num_bytes;
  }
}


void j_term_source(j_decompress_ptr)
{
}

}

std::unique_ptr<IfdFileContainer> & JfifContainer::ifdContainer()
{
  if(!m_ifd) {
    m_file->seek(0, SEEK_SET);

    // XXX check results and bail.
    auto result = readUInt16(m_file, m_endian); // SOI
    result = readUInt16(m_file, m_endian); // APP0
    result = readUInt16(m_file, m_endian); // ignore

    char delim[7];
    delim[6] = 0;
    m_file->read(delim, 6);
    if(memcmp(delim, "Exif\0\0", 6) == 0) {
      m_exif_offset = m_file->seek(0, SEEK_CUR);
      m_ifd.reset(new IfdFileContainer(
        IO::Stream::Ptr(
          std::make_shared<IO::StreamClone>(m_file, m_exif_offset)), 0));
    }
  }
  return m_ifd;
}

IfdDir::Ref JfifContainer::mainIfd()
{
  if(ifdContainer()) {
    return m_ifd->setDirectory(0);
  }
  return IfdDir::Ref();
}

IfdDir::Ref JfifContainer::getIfdDirAt(int idx)
{
  if(ifdContainer()) {
    return m_ifd->setDirectory(idx);
  }
  return IfdDir::Ref();
}


IfdDir::Ref JfifContainer::exifIfd()
{
  IfdDir::Ref main = mainIfd();
  return main->getExifIFD();
}

}
}
/*
  Local Variables:
  mode:c++
  c-file-style:"stroustrup"
  c-file-offsets:((innamespace . 0))
  tab-width:2
  c-basic-offset:2
  indent-tabs-mode:nil
  fill-column:80
  End:
*/
