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

#ifndef PDF_PAGES_COLLECTION_H
#define PDF_PAGES_COLLECTION_H

#include "PdfDeclarations.h"

#include "PdfElement.h"
#include "PdfArray.h"
#include "PdfPage.h"

namespace PoDoFo {

    /** Class for managing the tree of Pages in a PDF document
     *  Don't use this class directly. Use PdfDocument instead.
     *
     *  \see PdfDocument
     */
    class PODOFO_API PdfPageCollection final : public PdfDictionaryElement
    {
        friend class PdfDocument;
        friend class PdfPage;

    public:
        /** Construct a new PdfPageTree
         */
        PdfPageCollection(PdfDocument& doc);

        /** Construct a PdfPageTree from the root /Pages object
         *  \param pagesRoot pointer to page tree dictionary
         */
        PdfPageCollection(PdfObject& pagesRoot);

        /** Close/down destruct a PdfPageTree
         */
        virtual ~PdfPageCollection();

        /** Return the number of pages in document
         *  \returns number of pages
         */
        unsigned GetCount() const;

        /** Return a PdfPage for the specified Page index
         *  The returned page is owned by the pages tree and
         *  deleted along with it.
         *
         *  \param index page index, 0-based
         *  \returns a pointer to the requested page
         */
        PdfPage& GetPageAt(unsigned index);
        const PdfPage& GetPageAt(unsigned index) const;

        /** Return a PdfPage for the specified Page reference.
         *  The returned page is owned by the pages tree and
         *  deleted along with it.
         *
         *  \param ref the reference of the pages object
         *  \returns a pointer to the requested page
         */
        PdfPage& GetPage(const PdfReference& ref);
        const PdfPage& GetPage(const PdfReference& ref) const;

        /** Creates a new page object and inserts it into the internal
         *  page tree.
         *  The returned page is owned by the pages tree and will get deleted along
         *  with it!
         *
         *  \param size a Rect specifying the size of the page (i.e the /MediaBox key) in PDF units
         *  \returns a pointer to a PdfPage object
         */
        PdfPage& CreatePage(const nullable<Rect>& size = nullptr);
        PdfPage& CreatePage(PdfPageSize pageSize);

        /** Creates a new page object and inserts it at index atIndex.
         *  The returned page is owned by the pages tree and will get deleted along
         *  with it!
         *
         *  \param size a Rect specifying the size of the page (i.e the /MediaBox key) in PDF units
         *  \param atIndex index where to insert the new page (0-based)
         *  \returns a pointer to a PdfPage object
         */
        PdfPage& CreatePageAt(unsigned atIndex, const nullable<Rect>& size = nullptr);
        PdfPage& CreatePageAt(unsigned atIndex, PdfPageSize pageSize);

        /** Create count new page objects and insert at the index atIndex. This is significantly faster
         *  than calling CreatePageAt repeatedly.
         *
         *  \param size a Rect specifying the size of the page (i.e the /MediaBox key) in PDF units
         *  \param count number of pages to create
         *  \param atIndex index where to insert the new page (0-based)
         */
        void CreatePagesAt(unsigned atIndex, unsigned count, const nullable<Rect>& size = nullptr);
        void CreatePagesAt(unsigned atIndex, unsigned count, PdfPageSize pageSize);

        /** Appends another PdfDocument to this document.
         *  \param doc the document to append
         */
        void AppendDocumentPages(const PdfDocument& doc);

        /** Copies one or more pages from another PdfMemDocument to this document
         *  \param doc the document to append
         *  \param atIndex the first page number to copy (0-based)
         *  \param pageCount the number of pages to copy
         */
        void AppendDocumentPages(const PdfDocument& doc, unsigned pageIndex, unsigned pageCount);

        /** Inserts existing page from another PdfDocument to this document.
         *  \param atIndex index at which to add the page in this document
         *  \param doc the document to append from
         *  \param pageIndex index of page to append from doc
         */
        void InsertDocumentPageAt(unsigned atIndex, const PdfDocument& doc, unsigned pageIndex);

        /**  Delete the specified page object from the internal pages tree.
         *   It does NOT remove any PdfObjects from memory - just the reference from the tree
         *
         *   \param atIndex the page number (0-based) to be removed
         *
         *   The PdfPage object referring to this page will be deleted by this call!
         *   Empty page nodes will also be deleted.
         *
         *   \see PdfMemDocument::DeletePages
         */
        void RemovePageAt(unsigned atIndex);

        /**  Flatten the document page structure tree
         *
         * This copy pages inheritable attributes and remove intermediate /Pages nodes.
         * This operation is allowed by the PDF specification, see "ISO 32000-2:2020, 7.7.3.2 Page tree nodes"
         */
        void FlattenStructure();

    public:
        template <typename TObject, typename TListIterator>
        class Iterator final
        {
            friend class PdfPageCollection;
        public:
            using difference_type = void;
            using value_type = TObject*;
            using pointer = void;
            using reference = void;
            using iterator_category = std::forward_iterator_tag;
        public:
            Iterator() {}
        private:
            Iterator(const TListIterator& iterator) : m_iterator(iterator) {}
        public:
            Iterator(const Iterator&) = default;
            Iterator& operator=(const Iterator&) = default;
            bool operator==(const Iterator& rhs) const
            {
                return m_iterator == rhs.m_iterator;
            }
            bool operator!=(const Iterator& rhs) const
            {
                return m_iterator != rhs.m_iterator;
            }
            Iterator& operator++()
            {
                m_iterator++;
                return *this;
            }
            Iterator operator++(int)
            {
                auto copy = *this;
                m_iterator++;
                return copy;
            }
            value_type operator*()
            {
                return *m_iterator;
            }
            value_type operator->()
            {
                return *m_iterator;
            }
        private:
            TListIterator m_iterator;
        };

        using PageList = std::vector<PdfPage*>;

        using iterator = Iterator<PdfPage, PageList::iterator>;
        using const_iterator = Iterator<const PdfPage, PageList::const_iterator>;

    public:
        iterator begin();
        iterator end();
        const_iterator begin() const;
        const_iterator end() const;

    private:
        /**
         * Insert page at the given index
         * \remarks Can be used by PdfDocument
         */
        void InsertPageAt(unsigned atIndex, PdfPage& page);

        /**
         * Insert pages at the given index
         * \remarks Can be used by PdfDocument
         */
        void InsertPagesAt(unsigned atIndex, cspan<PdfPage*> pages);

        bool TryMovePageTo(unsigned atIndex, unsigned toIndex);

    private:
        void insertPageAt(unsigned atIndex, PdfPage& page);
        void insertPagesAt(unsigned atIndex, cspan<PdfPage*> pages);
        Rect getActualRect(const nullable<Rect>& size);

        PdfPage& getPage(const PdfReference& ref) const;

        void initPages();

        unsigned traversePageTreeNode(PdfObject& obj, unsigned count,
            std::vector<PdfObject*>& parents, std::unordered_set<PdfObject*>& visitedNodes);

        PdfPageCollection(PdfPageCollection&) = delete;
        PdfPageCollection& operator=(PdfPageCollection&) = delete;

    private:
        bool m_initialized;
        PageList m_Pages;
        PdfArray* m_kidsArray;
    };

};

#endif // PDF_PAGES_COLLECTION_H
