/*

  EXAMPLE osmium_index

  Example program to look at Osmium indexes on disk.

  You can use the osmium_dump_internal example program to create the offset
  indexes or osmium_location_cache_create to create a node location index.

  DEMONSTRATES USE OF:
  * access to indexes on disk

  SIMPLER EXAMPLES you might want to understand first:
  * osmium_read
  * osmium_count
  * osmium_road_length
  * osmium_location_cache_create
  * osmium_location_cache_use

  LICENSE
  The code in this example file is released into the Public Domain.

*/

#include <algorithm>   // for std::all_of, std::equal_range
#include <cstdlib>     // for std::exit
#include <cstring>     // for std::strcmp
#include <exception>
#include <fcntl.h>     // for open
#include <iostream>    // for std::cout, std::cerr
#include <memory>      // for std::unique_ptr
#include <string>      // for std::string
#include <vector>      // for std::vector

#ifdef _WIN32
# include <io.h>       // for _setmode
#endif

// Disk-based indexes
#include <osmium/index/map/dense_file_array.hpp>
#include <osmium/index/map/sparse_file_array.hpp>

// osmium::Location
#include <osmium/osm/location.hpp>

// Basic Osmium types
#include <osmium/osm/types.hpp>

// Virtual class for disk index access. If offers functions to dump the
// indexes and to search for ids in the index.
template <typename TValue>
class IndexAccess {

    int m_fd;

public:

    explicit IndexAccess(int fd) :
        m_fd(fd) {
    }

    int fd() const noexcept {
        return m_fd;
    }

    IndexAccess(const IndexAccess&) = delete;
    IndexAccess& operator=(const IndexAccess&) = delete;

    IndexAccess(IndexAccess&&) noexcept = delete;
    IndexAccess& operator=(IndexAccess&&) noexcept = delete;

    virtual ~IndexAccess() noexcept = default;

    virtual void dump() const = 0;

    virtual bool search(const osmium::unsigned_object_id_type& key) const = 0;

    bool search(const std::vector<osmium::unsigned_object_id_type>& keys) const {
        return std::all_of(keys.cbegin(), keys.cend(), [this](const osmium::unsigned_object_id_type& key) {
            return search(key);
        });
    }

}; // class IndexAccess

// Implementation of IndexAccess for dense indexes usually used for very large
// extracts or the planet.
template <typename TValue>
class IndexAccessDense : public IndexAccess<TValue> {

    using index_type = typename osmium::index::map::DenseFileArray<osmium::unsigned_object_id_type, TValue>;

public:

    explicit IndexAccessDense(int fd) :
        IndexAccess<TValue>(fd) {
    }

    IndexAccessDense(const IndexAccessDense&) = default;
    IndexAccessDense& operator=(const IndexAccessDense&) = default;

    IndexAccessDense(IndexAccessDense&&) noexcept = default;
    IndexAccessDense& operator=(IndexAccessDense&&) noexcept = default;

    ~IndexAccessDense() noexcept override = default;

    void dump() const override {
        const index_type index{this->fd()};

        for (std::size_t i = 0; i < index.size(); ++i) {
            if (index.get(i) != TValue{}) {
                std::cout << i << " " << index.get(i) << "\n";
            }
        }
    }

    bool search(const osmium::unsigned_object_id_type& key) const override {
        const index_type index{this->fd()};

        try {
            TValue value = index.get(key);
            std::cout << key << " " << value << "\n";
        } catch (...) {
            std::cout << key << " not found\n";
            return false;
        }

        return true;
    }

}; // class IndexAccessDense

// Implementation of IndexAccess for sparse indexes usually used for small or
// medium sized extracts or for "multimap" type indexes.
template <typename TValue>
class IndexAccessSparse : public IndexAccess<TValue> {

    using index_type = typename osmium::index::map::SparseFileArray<osmium::unsigned_object_id_type, TValue>;

public:

    explicit IndexAccessSparse(int fd) :
        IndexAccess<TValue>(fd) {
    }

    IndexAccessSparse(const IndexAccessSparse&) = default;
    IndexAccessSparse& operator=(const IndexAccessSparse&) = default;

    IndexAccessSparse(IndexAccessSparse&&) noexcept = default;
    IndexAccessSparse& operator=(IndexAccessSparse&&) noexcept = default;

    ~IndexAccessSparse() noexcept override = default;

    void dump() const override {
        const index_type index{this->fd()};

        for (const auto& element : index) {
            std::cout << element.first << " " << element.second << "\n";
        }
    }

    bool search(const osmium::unsigned_object_id_type& key) const override {
        using element_type = typename index_type::element_type;
        index_type index{this->fd()};

        const element_type elem{key, TValue{}};
        const auto positions = std::equal_range(index.begin(),
                                                index.end(),
                                                elem,
                                                [](const element_type& lhs,
                                                   const element_type& rhs) {
            return lhs.first < rhs.first;
        });
        if (positions.first == positions.second) {
            std::cout << key << " not found\n";
            return false;
        }

        for (auto it = positions.first; it != positions.second; ++it) {
            std::cout << it->first << " " << it->second << "\n";
        }

        return true;
    }

}; // class IndexAccessSparse

// This class contains the code to parse the command line arguments, check
// them and present the results to the rest of the program in an easy-to-use
// way.
class Options {

    std::vector<osmium::unsigned_object_id_type> m_ids;
    std::string m_type;
    std::string m_filename;
    bool m_dump = false;
    bool m_array_format = false;
    bool m_list_format = false;

    static void print_help() {
        std::cout << "Usage: osmium_index_lookup [OPTIONS]\n\n"
                  << "-h, --help        Print this help message\n"
                  << "-a, --array=FILE  Read given index file in array format\n"
                  << "-l, --list=FILE   Read given index file in list format\n"
                  << "-d, --dump        Dump contents of index file to STDOUT\n"
                  << "-s, --search=ID   Search for given id (Option can appear multiple times)\n"
                  << "-t, --type=TYPE   Type of value ('location', 'id', or 'offset')\n"
        ;
    }

    static void print_usage(const char* prgname) {
        std::cout << "Usage: " << prgname << " [OPTIONS]\n\n";
        std::exit(0);
    }

public:

    Options(int argc, char* argv[]) {
        if (argc == 1) {
            print_usage(argv[0]);
        }

        if (argc > 1 && (!std::strcmp(argv[1], "-h") ||
                         !std::strcmp(argv[1], "--help"))) {
            print_help();
            std::exit(0);
        }

        for (int i = 1; i < argc; ++i) {
            if (!std::strcmp(argv[i], "-a") || !std::strcmp(argv[i], "--array")) {
                ++i;
                if (i < argc) {
                    m_array_format = true;
                    m_filename = argv[i];
                } else {
                    print_usage(argv[0]);
                }
            } else if (!std::strncmp(argv[i], "--array=", 8)) {
                m_array_format = true;
                m_filename = argv[i] + 8;
            } else if (!std::strcmp(argv[i], "-l") || !std::strcmp(argv[i], "--list")) {
                ++i;
                if (i < argc) {
                    m_list_format = true;
                    m_filename = argv[i];
                } else {
                    print_usage(argv[0]);
                }
            } else if (!std::strncmp(argv[i], "--list=", 7)) {
                m_list_format = true;
                m_filename = argv[i] + 7;
            } else if (!std::strcmp(argv[i], "-d") || !std::strcmp(argv[i], "--dump")) {
                m_dump = true;
            } else if (!std::strcmp(argv[i], "-s") || !std::strcmp(argv[i], "--search")) {
                ++i;
                if (i < argc) {
                    m_ids.push_back(std::atoll(argv[i])); // NOLINT(cert-err34-c)
                } else {
                    print_usage(argv[0]);
                }
            } else if (!std::strncmp(argv[i], "--search=", 9)) {
                m_ids.push_back(std::atoll(argv[i] + 9)); // NOLINT(cert-err34-c)
            } else if (!std::strcmp(argv[i], "-t") || !std::strcmp(argv[i], "--type")) {
                ++i;
                if (i < argc) {
                    m_type = argv[i];
                } else {
                    print_usage(argv[0]);
                }
            } else if (!std::strncmp(argv[i], "--type=", 7)) {
                m_type = argv[i] + 7;
            } else {
                std::cerr << "Unknown command line options or arguments\n";
                print_usage(argv[0]);
            }
        }

        if (m_array_format == m_list_format) {
            std::cerr << "Need option --array or --list, but not both\n";
            std::exit(2);
        }

        if (m_dump == !m_ids.empty()) {
            std::cerr << "Need option --dump or --search, but not both\n";
            std::exit(2);
        }

        if (m_type.empty()) {
            std::cerr << "Need --type argument.\n";
            std::exit(2);
        }

        if (m_type != "location" && m_type != "id" && m_type != "offset") {
            std::cerr << "Unknown type '" << m_type
                      << "'. Must be 'location', 'id', or 'offset'.\n";
            std::exit(2);
        }
    }

    const char* filename() const noexcept {
        return m_filename.c_str();
    }

    bool dense_format() const noexcept {
        return m_array_format;
    }

    bool do_dump() const noexcept {
        return m_dump;
    }

    const std::vector<osmium::unsigned_object_id_type>& search_keys() const noexcept {
        return m_ids;
    }

    bool type_is(const char* type) const noexcept {
        return m_type == type;
    }

}; // class Options

namespace {

// Factory function to create the right IndexAccess-derived class.
template <typename TValue>
std::unique_ptr<IndexAccess<TValue>> create(bool dense, int fd) {
    std::unique_ptr<IndexAccess<TValue>> ptr;

    if (dense) {
        ptr.reset(new IndexAccessDense<TValue>{fd});
    } else {
        ptr.reset(new IndexAccessSparse<TValue>{fd});
    }

    return ptr;
}

// Do the actual work: Either dump the index or search in the index.
template <typename TValue>
int run(const IndexAccess<TValue>& index, const Options& options) {
    if (options.do_dump()) {
        index.dump();
        return 0;
    }
    return index.search(options.search_keys()) ? 0 : 1;
}

} // anonymous namespace

int main(int argc, char* argv[]) {
    // Parse command line options.
    const Options options{argc, argv};

    // Open the index file.
    const int fd = ::open(options.filename(), O_RDWR);
    if (fd < 0) {
        std::cerr << "Can not open file '" << options.filename()
                  << "': " << std::strerror(errno) << '\n';
        return 2;
    }

#ifdef _WIN32
    _setmode(fd, _O_BINARY);
#endif

    try {
        // Depending on the type of index, we have different implementations.
        if (options.type_is("location")) {
            // index id -> location
            const auto index = create<osmium::Location>(options.dense_format(), fd);
            return run(*index, options);
        }

        if (options.type_is("id")) {
            // index id -> id
            const auto index = create<osmium::unsigned_object_id_type>(options.dense_format(), fd);
            return run(*index, options);
        }

        // index id -> offset
        const auto index = create<std::size_t>(options.dense_format(), fd);
        return run(*index, options);
    } catch (const std::exception& e) {
        // All exceptions used by the Osmium library derive from std::exception.
        std::cerr << e.what() << '\n';
        return 1;
    }
}

