/*
 * libopenraw - mrwcontainer.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 <fcntl.h>
#include <stddef.h>

#include <libopenraw/debug.h>

#include "trace.hpp"
#include "mrwcontainer.hpp"

using namespace Debug;

namespace OpenRaw {
namespace Internals {

namespace MRW {

DataBlock::DataBlock(off_t start, MRWContainer *_container)
    : m_start(start), m_container(_container), m_loaded(false)
{
    LOGDBG2("> DataBlock start == %lld\n", (long long int)start);
    if (m_container->fetchData(m_name, m_start, 4) != 4) {
        // FIXME: Handle error
        LOGWARN("  Error reading block name %lld\n", (long long int)start);
        return;
    }
    auto result = m_container->readInt32(m_container->file(), m_container->endian());
    if (result.empty()) {
        // FIXME: Handle error
        LOGWARN("  Error reading block length %lld\n", (long long int)start);
        return;
    }
    m_length = result.value();
    LOGDBG1("  DataBlock %s, length %d at %lld\n", name().c_str(), m_length,
            (long long int)m_start);
    LOGDBG2("< DataBlock\n");
    m_loaded = true;
}

Option<uint8_t>
DataBlock::uint8_val(off_t off) const
{
    MRWContainer *mc = m_container;
    mc->file()->seek(m_start + DataBlockHeaderLength + off, SEEK_SET);
    return  mc->readUInt8(mc->file());
}

Option<uint16_t>
DataBlock::uint16_val(off_t off) const
{
    MRWContainer *mc = m_container;
    mc->file()->seek(m_start + DataBlockHeaderLength + off, SEEK_SET);
    return mc->readUInt16(mc->file(), mc->endian());
}

Option<std::string>
DataBlock::string_val(off_t off) const
{
    char buf[9];
    size_t s;
    MRWContainer *mc = m_container;
    s = mc->fetchData(buf, m_start + DataBlockHeaderLength + off, 8);
    if (s != 8) {
        return Option<std::string>();
    }
    buf[8] = 0;
    return Option<std::string>(buf);
}

}

MRWContainer::MRWContainer(const IO::Stream::Ptr &_file, off_t _offset)
    : IfdFileContainer(_file, _offset)
{
}

IfdFileContainer::EndianType MRWContainer::isMagicHeader(const char *p, int len)
{
    if (len < 4) {
        // we need at least 4 bytes to check
        return ENDIAN_NULL;
    }

    if ((p[0] == 0x00) && (p[1] == 'M') && (p[2] == 'R') && (p[3] == 'M')) {

        LOGDBG1("Identified MRW file\n");

        return ENDIAN_BIG;
    }

    LOGDBG1("Unidentified MRW file\n");

    return ENDIAN_NULL;
}

bool MRWContainer::locateDirsPreHook()
{
    char version[9];
    off_t position;

    LOGDBG1("> MRWContainer::locateDirsPreHook()\n");
    m_endian = ENDIAN_BIG;

    /* MRW file always starts with an MRM datablock. */
    mrm = std::make_shared<MRW::DataBlock>(m_offset, this);
    if (mrm->name() != "MRM") {
        LOGWARN("MRW file begins not with MRM block, "
                "but with unrecognized DataBlock :: name == %s\n",
                mrm->name().c_str());
        return false;
    }

    /* Subblocks are contained within the MRM block. Scan them and create
     * appropriate block descriptors.
     */
    position = mrm->offset() + MRW::DataBlockHeaderLength;
    while (position < pixelDataOffset()) {
        auto ref = std::make_shared<MRW::DataBlock>(position, this);
        LOGDBG1("Loaded DataBlock :: name == %s\n", ref->name().c_str());
        if (!ref || !ref->loaded()) {
            break;
        }
        if (ref->name() == "PRD") {
            if (prd) {
                LOGWARN("File contains duplicate DataBlock :: name == %s\n",
                        ref->name().c_str());
            }
            prd = ref;
        } else if (ref->name() == "TTW") {
            if (ttw) {
                LOGWARN("File contains duplicate DataBlock :: name == %s\n",
                        ref->name().c_str());
            }
            ttw = ref;
        } else if (ref->name() == "WBG") {
            if (wbg) {
                LOGWARN("File contains duplicate DataBlock :: name == %s\n",
                        ref->name().c_str());
            }
            wbg = ref;
        } else if (ref->name() == "RIF") {
            if (rif) {
                LOGWARN("File contains duplicate DataBlock :: name == %s\n",
                        ref->name().c_str());
            }
            rif = ref;
        } else if (ref->name() != "PAD") {
            LOGWARN("File contains unrecognized DataBlock :: name == %s\n",
                    ref->name().c_str());
        }
        position = ref->offset() + MRW::DataBlockHeaderLength + ref->length();
    }

    /* Check that we found all the expected data blocks. */
    if (!prd) {
        LOGWARN("File does NOT contain expected DataBlock :: name == PRD\n");
        return false;
    }
    if (!ttw) {
        LOGWARN("File does NOT contain expected DataBlock :: name == TTW\n");
        return false;
    }
    if (!wbg) {
        LOGWARN("File does NOT contain expected DataBlock :: name == WBG\n");
        return false;
    }
    if (!rif) {
        LOGWARN("File does NOT contain expected DataBlock :: name == RIF\n");
        return false;
    }

    /* Extract the file version string. */
    if (fetchData(version,
                  prd->offset() + MRW::DataBlockHeaderLength + MRW::PRD_VERSION,
                  8) != 8) {
        // FIXME: Handle error
        LOGDBG1("  Error reading version string\n");
    }
    version[8] = '\0';
    m_version = std::string(version);
    LOGDBG1("  MRW file version == %s\n", m_version.c_str());

    /* For the benefit of our parent class, set the container offset to the
     * beginning of
     * the TIFF data (the contents of the TTW data block), and seek there.
     */
    m_offset = ttw->offset() + MRW::DataBlockHeaderLength;

    // TODO: Not sure exactly here the origin of this.
    // But it doesn't work.
    //  if((version[2] != '7') || (version[3] != '3')) {
    setExifOffsetCorrection(m_offset);
    LOGDBG1("setting correction to %lld\n", (long long int)m_offset);
    //  }

    m_file->seek(m_offset, SEEK_SET);
    LOGDBG1("< MRWContainer\n");

    return true;
}

}
}
