/*
    RawSpeed - RAW file decoder.

    Copyright (C) 2009-2014 Klaus Post
    Copyright (C) 2014 Pedro Côrte-Real
    Copyright (C) 2015-2018 Roman Lebedev

    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 2 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, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

#include "rawspeedconfig.h"
#include "decoders/CrwDecoder.h"
#include "adt/Array1DRef.h"
#include "adt/Casts.h"
#include "adt/Invariant.h"
#include "adt/Optional.h"
#include "adt/Point.h"
#include "common/RawImage.h"
#include "decoders/RawDecoder.h"
#include "decoders/RawDecoderException.h"
#include "decompressors/CrwDecompressor.h"
#include "io/Buffer.h"
#include "metadata/Camera.h"
#include "metadata/ColorFilterArray.h"
#include "tiff/CiffEntry.h"
#include "tiff/CiffIFD.h"
#include "tiff/CiffTag.h"
#include <array>
#include <cassert>
#include <cmath>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

using std::vector;

using std::abs;

namespace rawspeed {

class CameraMetaData;

bool CrwDecoder::isCRW(Buffer input) {
  static const std::array<char, 8> magic = {
      {'H', 'E', 'A', 'P', 'C', 'C', 'D', 'R'}};
  static const size_t magic_offset = 6;
  const Buffer data = input.getSubView(magic_offset, magic.size());
  return 0 == memcmp(data.begin(), magic.data(), magic.size());
}

CrwDecoder::CrwDecoder(std::unique_ptr<const CiffIFD> rootIFD, Buffer file)
    : RawDecoder(file), mRootIFD(std::move(rootIFD)) {}

RawImage CrwDecoder::decodeRawInternal() {
  const CiffEntry* rawData = mRootIFD->getEntry(CiffTag::RAWDATA);
  if (!rawData)
    ThrowRDE("Couldn't find the raw data chunk");

  const CiffEntry* sensorInfo =
      mRootIFD->getEntryRecursive(CiffTag::SENSORINFO);
  if (!sensorInfo || sensorInfo->count < 6 ||
      sensorInfo->type != CiffDataType::SHORT)
    ThrowRDE("Couldn't find image sensor info");

  assert(sensorInfo != nullptr);
  uint32_t width = sensorInfo->getU16(1);
  uint32_t height = sensorInfo->getU16(2);
  mRaw->dim = iPoint2D(width, height);

  if (width == 0 || height == 0 || width % 4 != 0 || width > 4104 ||
      height > 3048 || (height * width) % 64 != 0)
    ThrowRDE("Unexpected image dimensions found: (%u; %u)", width, height);

  const CiffEntry* decTable =
      mRootIFD->getEntryRecursive(CiffTag::DECODERTABLE);
  if (!decTable || decTable->type != CiffDataType::LONG)
    ThrowRDE("Couldn't find decoder table");

  assert(decTable != nullptr);
  uint32_t dec_table = decTable->getU32();

  bool lowbits = !hints.contains("no_decompressed_lowbits");

  ByteStream rawInput = rawData->getData();

  Optional<Array1DRef<const uint8_t>> lowbitInput;
  if (lowbits) {
    // If there are low bits, the first part (size is calculable) is low bits
    // Each block is 4 pairs of 2 bits, so we have 1 block per 4 pixels
    const int lBlocks = 1 * height * width / 4;
    invariant(lBlocks > 0);
    lowbitInput = rawInput.getStream(lBlocks);
  }

  // We always ignore next 514 bytes of 'padding'. No idea what is in there.
  rawInput.skipBytes(514);

  Array1DRef<const uint8_t> input =
      rawInput.peekRemainingBuffer().getAsArray1DRef();

  CrwDecompressor c(mRaw, dec_table, input, lowbitInput);
  mRaw->createData();
  c.decompress();

  return mRaw;
}

void CrwDecoder::checkSupportInternal(const CameraMetaData* meta) {
  vector<const CiffIFD*> data = mRootIFD->getIFDsWithTag(CiffTag::MAKEMODEL);
  if (data.empty())
    ThrowRDE("Model name not found");
  vector<std::string> makemodel =
      data[0]->getEntry(CiffTag::MAKEMODEL)->getStrings();
  if (makemodel.size() < 2)
    ThrowRDE("wrong number of strings for make/model");
  const std::string& make = makemodel[0];
  const std::string& model = makemodel[1];

  this->checkCameraSupported(meta, make, model, "");
}

// based on exiftool's Image::ExifTool::Canon::CanonEv
float RAWSPEED_READNONE CrwDecoder::canonEv(const int64_t in) {
  // remove sign
  int64_t val = abs(in);
  // remove fraction
  int64_t frac = val & 0x1f;
  val -= frac;
  // convert 1/3 (0x0c) and 2/3 (0x14) codes
  if (frac == 0x0c) {
    frac = implicit_cast<int64_t>(32.0F / 3);
  } else if (frac == 0x14) {
    frac = implicit_cast<int64_t>(64.0F / 3);
  }
  return copysignf(implicit_cast<float>(val + frac) / 32.0F,
                   implicit_cast<float>(in));
}

void CrwDecoder::decodeMetaDataInternal(const CameraMetaData* meta) {
  int iso = 0;
  mRaw->cfa.setCFA(iPoint2D(2, 2), CFAColor::RED, CFAColor::GREEN,
                   CFAColor::GREEN, CFAColor::BLUE);
  vector<const CiffIFD*> data = mRootIFD->getIFDsWithTag(CiffTag::MAKEMODEL);
  if (data.empty())
    ThrowRDE("Model name not found");
  vector<std::string> makemodel =
      data[0]->getEntry(CiffTag::MAKEMODEL)->getStrings();
  if (makemodel.size() < 2)
    ThrowRDE("wrong number of strings for make/model");
  const std::string& make = makemodel[0];
  const std::string& model = makemodel[1];
  std::string mode;

  if (mRootIFD->hasEntryRecursive(CiffTag::SHOTINFO)) {
    const CiffEntry* shot_info = mRootIFD->getEntryRecursive(CiffTag::SHOTINFO);
    if (shot_info->type == CiffDataType::SHORT && shot_info->count >= 2) {
      // os << exp(canonEv(value.toLong()) * log(2.0)) * 100.0 / 32.0;
      uint16_t iso_index = shot_info->getU16(2);
      iso = implicit_cast<int>(
          expf(canonEv(static_cast<int64_t>(iso_index)) * logf(2.0)) * 100.0F /
          32.0F);
    }
  }

  // Fetch the white balance
  try {
    if (mRootIFD->hasEntryRecursive(static_cast<CiffTag>(0x0032))) {
      const CiffEntry* wb =
          mRootIFD->getEntryRecursive(static_cast<CiffTag>(0x0032));
      if (wb->type == CiffDataType::BYTE && wb->count == 768) {
        // We're in a D30 file, values are RGGB
        // This, not 0x102c tag, should be used.
        std::array<uint16_t, 4> wbMuls{
            {wb->getU16(36), wb->getU16(37), wb->getU16(38), wb->getU16(39)}};
        for (const auto& mul : wbMuls) {
          if (0 == mul)
            ThrowRDE("WB coefficient is zero!");
        }

        std::array<float, 4> wbCoeffs = {};
        wbCoeffs[0] = static_cast<float>(1024.0 / wbMuls[0]);
        wbCoeffs[1] =
            static_cast<float>((1024.0 / wbMuls[1]) + (1024.0 / wbMuls[2])) /
            2.0F;
        wbCoeffs[2] = static_cast<float>(1024.0 / wbMuls[3]);
        mRaw->metadata.wbCoeffs = wbCoeffs;
      } else if (wb->type == CiffDataType::BYTE &&
                 wb->count > 768) { // Other G series and S series cameras
        // correct offset for most cameras
        int offset = hints.get("wb_offset", 120);

        std::array<uint16_t, 2> key = {{0x410, 0x45f3}};
        if (!hints.contains("wb_mangle"))
          key[0] = key[1] = 0;

        offset /= 2;
        std::array<float, 4> wbCoeffs = {};
        wbCoeffs[0] = static_cast<float>(wb->getU16(offset + 1) ^ key[1]);
        wbCoeffs[1] = static_cast<float>(wb->getU16(offset + 0) ^ key[0]);
        wbCoeffs[2] = static_cast<float>(wb->getU16(offset + 2) ^ key[0]);
        mRaw->metadata.wbCoeffs = wbCoeffs;
      }
    }
    if (mRootIFD->hasEntryRecursive(static_cast<CiffTag>(0x102c))) {
      const CiffEntry* entry =
          mRootIFD->getEntryRecursive(static_cast<CiffTag>(0x102c));
      if (entry->type == CiffDataType::SHORT && entry->getU16() > 512) {
        // G1/Pro90 CYGM pattern
        std::array<float, 4> wbCoeffs = {};
        wbCoeffs[0] = static_cast<float>(entry->getU16(62));
        wbCoeffs[1] = static_cast<float>(entry->getU16(63));
        wbCoeffs[2] = static_cast<float>(entry->getU16(60));
        wbCoeffs[3] = static_cast<float>(entry->getU16(61));
        mRaw->metadata.wbCoeffs = wbCoeffs;
      } else if (entry->type == CiffDataType::SHORT && entry->getU16() != 276) {
        /* G2, S30, S40 */
        std::array<float, 4> wbCoeffs = {};
        wbCoeffs[0] = static_cast<float>(entry->getU16(51));
        wbCoeffs[1] = (static_cast<float>(entry->getU16(50)) +
                       static_cast<float>(entry->getU16(53))) /
                      2.0F;
        wbCoeffs[2] = static_cast<float>(entry->getU16(52));
        mRaw->metadata.wbCoeffs = wbCoeffs;
      }
    }
    if (mRootIFD->hasEntryRecursive(CiffTag::SHOTINFO) &&
        mRootIFD->hasEntryRecursive(CiffTag::WHITEBALANCE)) {
      const CiffEntry* shot_info =
          mRootIFD->getEntryRecursive(CiffTag::SHOTINFO);
      uint16_t wb_index = shot_info->getU16(7);
      const CiffEntry* wb_data =
          mRootIFD->getEntryRecursive(CiffTag::WHITEBALANCE);
      /* CANON EOS D60, CANON EOS 10D, CANON EOS 300D */
      if (wb_index > 9)
        ThrowRDE("Invalid white balance index");
      int wb_offset =
          1 + ((std::string_view("0134567028")[wb_index] - '0') * 4);
      std::array<float, 4> wbCoeffs = {};
      wbCoeffs[0] = wb_data->getU16(wb_offset + 0);
      wbCoeffs[1] = wb_data->getU16(wb_offset + 1);
      wbCoeffs[2] = wb_data->getU16(wb_offset + 3);
      mRaw->metadata.wbCoeffs = wbCoeffs;
    }
  } catch (const RawspeedException& e) {
    mRaw->setError(e.what());
    // We caught an exception reading WB, just ignore it
  }

  setMetaData(meta, make, model, mode, iso);
}

} // namespace rawspeed
