/*
 * libopenraw - mrwfile.cpp
 *
 * Copyright (C) 2006-2023 Hubert Figuière
 * Copyright (C) 2008 Bradley Broom
 *
 * 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 <stddef.h>
#include <sys/types.h>
#include <cstdint>
#include <string>
#include <memory>

#include <libopenraw/cameraids.h>
#include <libopenraw/debug.h>

#include "thumbnail.hpp"
#include "rawdata.hpp"

#include "trace.hpp"
#include "io/stream.hpp"
#include "mrwcontainer.hpp"
#include "ifd.hpp"
#include "ifdentry.hpp"
#include "ifdfilecontainer.hpp"
#include "mrwfile.hpp"
#include "unpack.hpp"
#include "rawfile_private.hpp"

using namespace Debug;

namespace OpenRaw {
namespace Internals {

#define OR_MAKE_MINOLTA_TYPEID(camid) \
    OR_MAKE_FILE_TYPEID(OR_TYPEID_VENDOR_MINOLTA,camid)

/* taken from dcraw, by default */
static const BuiltinColourMatrix s_matrices[] = {
    { OR_MAKE_MINOLTA_TYPEID(OR_TYPEID_MINOLTA_MAXXUM_5D), 0, 0xffb,
	{ 10284,-3283,-1086,-7957,15762,2316,-829,882,6644 } },
    { OR_MAKE_MINOLTA_TYPEID(OR_TYPEID_MINOLTA_MAXXUM_7D), 0, 0xffb,
	{ 10239,-3104,-1099,-8037,15727,2451,-927,925,6871 } },
    { OR_MAKE_MINOLTA_TYPEID(OR_TYPEID_MINOLTA_DIMAGE5), 0, 0xf7d,
	{ 8983,-2942,-963,-6556,14476,2237,-2426,2887,8014 } },
    { OR_MAKE_MINOLTA_TYPEID(OR_TYPEID_MINOLTA_DIMAGE7), 0, 0xf7d,
	{ 9144,-2777,-998,-6676,14556,2281,-2470,3019,7744 } },
    { OR_MAKE_MINOLTA_TYPEID(OR_TYPEID_MINOLTA_DIMAGE7I), 0, 0xf7d,
	{ 9144,-2777,-998,-6676,14556,2281,-2470,3019,7744 } },
    { OR_MAKE_MINOLTA_TYPEID(OR_TYPEID_MINOLTA_DIMAGE7HI), 0, 0xf7d,
	{ 11368,-3894,-1242,-6521,14358,2339,-2475,3056,7285 } },
    { OR_MAKE_MINOLTA_TYPEID(OR_TYPEID_MINOLTA_A1), 0, 0xf8b,
	{ 9274,-2547,-1167,-8220,16323,1943,-2273,2720,8340 } },
    { OR_MAKE_MINOLTA_TYPEID(OR_TYPEID_MINOLTA_A2), 0, 0xf8f,
	{ 9097,-2726,-1053,-8073,15506,2762,-966,981,7763 } },
    { OR_MAKE_MINOLTA_TYPEID(OR_TYPEID_MINOLTA_A200), 0, 0,
	{ 8560,-2487,-986,-8112,15535,2771,-1209,1324,7743 } },

    { 0, 0, 0, { 0, 0, 0, 0, 0, 0, 0, 0, 0 } }
};

const struct IfdFile::camera_ids_t MRWFile::s_def[] = {
    { "21860002", OR_MAKE_MINOLTA_TYPEID(OR_TYPEID_MINOLTA_MAXXUM_5D) },
    { "21810002", OR_MAKE_MINOLTA_TYPEID(OR_TYPEID_MINOLTA_MAXXUM_7D) },
    { "27730001", OR_MAKE_MINOLTA_TYPEID(OR_TYPEID_MINOLTA_DIMAGE5) },
    { "27660001", OR_MAKE_MINOLTA_TYPEID(OR_TYPEID_MINOLTA_DIMAGE7) },
    { "27790001", OR_MAKE_MINOLTA_TYPEID(OR_TYPEID_MINOLTA_DIMAGE7I) },
    { "27780001", OR_MAKE_MINOLTA_TYPEID(OR_TYPEID_MINOLTA_DIMAGE7HI) },
    { "27820001", OR_MAKE_MINOLTA_TYPEID(OR_TYPEID_MINOLTA_A1) },
    { "27200001", OR_MAKE_MINOLTA_TYPEID(OR_TYPEID_MINOLTA_A2) },
    { "27470002", OR_MAKE_MINOLTA_TYPEID(OR_TYPEID_MINOLTA_A200) },
    { 0, 0 }
};

RawFile *MRWFile::factory(const IO::Stream::Ptr &_f)
{
    return new MRWFile(_f);
}

MRWFile::MRWFile(const IO::Stream::Ptr &_f)
    : IfdFile(_f, OR_RAWFILE_TYPE_MRW, false)
{
    _setIdMap(s_def);
    _setMatrices(s_matrices);
    m_container = new MRWContainer (m_io, 0);
}

IfdDir::Ref  MRWFile::_locateCfaIfd()
{
    // in MRW the CFA IFD is the main IFD
    return mainIfd();
}

IfdDir::Ref  MRWFile::_locateMainIfd()
{
    auto ifd = m_container->setDirectory(0);
    if (ifd) {
        ifd->setType(OR_IFD_MAIN);
    }
    return ifd;
}

void MRWFile::_identifyId()
{
    MRWContainer *mc = (MRWContainer *)m_container;

    // it is important that the main IFD be loaded.
    // this ensures it.
    const IfdDir::Ref & _mainIfd = mainIfd();

    if(_mainIfd && mc->prd) {
        auto version = mc->prd->string_val(MRW::PRD_VERSION);
        if (version) {
            _setTypeId(_typeIdFromModel("Minolta Co., Ltd.", version.value()));
        } else {
            LOGERR("Coudln't read Minolta version\n");
        }
    }
}


/* This code only knows about Dimage 5/7, in which the thumbnail position is
 * special. */
::or_error MRWFile::_enumThumbnailSizes(std::vector<uint32_t> &list)
{
    list.push_back(640);
    return OR_ERROR_NONE;
}

/* This code only knows about Dimage 5/7, in which the thumbnail position is
 * special. */
::or_error MRWFile::_getThumbnail(uint32_t /*size*/, Thumbnail & thumbnail)
{
    auto mnote = makerNoteIfd();
    if (!mnote) {
        LOGWARN("No MakerNote found\n");
        return OR_ERROR_NOT_FOUND;
    }

    uint32_t tnail_offset = 0;
    uint32_t tnail_len = 0;
    IfdEntry::Ref thumb_ent = mnote->getEntry(MRW::MRWTAG_THUMBNAIL);
    if (thumb_ent) {
        tnail_offset = thumb_ent->offset();
        tnail_len = thumb_ent->count();
    } else {
        auto result = mnote->getValue<uint32_t>(MRW::MRWTAG_THUMBNAIL_OFFSET);
        if (result.empty()) {
            LOGWARN("thumbnail offset entry not found\n");
            return OR_ERROR_NOT_FOUND;
        }
        tnail_offset = result.value();

        result = mnote->getValue<uint32_t>(MRW::MRWTAG_THUMBNAIL_LENGTH);
        if (result.empty()) {
            LOGWARN("thumbnail lenght entry not found\n");
            return OR_ERROR_NOT_FOUND;
        }
        tnail_len = result.value();
    }

    LOGDBG1("thumbnail offset found, offset == %u count == %u\n",
            tnail_offset, tnail_len);
    void *p = thumbnail.allocData(tnail_len);
    MRWContainer *mc = (MRWContainer *)m_container;
    size_t fetched = m_container->fetchData(p, mc->ttw->offset()
                                            + MRW::DataBlockHeaderLength
                                            + tnail_offset,
                                            tnail_len);
    if (fetched != tnail_len) {
        LOGWARN("Unable to fetch all thumbnail data: %lu not %u bytes\n",
                (LSIZE)fetched, tnail_len);
    }
    /* Need to patch first byte. */
    ((unsigned char *)p)[0] = 0xFF;

    thumbnail.setDataType(OR_DATA_TYPE_JPEG);
    thumbnail.setDimensions(640, 480);
    return OR_ERROR_NONE;
}

::or_error MRWFile::_getRawData(RawData & data, uint32_t options)
{
    or_error ret = OR_ERROR_NONE;
    MRWContainer *mc = (MRWContainer *)m_container;

    if (!mc->prd) {
        return OR_ERROR_NOT_FOUND;
    }
    /* Obtain sensor dimensions from PRD block. */
    uint16_t y = mc->prd->uint16_val(MRW::PRD_SENSOR_LENGTH).value_or(0);
    uint16_t x = mc->prd->uint16_val(MRW::PRD_SENSOR_WIDTH).value_or(0);
    uint8_t bpc = mc->prd->uint8_val(MRW::PRD_PIXEL_SIZE).value_or(0);

    bool is_compressed = (mc->prd->uint8_val(MRW::PRD_STORAGE_TYPE).value_or(MRW::STORAGE_TYPE_UNPACKED) == MRW::STORAGE_TYPE_PACKED);
    /* Allocate space for and retrieve pixel data.
     * Currently only for cameras that don't compress pixel data.
     */
    /* Set pixel array parameters. */
    uint32_t finaldatalen = 2 * x * y;
    uint32_t datalen =
        (is_compressed ? x * y + ((x * y) >> 1) : finaldatalen);

    if (options & OR_OPTIONS_DONT_DECOMPRESS) {
        finaldatalen = datalen;
    }
    if (is_compressed && (options & OR_OPTIONS_DONT_DECOMPRESS)) {
        data.setDataType(OR_DATA_TYPE_COMPRESSED_RAW);
    } else {
        data.setDataType(OR_DATA_TYPE_RAW);
    }
    data.setBpc(bpc);
    // this seems to be the hardcoded value.
    uint16_t black = 0;
    uint16_t white = 0;
    RawFile::_getBuiltinLevels(_getMatrices(), typeId(), black, white);
    data.setBlackLevel(black);
    data.setWhiteLevel(white);
    LOGDBG1("datalen = %d final datalen = %u\n", datalen, finaldatalen);
    void *p = data.allocData(finaldatalen);
    size_t fetched = 0;
    off_t offset = mc->pixelDataOffset();
    if (!is_compressed || (options & OR_OPTIONS_DONT_DECOMPRESS)) {
        fetched = m_container->fetchData(p, offset, datalen);
        uint16_t* pixels = (uint16_t*)p;
        for (auto i = 0; i < x * y; i++) {
            *pixels = be16toh(*pixels);
            pixels++;
        }
    } else {
        Unpack unpack(x, IFD::COMPRESS_NONE);
        size_t blocksize = unpack.block_size();
        auto block = std::make_unique<uint8_t[]>(blocksize);
        uint16_t* outdata = (uint16_t*)data.data();
        size_t outsize = finaldatalen;
        size_t got;
        do {
            LOGDBG2("fetchData @offset %lld\n", (long long int)offset);
            got = m_container->fetchData(block.get(),
                                         offset, blocksize);
            fetched += got;
            offset += got;
            LOGDBG2("got %lu\n", (LSIZE)got);
            if (got) {
                size_t out;
                or_error err = unpack.unpack_be12to16(outdata, outsize,
                                                      block.get(), got, out);
                outdata += out / 2;
                outsize -= out;
                LOGDBG2("unpacked %lu bytes from %lu\n", (LSIZE)out, (LSIZE)got);
                if (err != OR_ERROR_NONE) {
                    ret = err;
                    break;
                }
            }
        } while ((got != 0) && (fetched < datalen));
    }
    if (fetched < datalen) {
        LOGWARN("Fetched only %lu of %u: continuing anyway.\n", (LSIZE)fetched,
                datalen);
    }
    uint16_t bpat = mc->prd->uint16_val(MRW::PRD_BAYER_PATTERN)
        .value_or(MRW::BAYER_PATTERN_RGGB);
    or_cfa_pattern cfa_pattern = OR_CFA_PATTERN_NONE;
    switch (bpat)
    {
    case MRW::BAYER_PATTERN_RGGB:
        cfa_pattern = OR_CFA_PATTERN_RGGB;
        break;
    case MRW::BAYER_PATTERN_GBRG:
        cfa_pattern = OR_CFA_PATTERN_GBRG;
        break;
    default:
        break;
    }
    data.setCfaPatternType(cfa_pattern);
    data.setDimensions(x, y);

    return ret;
}

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