/*
 * Copyright 2018 Google LLC
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#ifndef SkPDFTag_DEFINED
#define SkPDFTag_DEFINED

#include "include/core/SkSpan.h"
#include "include/core/SkString.h"
#include "include/docs/SkPDFDocument.h"
#include "include/private/base/SkTArray.h"
#include "src/base/SkArenaAlloc.h"
#include "src/core/SkTHash.h"
#include "src/pdf/SkPDFTypes.h"

#include <cstddef>
#include <variant>

class SkPDFDocument;
struct SkPDFStructElem;
struct SkPoint;

class SkPDFStructTree {
public:
    SkPDFStructTree(SkPDF::StructureElementNode*, SkPDF::Metadata::Outline);
    SkPDFStructTree(const SkPDFStructTree&) = delete;
    SkPDFStructTree& operator=(const SkPDFStructTree&) = delete;
    SkPDFStructTree(SkPDFStructTree&&) = delete;
    SkPDFStructTree& operator=(SkPDFStructTree&&) = delete;
    ~SkPDFStructTree();

    class Mark {
        SkPDFStructElem* fStructElem;
        size_t fMarkIndex;
    public:
        Mark(SkPDFStructElem* structElem, size_t markIndex)
            : fStructElem(structElem), fMarkIndex(markIndex) {}
        Mark() : Mark(nullptr, 0) {}
        Mark(const Mark&) = default;
        Mark& operator=(const Mark&) = default;
        Mark(Mark&&) = default;
        Mark& operator=(Mark&&) = default;

        explicit operator bool() const { return fStructElem; }
        int mcid() const; // mcid < 0 means no active mark, if bool(this) always >= 0
        int elemId() const; // 0 elemId means no active structure element
        SkString structType() const; // only call when bool(this)
        void accumulate(SkPoint); // only call when bool(this)
    };

    // Create a new marked-content identifier (MCID) to be used with a marked-content sequence
    // parented by the structure element (StructElem) with the given element identifier (elemId).
    // The StructTreeRoot::ParentTree[?::StructParents][mcid] will refer to the structure element.
    // The structure element will add this MCID as its next child (in StructElem::K).
    // Returns a false Mark if if elemId does not refer to a StructElem.
    //
    // If a true Mark is returned and structParentsKey is false, the Mark will be added to a new
    // StructParents and structParentsKey will be updated to reference the StructParents entry.
    SkPDFStructTree::Mark createMarkForElemId(int elemId, unsigned pageIndex,
                                              SkPDFParentTreeKey& structParentsKey);

    static constexpr SkPDFIndirectReference kPageContentStreamRef = SkPDFIndirectReference{-2};
    void setContentStreamRefForStructParentsKey(SkPDFParentTreeKey structParentsKey,
                                                SkPDFIndirectReference contentStreamRef);
    SkPDFIndirectReference getContentStreamRefForStructParentsKey(
            SkPDFParentTreeKey structParentsKey) const;

    // Create a key to use with /StructParent in a content item (usually an annotation) which refers
    // to the structure element (StructElem) with the given element identifier (elemId).
    // The StructTreeRoot ParentTree will map from this key to the structure element.
    // The structure element will add the content item as its next child (as StructElem::K::OBJR).
    // Returns -1 if elemId does not refer to a StructElem.
    SkPDFParentTreeKey createStructParentKeyForElemId(int elemId, unsigned pageIndex,
                                                      SkPDFIndirectReference contentItemRef);

    void addStructElemTitle(int elemId, SkSpan<const char>);
    SkPDFIndirectReference emitStructTreeRoot(SkPDFDocument* doc) const;
    SkPDFIndirectReference makeOutline(SkPDFDocument* doc) const;
    SkString getRootLanguage();

    // An entry in an ordered map from an element identifier to an indirect reference to its
    // corresponding structure element.
    struct IDTreeEntry {
        int elemId;
        SkPDFIndirectReference structElemRef;
    };
private:
    void move(SkPDF::StructureElementNode& node, SkPDFStructElem* structElem, bool wantTitle);

    SkArenaAlloc fArena;
    skia_private::THashMap<int, SkPDFStructElem*> fStructElemForElemId;
    SkPDFStructElem* fRoot = nullptr;
    SkPDF::Metadata::Outline fOutline = SkPDF::Metadata::Outline::None;

    struct Item {
        SkPDFStructElem* fStructElem;
    };
    struct Stream {
        skia_private::TArray<SkPDFStructElem*> fChildren; // indexed by mcid
        // the non-page content stream reference, kPageContentStreamRef, or empty
        SkPDFIndirectReference fContentStreamRef;
    };
    using ParentTreeEntry = std::variant<Item, Stream>;
    // indexed by ?::StructParent or ?::StructParents
    skia_private::TArray<ParentTreeEntry> fParentTree;
};

#endif
