// SPDX-FileCopyrightText: 2008 Dominik Seichter <domseichter@web.de>
// SPDX-FileCopyrightText: 2020 Francesco Pretto <ceztko@gmail.com>
// SPDX-License-Identifier: LGPL-2.0-or-later OR MPL-2.0

#ifndef PDF_DIFFERENCE_ENCODING_H
#define PDF_DIFFERENCE_ENCODING_H

#include "PdfEncodingMap.h"
#include "PdfArray.h"

namespace PoDoFo {

class PdfFontMetrics;
struct CodePointMapNode;

struct PODOFO_API PdfDifferenceMapping final
{
    PdfName Name;
    unsigned char Code = 0;
    CodePointSpan CodePoints;
};

/// A helper class for PdfDifferenceEncoding that
/// can be used to create a differences array.
class PODOFO_API PdfDifferenceMap final
{
    friend class PdfDifferenceEncoding;
public:
    using const_iterator = std::vector<PdfDifferenceMapping>::const_iterator;

public:
    /// Create a PdfEncodingDifference object.
    PdfDifferenceMap();

    PdfDifferenceMap(const PdfDifferenceMap& rhs) = default;
    PdfDifferenceMap& operator=(const PdfDifferenceMap& rhs) = default;

    /// Add a difference to the encoding
    ///
    /// The added name is determined by the "Adobe Glyph List for New Fonts"
    /// https://github.com/adobe-type-tools/agl-aglfn/blob/master/aglfn.txt
    /// @param code code unit of the difference (0 to 255 are legal values)
    /// @param codePoint actual unicode code point
    ///
    void AddDifference(unsigned char code, char32_t codePoint);

    /// Add a difference to the encoding
    ///
    /// The added name is determined by the "Adobe Glyph List for New Fonts"
    /// https://github.com/adobe-type-tools/agl-aglfn/blob/master/aglfn.txt
    /// @param code code unit of the difference (0 to 255 are legal values)
    /// @param codePoints a span of unicode code points
    ///
    void AddDifference(unsigned char code, const codepointview& codePoints);

    /// Get the mapped code point from a char code
    ///
    /// @param code test if the given code is part of the differences
    /// @param code write the associated unicode values of the name to this value
    ///
    /// @returns true if the code is part of the difference
    bool TryGetMappedName(unsigned char code, const PdfName*& name) const;
    bool TryGetMappedName(unsigned char code, const PdfName*& name, CodePointSpan& codePoints) const;

    /// Convert the PdfEncodingDifference to an array
    ///
    /// @param arr write to this array
    void ToArray(PdfArray& arr) const;

    /// Get the number of differences in this object.
    /// If the user added .notdef as a difference it is
    /// counted, even it is no real difference in the final encoding.
    ///
    /// @returns the number of differences in this object
    unsigned GetCount() const;

    const_iterator begin() const { return m_differences.begin(); }

    const_iterator end() const { return m_differences.end(); }

private:
    void AddDifference(unsigned char code, const std::string_view& name);

    void addDifference(unsigned char code, const codepointview& codepoints, const PdfName& name);

    struct DifferenceComparatorPredicate
    {
    public:
        bool operator()(const PdfDifferenceMapping& diff1, const PdfDifferenceMapping& diff2) const
        {
            return diff1.Code < diff2.Code;
        }
    };

    using DifferenceList = std::vector<PdfDifferenceMapping>;

private:
    DifferenceList m_differences;
};

/// PdfDifferenceEncoding is an encoding, which is based
/// on either the fonts encoding or a predefined encoding
/// and defines differences to this base encoding.
class PODOFO_API PdfDifferenceEncoding final : public PdfEncodingMapSimple
{
    friend class PdfDifferenceMap;

public:
    /// Create a new PdfDifferenceEncoding which is based on
    /// a predefined encoding.
    ///
    /// @param differences the differences in this encoding
    /// @param baseEncoding the base encoding of this font
    PdfDifferenceEncoding(PdfEncodingMapConstPtr baseEncoding,
        PdfDifferenceMap differences);

    ~PdfDifferenceEncoding();

public:
    /// Create a new PdfDifferenceEncoding from an existing object
    ///
    /// @param obj object for the difference encoding
    /// @param metrics an existing font metrics
    static bool TryCreateFromObject(const PdfObject& obj, const PdfFontMetrics& metrics,
        std::unique_ptr<PdfDifferenceEncoding>& encoding);

    /// Create a new PdfDifferenceEncoding from an existing object
    ///
    /// @param obj object for the difference encoding
    /// @param metrics an existing font metrics
    /// @returns On success, returns a non null PdfDifferenceEncoding
    /// @remarks throws on failure
    static std::unique_ptr<PdfDifferenceEncoding> CreateFromObject(const PdfObject& obj, const PdfFontMetrics& metrics);

    /// Try to convert a standard character name to a unicode code points
    ///
    /// @param name a standard character name.
    ///   See https://github.com/adobe-type-tools/agl-aglfn/ for known names
    /// @param codepoints the returned unicode code points span
    static bool TryGetCodePointsFromCharName(const std::string_view& name, CodePointSpan& codepoints);

    /// Get read-only access to the object containing the actual
    /// differences.
    ///
    /// @returns the container with the actual differences
    const PdfDifferenceMap& GetDifferences() const { return m_differences; }

protected:
    void GetBaseEncoding(const PdfEncodingMap*& baseEncoding, const PdfDifferenceMap*& differences) const override;

    void getExportObject(PdfIndirectObjectList& objects, PdfName& name, PdfObject*& obj) const override;
    bool tryGetCharCode(char32_t codePoint, PdfCharCode& codeUnit) const override;
    bool tryGetCharCodeSpan(const unicodeview& codePoints, PdfCharCode& codeUnit) const override;
    bool tryGetNextCharCode(std::string_view::iterator& it,
        const std::string_view::iterator& end, PdfCharCode& codeUnit) const override;
    bool tryGetCodePoints(const PdfCharCode& codeUnit, const unsigned* cidId, CodePointSpan& codePoints) const override;

private:
    static bool TryGetCodePointsFromCharName(std::string_view charName, CodePointSpan& codepoints, const PdfName*& actualName);

    void buildReverseMap();

private:
    PdfEncodingMapConstPtr m_baseEncoding;
    PdfDifferenceMap m_differences;
    CodePointMapNode* m_reverseMap;
};

};

#endif // PDF_DIFFERENCE_ENCODING_H
