// ***************************************************************** -*- C++ -*-
/*
 * Copyright (C) 2004-2018 Exiv2 authors
 * This program is part of the Exiv2 distribution.
 *
 * 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 2
 * 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA.
 */
/*
  File:      tags.cpp
  Author(s): Andreas Huggel (ahu) <ahuggel@gmx.net>
             Gilles Caulier (gc) <caulier dot gilles at gmail dot com>
  History:   15-Jan-04, ahu: created
             21-Jan-05, ahu: added MakerNote TagInfo registry and related code
 */
// *****************************************************************************
// included header files
#include "types.hpp"
#include "tags.hpp"
#include "tags_int.hpp"
#include "error.hpp"
#include "futils.hpp"
#include "value.hpp"
#include "convert.hpp"
#include "i18n.h"                // NLS support.

#include "canonmn_int.hpp"
#include "casiomn_int.hpp"
#include "fujimn_int.hpp"
#include "minoltamn_int.hpp"
#include "nikonmn_int.hpp"
#include "olympusmn_int.hpp"
#include "panasonicmn_int.hpp"
#include "pentaxmn_int.hpp"
#include "samsungmn_int.hpp"
#include "sigmamn_int.hpp"
#include "sonymn_int.hpp"

#include <iostream>
#include <iomanip>
#include <sstream>
#include <utility>
#include <cstdlib>
#include <cassert>
#include <cmath>
#include <cstring>


// *****************************************************************************
// class member definitions
namespace Exiv2 {

    using namespace Internal;


    //! List of all defined Exif sections.
    extern const SectionInfo sectionInfo[] = {
        { sectionIdNotSet, "(UnknownSection)",     N_("Unknown section")              },
        { imgStruct,       "ImageStructure",       N_("Image data structure")         },
        { recOffset,       "RecordingOffset",      N_("Recording offset")             },
        { imgCharacter,    "ImageCharacteristics", N_("Image data characteristics")   },
        { otherTags,       "OtherTags",            N_("Other data")                   },
        { exifFormat,      "ExifFormat",           N_("Exif data structure")          },
        { exifVersion,     "ExifVersion",          N_("Exif version")                 },
        { imgConfig,       "ImageConfig",          N_("Image configuration")          },
        { userInfo,        "UserInfo",             N_("User information")             },
        { relatedFile,     "RelatedFile",          N_("Related file")                 },
        { dateTime,        "DateTime",             N_("Date and time")                },
        { captureCond,     "CaptureConditions",    N_("Picture taking conditions")    },
        { gpsTags,         "GPS",                  N_("GPS information")              },
        { iopTags,         "Interoperability",     N_("Interoperability information") },
        { mpfTags,         "MPF",                  N_("CIPA Multi-Picture Format")    },
        { makerTags,       "Makernote",            N_("Vendor specific information")  },
        { dngTags,         "DngTags",              N_("Adobe DNG tags")               },
        { panaRaw,         "PanasonicRaw",         N_("Panasonic RAW tags")           },
        { tiffEp,          "TIFF/EP",              N_("TIFF/EP tags")                 },
        { tiffPm6,         "TIFF&PM6",             N_("TIFF PageMaker 6.0 tags")      },
        { adobeOpi,        "AdobeOPI",             N_("Adobe OPI tags")               },
        { lastSectionId,   "(LastSection)",        N_("Last section")                 }
    };

} // namespace Exiv2

namespace Exiv2 {
    namespace Internal {

    bool TagVocabulary::operator==(const std::string& key) const
    {
        if (strlen(voc_) > key.size()) return false;
        return 0 == strcmp(voc_, key.c_str() + key.size() - strlen(voc_));
    }

    // Unknown Tag
    static const TagInfo unknownTag(0xffff, "Unknown tag", N_("Unknown tag"),
                                    N_("Unknown tag"),
                                    ifdIdNotSet, sectionIdNotSet, asciiString, -1, printValue);




}}                                      // namespace Internal, Exiv2

namespace Exiv2 {

    //! @cond IGNORE
    GroupInfo::GroupName::GroupName(const std::string& groupName): g_(groupName)
    {
    }
    //! @endcond

    bool GroupInfo::operator==(int ifdId) const
    {
        return ifdId_ == ifdId;
    }

    bool GroupInfo::operator==(const GroupName& groupName) const
    {
        return 0 == strcmp(groupName.g_.c_str(), groupName_);
    }

    TagInfo::TagInfo(
        uint16_t tag,
        const char* name,
        const char* title,
        const char* desc,
        int ifdId,
        int sectionId,
        TypeId typeId,
        int16_t count,
        PrintFct printFct
    )
        : tag_(tag), name_(name), title_(title), desc_(desc), ifdId_(ifdId),
          sectionId_(sectionId), typeId_(typeId), count_(count), printFct_(printFct)
    {
    }

    const char* ExifTags::sectionName(const ExifKey& key)
    {
        const TagInfo* ti = tagInfo(key.tag(), static_cast<Internal::IfdId>(key.ifdId()));
        if (ti == nullptr) return sectionInfo[unknownTag.sectionId_].name_;
        return sectionInfo[ti->sectionId_].name_;
    }

    uint16_t ExifTags::defaultCount(const ExifKey& key)
    {
        const TagInfo* ti = tagInfo(key.tag(), static_cast<Internal::IfdId>(key.ifdId()));
        if (ti == nullptr) return unknownTag.count_;
        return ti->count_;
    }

    const char* ExifTags::ifdName(const std::string& groupName)
    {
        IfdId ifdId = Internal::groupId(groupName);
        return Internal::ifdName(ifdId);
    }

    bool ExifTags::isMakerGroup(const std::string& groupName)
    {
        IfdId ifdId = Internal::groupId(groupName);
        return Internal::isMakerIfd(ifdId);
    }

    bool ExifTags::isExifGroup(const std::string& groupName)
    {
        IfdId ifdId = Internal::groupId(groupName);
        return Internal::isExifIfd(ifdId);
    }

    const GroupInfo *ExifTags::groupList()
    {
        return Internal::groupList();
    }

    const TagInfo *ExifTags::tagList(const std::string &groupName)
    {
        return Internal::tagList(groupName);
    }

    void ExifTags::taglist(std::ostream& os)
    {
        const TagInfo* ifd = ifdTagList();
        const TagInfo* exif = exifTagList();
        const TagInfo* gps = gpsTagList();
        const TagInfo* iop = iopTagList();

        for (int i=0; ifd[i].tag_ != 0xffff; ++i) {
            os << ifd[i] << "\n";
        }
        for (int i=0; exif[i].tag_ != 0xffff; ++i) {
            os << exif[i] << "\n";
        }
        for (int i=0; iop[i].tag_ != 0xffff; ++i) {
            os << iop[i] << "\n";
        }
        for (int i=0; gps[i].tag_ != 0xffff; ++i) {
            os << gps[i] << "\n";
        }
    } // ExifTags::taglist

    void ExifTags::taglist(std::ostream& os, const std::string& groupName)
    {
        IfdId ifdId = Internal::groupId(groupName);
        Internal::taglist(os, ifdId);
    }

    //! %Internal Pimpl structure with private members and data of class ExifKey.
    struct ExifKey::Impl {
        //! @name Creators
        //@{
        Impl();                         //!< Default constructor
        //@}

        //! @name Manipulators
        //@{
        /*!
          @brief Set the key corresponding to the \em tag, \em ifdId and \em tagInfo.
                 The key is of the form '<b>Exif</b>.groupName.tagName'.
         */
        void makeKey(uint16_t tag, IfdId ifdId, const TagInfo* tagInfo);
        /*!
          @brief Parse and convert the key string into tag and IFD Id.
                 Updates data members if the string can be decomposed,
                 or throws \em Error .

          @throw Error if the key cannot be decomposed.
         */
        void decomposeKey(const std::string& key);
        //@}

        //! @name Accessors
        //@{
        //! Return the name of the tag
        std::string tagName() const;
        //@}

        // DATA
        static const char* familyName_; //!< "Exif"

        const TagInfo* tagInfo_;        //!< Tag info
        uint16_t tag_;                  //!< Tag value
        IfdId ifdId_;                   //!< The IFD associated with this tag
        int idx_;                       //!< Unique id of the Exif key in the image
        std::string groupName_;         //!< The group name
        std::string key_;               //!< %Key
    };

    const char* ExifKey::Impl::familyName_ = "Exif";

    ExifKey::Impl::Impl()
        : tagInfo_(nullptr), tag_(0), ifdId_(ifdIdNotSet), idx_(0)
    {
    }

    std::string ExifKey::Impl::tagName() const
    {
        if (tagInfo_ != nullptr && tagInfo_->tag_ != 0xffff) {
            return tagInfo_->name_;
        }
        std::ostringstream os;
        os << "0x" << std::setw(4) << std::setfill('0') << std::right
           << std::hex << tag_;
        return os.str();
    }

    void ExifKey::Impl::decomposeKey(const std::string& key)
    {
        // Get the family name, IFD name and tag name parts of the key
        std::string::size_type pos1 = key.find('.');
        if (pos1 == std::string::npos) throw Error(kerInvalidKey, key);
        std::string familyName = key.substr(0, pos1);
        if (0 != strcmp(familyName.c_str(), familyName_)) {
            throw Error(kerInvalidKey, key);
        }
        std::string::size_type pos0 = pos1 + 1;
        pos1 = key.find('.', pos0);
        if (pos1 == std::string::npos) throw Error(kerInvalidKey, key);
        std::string groupName = key.substr(pos0, pos1 - pos0);
        if (groupName.empty()) throw Error(kerInvalidKey, key);
        std::string tn = key.substr(pos1 + 1);
        if (tn.empty()) throw Error(kerInvalidKey, key);

        // Find IfdId
        IfdId ifdId = groupId(groupName);
        if (ifdId == ifdIdNotSet) throw Error(kerInvalidKey, key);
        if (!Internal::isExifIfd(ifdId) && !Internal::isMakerIfd(ifdId)) {
            throw Error(kerInvalidKey, key);
        }
        // Convert tag
        uint16_t tag = tagNumber(tn, ifdId);
        // Get tag info
        tagInfo_ = tagInfo(tag, ifdId);
        if (tagInfo_ == nullptr) throw Error(kerInvalidKey, key);

        tag_ = tag;
        ifdId_ = ifdId;
        groupName_ = groupName;
        // tagName() translates hex tag name (0xabcd) to a real tag name if there is one
        key_ = familyName + "." + groupName + "." + tagName();
    }

    void ExifKey::Impl::makeKey(uint16_t tag, IfdId ifdId, const TagInfo* tagInfo)
    {
        assert(tagInfo != 0);

        tagInfo_ = tagInfo;
        tag_ = tag;
        ifdId_ = ifdId;
        key_ = std::string(familyName_) + "." + groupName_ + "." + tagName();
    }

    ExifKey::ExifKey(uint16_t tag, const std::string& groupName)
        : p_(new Impl)
    {
        IfdId ifdId = groupId(groupName);
        // Todo: Test if this condition can be removed
        if (!Internal::isExifIfd(ifdId) && !Internal::isMakerIfd(ifdId)) {
            throw Error(kerInvalidIfdId, ifdId);
        }
        const TagInfo* ti = tagInfo(tag, ifdId);
        if (ti == nullptr) {
            throw Error(kerInvalidIfdId, ifdId);
        }
        p_->groupName_ = groupName;
        p_->makeKey(tag, ifdId, ti);
    }

    ExifKey::ExifKey(const TagInfo& ti)
        : p_(new Impl)
    {
        IfdId ifdId = static_cast<IfdId>(ti.ifdId_);
        if (!Internal::isExifIfd(ifdId) && !Internal::isMakerIfd(ifdId)) {
            throw Error(kerInvalidIfdId, ifdId);
        }
        p_->groupName_ = Exiv2::groupName(ifdId);
        p_->makeKey(ti.tag_, ifdId, &ti);
    }

    ExifKey::ExifKey(const std::string& key)
        : p_(new Impl)
    {
        p_->decomposeKey(key);
    }

    ExifKey::ExifKey(const ExifKey& rhs)
        : Key(rhs), p_(new Impl(*rhs.p_))
    {
    }

    ExifKey::~ExifKey() {}

    ExifKey& ExifKey::operator=(const ExifKey& rhs)
    {
        if (this == &rhs) return *this;
        Key::operator=(rhs);
        *p_ = *rhs.p_;
        return *this;
    }

    void ExifKey::setIdx(int idx)
    {
        p_->idx_ = idx;
    }

    std::string ExifKey::key() const
    {
        return p_->key_;
    }

    const char* ExifKey::familyName() const
    {
        return p_->familyName_;
    }

    std::string ExifKey::groupName() const
    {
        return p_->groupName_;
    }

    std::string ExifKey::tagName() const
    {
        return p_->tagName();
    }

    std::string ExifKey::tagLabel() const
    {
        if (p_->tagInfo_ == nullptr || p_->tagInfo_->tag_ == 0xffff) return "";
        return _(p_->tagInfo_->title_);
    }

    std::string ExifKey::tagDesc() const
    {
        if (p_->tagInfo_ == nullptr || p_->tagInfo_->tag_ == 0xffff) return "";
        return _(p_->tagInfo_->desc_);
    }

    TypeId ExifKey::defaultTypeId() const
    {
        if (p_->tagInfo_ == nullptr) return unknownTag.typeId_;
        return p_->tagInfo_->typeId_;
    }

    uint16_t ExifKey::tag() const
    {
        return p_->tag_;
    }

    ExifKey::UniquePtr ExifKey::clone() const
    {
        return UniquePtr(clone_());
    }

    ExifKey* ExifKey::clone_() const
    {
        return new ExifKey(*this);
    }

    int ExifKey::ifdId() const
    {
        return p_->ifdId_;
    }

    int ExifKey::idx() const
    {
        return p_->idx_;
    }

    // *************************************************************************
    // free functions

    std::ostream& operator<<(std::ostream& os, const TagInfo& ti)
    {
        std::ios::fmtflags f( os.flags() );
        ExifKey exifKey(ti);
        os << exifKey.tagName() << ",\t"
           << std::dec << exifKey.tag() << ",\t"
           << "0x" << std::setw(4) << std::setfill('0')
           << std::right << std::hex << exifKey.tag() << ",\t"
           << exifKey.groupName() << ",\t"
           << exifKey.key() << ",\t"
           << TypeInfo::typeName(exifKey.defaultTypeId()) << ",\t"
           << exifKey.tagDesc();

        os.flags(f);
        return os;
    }

}                                       // namespace Exiv2

