/*

  EXAMPLE osmium_dump_internal

  Reads an OSM file and dumps the internal datastructure to disk including
  indexes to find objects and object relations.

  Note that this example programm will only work with small and medium sized
  OSM files, not with the planet.

  You can use the osmium_index example program to inspect the indexes.

  DEMONSTRATES USE OF:
  * file input
  * indexes and maps
  * use of the DiskStore handler
  * use of the ObjectRelations handler

  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 <cerrno>      // for errno
#include <cstring>     // for std::strerror
#include <exception>
#include <iostream>    // for std::cout, std::cerr
#include <string>      // for std::string
#include <sys/stat.h>  // for open
#include <sys/types.h> // for open
#include <system_error>

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

#ifdef _MSC_VER
# include <direct.h>
#endif

// Allow any format of input files (XML, PBF, ...)
#include <osmium/io/any_input.hpp>

// The DiskStore handler
#include <osmium/handler/disk_store.hpp>

// The ObjectRelations handler
#include <osmium/handler/object_relations.hpp>

// The indexes
#include <osmium/index/map/sparse_mem_array.hpp>
#include <osmium/index/multimap/sparse_mem_array.hpp>

using offset_index_type = osmium::index::map::SparseMemArray<osmium::unsigned_object_id_type, size_t>;
using map_type = osmium::index::multimap::SparseMemArray<osmium::unsigned_object_id_type, osmium::unsigned_object_id_type>;

/**
 * Small class wrapping index files, basically making sure errors are handled
 * and the files are closed on destruction.
 */
class IndexFile {

    int m_fd;

public:

    explicit IndexFile(const std::string& filename) :
        m_fd(::open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666)) { // NOLINT(hicpp-signed-bitwise)
        if (m_fd < 0) {
            throw std::system_error{errno, std::system_category(), "Can't open index file '" + filename};
        }
#ifdef _WIN32
        _setmode(m_fd, _O_BINARY);
#endif
    }

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

    IndexFile(IndexFile&&) = delete;
    IndexFile& operator=(IndexFile&&) = delete;

    ~IndexFile() {
        if (m_fd >= 0) {
            close(m_fd);
        }
    }

    int fd() const noexcept {
        return m_fd;
    }

}; // class IndexFile

int main(int argc, char* argv[]) {
    if (argc != 3) {
        std::cerr << "Usage: " << argv[0] << " OSMFILE DIR\n";
        return 2;
    }

    try {
        const std::string input_file_name{argv[1]};
        const std::string output_dir{argv[2]};

        // Create output directory. Ignore the error if it already exists.
#ifndef _WIN32
        const int result = ::mkdir(output_dir.c_str(), 0777);
#else
        const int result = mkdir(output_dir.c_str());
#endif
        if (result == -1 && errno != EEXIST) {
            std::cerr << "Problem creating directory '" << output_dir << "': " << std::strerror(errno) << "\n";
            return 2;
        }

        // Create the output file which will contain our serialized OSM data
        const std::string data_file{output_dir + "/data.osm.ser"};
        const int data_fd = ::open(data_file.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666); // NOLINT(hicpp-signed-bitwise)
        if (data_fd < 0) {
            std::cerr << "Can't open data file '" << data_file << "': " << std::strerror(errno) << "\n";
            return 2;
        }

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

        // These indexes store the offset in the data file where each node, way,
        // or relation is stored.
        offset_index_type node_index;
        offset_index_type way_index;
        offset_index_type relation_index;

        // This handler will dump the internal data to disk using the given file
        // descriptor while updating the indexes.
        osmium::handler::DiskStore disk_store_handler{data_fd, node_index, way_index, relation_index};

        // These indexes store the mapping from node id to the ids of the ways
        // containing this node, and from node/way/relation ids to the ids of the
        // relations containing those objects.
        map_type map_node2way;
        map_type map_node2relation;
        map_type map_way2relation;
        map_type map_relation2relation;

        // This handler will update the map indexes.
        osmium::handler::ObjectRelations object_relations_handler{map_node2way, map_node2relation, map_way2relation, map_relation2relation};

        // Read OSM data buffer by buffer.
        osmium::io::Reader reader{input_file_name};

        while (osmium::memory::Buffer buffer = reader.read()) {
            // Write buffer to disk and update indexes.
            disk_store_handler(buffer);

            // Update object relation index maps.
            osmium::apply(buffer, object_relations_handler);
        }

        reader.close();

        // Write out node, way, and relation offset indexes to disk.
        const IndexFile nodes_idx{output_dir + "/nodes.idx"};
        node_index.dump_as_list(nodes_idx.fd());

        const IndexFile ways_idx{output_dir + "/ways.idx"};
        way_index.dump_as_list(ways_idx.fd());

        const IndexFile relations_idx{output_dir + "/relations.idx"};
        relation_index.dump_as_list(relations_idx.fd());

        // Sort the maps (so later binary search will work on them) and write
        // them to disk.
        map_node2way.sort();
        const IndexFile node2way_idx{output_dir + "/node2way.map"};
        map_node2way.dump_as_list(node2way_idx.fd());

        map_node2relation.sort();
        const IndexFile node2relation_idx{output_dir + "/node2rel.map"};
        map_node2relation.dump_as_list(node2relation_idx.fd());

        map_way2relation.sort();
        const IndexFile way2relation_idx{output_dir + "/way2rel.map"};
        map_way2relation.dump_as_list(way2relation_idx.fd());

        map_relation2relation.sort();
        const IndexFile relation2relation_idx{output_dir + "/rel2rel.map"};
        map_relation2relation.dump_as_list(relation2relation_idx.fd());
    } catch (const std::exception& e) {
        // All exceptions used by the Osmium library derive from std::exception.
        std::cerr << e.what() << '\n';
        return 1;
    }
}

