/***

  Olive - Non-Linear Video Editor
  Copyright (C) 2019 Olive Team

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program 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 General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.

***/

#include "oiiodecoder.h"

#include <QDebug>
#include <QFileInfo>

#include "common/define.h"

QStringList OIIODecoder::supported_formats_;

OIIODecoder::OIIODecoder() :
  image_(nullptr),
  frame_(nullptr)
{
}

QString OIIODecoder::id()
{
  return "oiio";
}

bool OIIODecoder::Probe(Footage *f)
{
  // We prioritize OIIO over FFmpeg to pick up still images more effectively, but some OIIO decoders (notably OpenJPEG)
  // will segfault entirely if given unexpected data (an MPEG-4 for instance). To workaround this issue, we use OIIO's
  // "extension_list" attribute and match it with the extension of the file.

  // Check if we've created the supported formats list, create it if not
  if (supported_formats_.isEmpty()) {
    QStringList extension_list = QString::fromStdString(OIIO::get_string_attribute("extension_list")).split(';');

    // The format of "extension_list" is "format:ext", we want to separate it into a simple list of extensions
    foreach (const QString& ext, extension_list) {
      QStringList format_and_ext = ext.split(':');

      supported_formats_.append(format_and_ext.at(1).split(','));
    }
  }

  //
  QFileInfo file_info(f->filename());

  if (!supported_formats_.contains(file_info.completeSuffix(), Qt::CaseInsensitive)) {
    return false;
  }

  std::string std_filename = f->filename().toStdString();

  auto in = OIIO::ImageInput::open(std_filename);

  if (!in) {
    return false;
  }

  if (!strcmp(in->format_name(), "FFmpeg movie")) {
    // If this is FFmpeg via OIIO, fall-through to our native FFmpeg decoder
    return false;
  }

  // Get stats for this image and dump them into the Footage file
  const OIIO::ImageSpec& spec = in->spec();

  ImageStreamPtr image_stream = std::make_shared<ImageStream>();
  image_stream->set_width(spec.width);
  image_stream->set_height(spec.height);

  // OIIO automatically premultiplies alpha
  // FIXME: We usually disassociate the alpha for the color management later, for 8-bit images this likely reduces the
  //        fidelity?
  image_stream->set_premultiplied_alpha(true);

  f->add_stream(image_stream);

  // If we're here, we have a successful image open
  in->close();

  return true;
}

bool OIIODecoder::Open()
{
  image_ = OIIO::ImageInput::open(stream()->footage()->filename().toStdString());

  if (!image_) {
    return false;
  }

  // Check if we can work with this pixel format
  const OIIO::ImageSpec& spec = image_->spec();

  width_ = spec.width;
  height_ = spec.height;

  // Weirdly, switch statement doesn't work correctly here
  if (spec.format == OIIO::TypeDesc::UINT8) {
    pix_fmt_ = PixelFormat::PIX_FMT_RGBA8;
  } else if (spec.format == OIIO::TypeDesc::UINT16) {
    pix_fmt_ = PixelFormat::PIX_FMT_RGBA16U;
  } else if (spec.format == OIIO::TypeDesc::HALF) {
    pix_fmt_ = PixelFormat::PIX_FMT_RGBA16F;
  } else if (spec.format == OIIO::TypeDesc::FLOAT) {
    pix_fmt_ = PixelFormat::PIX_FMT_RGBA32F;
  } else {
    qWarning() << "Failed to convert OIIO::ImageDesc to native pixel format";
    return false;
  }

  // FIXME: Many OIIO pixel formats are not handled here

  is_rgba_ = (spec.nchannels == kRGBAChannels);

  pix_fmt_info_ = PixelService::GetPixelFormatInfo(static_cast<PixelFormat::Format>(pix_fmt_));

  return true;
}

FramePtr OIIODecoder::RetrieveVideo(const rational &timecode)
{
  if (!open_ && !Open()) {
    return nullptr;
  }

  Q_UNUSED(timecode)

  if (!frame_) {
    frame_ = Frame::Create();

    frame_->set_width(width_);
    frame_->set_height(height_);
    frame_->set_format(pix_fmt_);
    frame_->allocate();

    // Use the native format to determine what format OIIO should return
    // FIXME: Behavior of RGB images as opposed to RGBA?
    image_->read_image(pix_fmt_info_.oiio_desc, frame_->data());

    if (!is_rgba_) {
      PixelService::ConvertRGBtoRGBA(frame_);
    }
  }

  return frame_;
}

void OIIODecoder::Close()
{
  if (image_ != nullptr) {
    image_->close();
#if OIIO_VERSION < 10903
    OIIO::ImageInput::destroy(image_);
#endif
    image_ = nullptr;
  }

  frame_ = nullptr;
}

int64_t OIIODecoder::GetTimestampFromTime(const rational &time)
{
  Q_UNUSED(time)

  // A still image will always return the same frame

  return 0;
}

bool OIIODecoder::SupportsVideo()
{
  return true;
}
