#include <qpdf/QPDF.hh>
#include <qpdf/QPDFNameTreeObjectHelper.hh>
#include <qpdf/QPDFNumberTreeObjectHelper.hh>
#include <qpdf/QPDFWriter.hh>
#include <qpdf/QUtil.hh>
#include <iostream>

static char const* whoami = nullptr;

void
usage()
{
    std::cerr << "Usage: " << whoami << " outfile.pdf" << '\n'
              << "Create some name/number trees and write to a file\n";
    exit(2);
}

int
main(int argc, char* argv[])
{
    whoami = QUtil::getWhoami(argv[0]);

    if (argc != 2) {
        usage();
    }

    char const* outfilename = argv[1];

    QPDF qpdf;
    qpdf.emptyPDF();

    // This example doesn't do anything particularly useful other than just illustrate how to use
    // the APIs for name and number trees. It also demonstrates use of the iterators for
    // dictionaries and arrays introduced at the same time with qpdf 10.2.

    // To use this example, compile it and run it. Study the output and compare it to what you
    // expect. When done, look at the generated output file in a text editor to inspect the
    // structure of the trees as left in the file.

    // We're just going to create some name and number trees, hang them off the document catalog
    // (root), and write an empty PDF to a file. The PDF will have no pages and won't be viewable,
    // but you can look at it in a text editor to see the resulting structure of the PDF.

    // Create a dictionary off the root where we will hang our name and number trees.
    auto root = qpdf.getRoot();
    auto example = QPDFObjectHandle::newDictionary();
    root.replaceKey("/Example", example);

    // Create a name tree, attach it to the file, and add some items.
    auto name_tree = QPDFNameTreeObjectHelper::newEmpty(qpdf);
    auto name_tree_oh = name_tree.getObjectHandle();
    example.replaceKey("/NameTree", name_tree_oh);
    name_tree.insert("K", QPDFObjectHandle::newUnicodeString("king"));
    name_tree.insert("Q", QPDFObjectHandle::newUnicodeString("queen"));
    name_tree.insert("R", QPDFObjectHandle::newUnicodeString("rook"));
    name_tree.insert("B", QPDFObjectHandle::newUnicodeString("bishop"));
    name_tree.insert("N", QPDFObjectHandle::newUnicodeString("knight"));
    auto iter = name_tree.insert("P", QPDFObjectHandle::newUnicodeString("pawn"));
    // Look at the iterator
    std::cout << "just inserted " << iter->first << " -> " << iter->second.unparse() << '\n';
    --iter;
    std::cout << "predecessor: " << iter->first << " -> " << iter->second.unparse() << '\n';
    ++iter;
    ++iter;
    std::cout << "successor: " << iter->first << " -> " << iter->second.unparse() << '\n';

    // Use range-for iteration
    std::cout << "Name tree items:\n";
    for (auto i: name_tree) {
        std::cout << "  " << i.first << " -> " << i.second.unparse() << '\n';
    }

    // This is a small tree, so everything will be at the root. We can look at it using dictionary
    // and array iterators.
    std::cout << "Keys in name tree object:\n";
    QPDFObjectHandle names;
    for (auto const& i: name_tree_oh.ditems()) {
        std::cout << i.first << '\n';
        if (i.first == "/Names") {
            names = i.second;
        }
    }
    // Values in names array:
    std::cout << "Values in names:\n";
    for (auto& i: names.aitems()) {
        std::cout << "  " << i.unparse() << '\n';
    }

    // pre 10.2 API
    std::cout << "Has Q?: " << name_tree.hasName("Q") << '\n';
    std::cout << "Has W?: " << name_tree.hasName("W") << '\n';
    QPDFObjectHandle obj;
    std::cout << "Found W?: " << name_tree.findObject("W", obj) << '\n';
    std::cout << "Found Q?: " << name_tree.findObject("Q", obj) << '\n';
    std::cout << "Q: " << obj.unparse() << '\n';

    // 10.2 API
    iter = name_tree.find("Q");
    std::cout << "Q: " << iter->first << " -> " << iter->second.unparse() << '\n';
    iter = name_tree.find("W");
    std::cout << "W found: " << (iter != name_tree.end()) << '\n';
    // Allow find to return predecessor
    iter = name_tree.find("W", true);
    std::cout << "W's predecessor: " << iter->first << " -> " << iter->second.unparse() << '\n';

    // We can also remove items
    std::cout << "Remove P: " << name_tree.remove("P", &obj) << '\n';
    std::cout << "Value removed: " << obj.unparse() << '\n';
    std::cout << "Has P?: " << name_tree.hasName("P") << '\n';
    // Or we can remove using an iterator
    iter = name_tree.find("K");
    std::cout << "Find K: " << iter->second.unparse() << '\n';
    iter.remove();
    std::cout << "Iter after removing K: " << iter->first << " -> " << iter->second.unparse()
              << '\n';
    std::cout << "Has K?: " << name_tree.hasName("K") << '\n';

    // Illustrate some more advanced usage using number trees. These calls work for name trees too.

    // The safe way to populate a tree is to call insert repeatedly as above, but if you know you
    // are definitely inserting items in order, it is more efficient to insert them using
    // insertAfter, which avoids doing a binary search through the tree for each insertion. Note
    // that if you don't insert items in order using this method, you will create an invalid tree.
    auto number_tree = QPDFNumberTreeObjectHelper::newEmpty(qpdf);
    auto number_tree_oh = number_tree.getObjectHandle();
    example.replaceKey("/NumberTree", number_tree_oh);
    auto iter2 = number_tree.begin();
    for (int i = 7; i <= 350; i += 7) {
        iter2.insertAfter(i, QPDFObjectHandle::newString("-" + std::to_string(i) + "-"));
    }
    std::cout << "Numbers:\n";
    int n = 1;
    for (auto& i: number_tree) {
        std::cout << i.first << " -> " << i.second.getUTF8Value();
        if (n % 5) {
            std::cout << ", ";
        } else {
            std::cout << '\n';
        }
        ++n;
    }

    // When you remove an item with an iterator, the iterator advances. This makes it possible to
    // filter while iterating. Remove all items that are multiples of 5.
    iter2 = number_tree.begin();
    while (iter2 != number_tree.end()) {
        if (iter2->first % 5 == 0) {
            iter2.remove(); // also advances
        } else {
            ++iter2;
        }
    }
    std::cout << "Numbers after filtering:\n";
    n = 1;
    for (auto& i: number_tree) {
        std::cout << i.first << " -> " << i.second.getUTF8Value();
        if (n % 5) {
            std::cout << ", ";
        } else {
            std::cout << '\n';
        }
        ++n;
    }

    // Write to an output file
    QPDFWriter w(qpdf, outfilename);
    w.setQDFMode(true);
    w.setStaticID(true); // for testing only
    w.write();

    return 0;
}
