#include "bdsg/overlays/packed_path_position_overlay.hpp"
#include "bdsg/internal/utility.hpp"

namespace bdsg {

PackedPositionOverlay::PackedPositionOverlay(const PathHandleGraph* graph) : graph(graph), steps_0(1024), steps_1(256), positions(256) {
    index_path_positions();
}

PackedPositionOverlay::PackedPositionOverlay() : steps_0(1), steps_1(1), positions(1) {
    
}

PackedPositionOverlay::~PackedPositionOverlay() {
    delete step_hash;
}

bool PackedPositionOverlay::has_node(nid_t node_id) const {
    return graph->has_node(node_id);
}

handle_t PackedPositionOverlay::get_handle(const nid_t& node_id, bool is_reverse) const {
    return graph->get_handle(node_id, is_reverse);
}

nid_t PackedPositionOverlay::get_id(const handle_t& handle) const {
    return graph->get_id(handle);
}

bool PackedPositionOverlay::get_is_reverse(const handle_t& handle) const {
    return graph->get_is_reverse(handle);
}

handle_t PackedPositionOverlay::flip(const handle_t& handle) const {
    return graph->flip(handle);
}

size_t PackedPositionOverlay::get_length(const handle_t& handle) const {
    return graph->get_length(handle);
}

string PackedPositionOverlay::get_sequence(const handle_t& handle) const {
    return graph->get_sequence(handle);
}

bool PackedPositionOverlay::follow_edges_impl(const handle_t& handle, bool go_left, const std::function<bool(const handle_t&)>& iteratee) const {
    return graph->follow_edges(handle, go_left, iteratee);
}

bool PackedPositionOverlay::for_each_handle_impl(const std::function<bool(const handle_t&)>& iteratee, bool parallel) const {
    return graph->for_each_handle(iteratee, parallel);
}

size_t PackedPositionOverlay::get_degree(const handle_t& handle, bool go_left) const {
    return graph->get_degree(handle, go_left);
}

bool PackedPositionOverlay::has_edge(const handle_t& left, const handle_t& right) const {
    return graph->has_edge(left, right);
}

char PackedPositionOverlay::get_base(const handle_t& handle, size_t index) const {
    return graph->get_base(handle, index);
}

std::string PackedPositionOverlay::get_subsequence(const handle_t& handle, size_t index, size_t size) const {
    return graph->get_subsequence(handle, index, size);
}

size_t PackedPositionOverlay::get_node_count(void) const {
    return graph->get_node_count();
}

nid_t PackedPositionOverlay::min_node_id(void) const {
    return graph->min_node_id();
}

nid_t PackedPositionOverlay::max_node_id(void) const {
    return graph->max_node_id();
}

size_t PackedPositionOverlay::get_path_count() const {
    return graph->get_path_count();
}

bool PackedPositionOverlay::has_path(const std::string& path_name) const {
    return graph->has_path(path_name);
}

path_handle_t PackedPositionOverlay::get_path_handle(const std::string& path_name) const {
    return graph->get_path_handle(path_name);
}

string PackedPositionOverlay::get_path_name(const path_handle_t& path_handle) const {
    return graph->get_path_name(path_handle);
}

bool PackedPositionOverlay::get_is_circular(const path_handle_t& path_handle) const {
    return graph->get_is_circular(path_handle);
}

size_t PackedPositionOverlay::get_step_count(const path_handle_t& path_handle) const {
    return graph->get_step_count(path_handle);
}

handle_t PackedPositionOverlay::get_handle_of_step(const step_handle_t& step_handle) const {
    return graph->get_handle_of_step(step_handle);
}

step_handle_t PackedPositionOverlay::path_begin(const path_handle_t& path_handle) const {
    return graph->path_begin(path_handle);
}

step_handle_t PackedPositionOverlay::path_end(const path_handle_t& path_handle) const {
    return graph->path_end(path_handle);
}

step_handle_t PackedPositionOverlay::path_back(const path_handle_t& path_handle) const {
    return graph->path_back(path_handle);
}

step_handle_t PackedPositionOverlay::path_front_end(const path_handle_t& path_handle) const {
    return graph->path_front_end(path_handle);
}

bool PackedPositionOverlay::has_next_step(const step_handle_t& step_handle) const {
    return graph->has_next_step(step_handle);
}

bool PackedPositionOverlay::has_previous_step(const step_handle_t& step_handle) const {
    return graph->has_previous_step(step_handle);
}

step_handle_t PackedPositionOverlay::get_next_step(const step_handle_t& step_handle) const {
    return graph->get_next_step(step_handle);
}

step_handle_t PackedPositionOverlay::get_previous_step(const step_handle_t& step_handle) const {
    return graph->get_previous_step(step_handle);
}

path_handle_t PackedPositionOverlay::get_path_handle_of_step(const step_handle_t& step_handle) const {
    return graph->get_path_handle_of_step(step_handle);
}

bool PackedPositionOverlay::for_each_path_handle_impl(const std::function<bool(const path_handle_t&)>& iteratee) const {
    return graph->for_each_path_handle(iteratee);
}

bool PackedPositionOverlay::for_each_step_on_handle_impl(const handle_t& handle,
                                                         const function<bool(const step_handle_t&)>& iteratee) const {
    return graph->for_each_step_on_handle(handle, iteratee);
}

size_t PackedPositionOverlay::get_path_length(const path_handle_t& path_handle) const {
    const auto& range = path_range.at(as_integer(path_handle));
    if (range.first == range.second) {
        return 0;
    }
    step_handle_t step;
    as_integers(step)[0] = steps_0.get(range.second - 1);
    as_integers(step)[1] = steps_1.get(range.second - 1);
    return positions.get(range.second - 1) + get_length(get_handle_of_step(step));
}

size_t PackedPositionOverlay::get_position_of_step(const step_handle_t& step) const {
    if (step == path_end(get_path_handle_of_step(step))) {
        return get_path_length(get_path_handle_of_step(step));
    }
    else {
        return step_positions.get(step_hash->lookup(step));
    }
}

step_handle_t PackedPositionOverlay::get_step_at_position(const path_handle_t& path,
                                                          const size_t& position) const {
    
    const auto& range = path_range.at(as_integer(path));
    
    // check if position it outside the range (handles edge case of an empty path too)
    if (position >= get_path_length(path)) {
        return path_end(path);
    }
    
    // bisect search within the range to find the index with the steps
    size_t low = range.first;
    size_t hi = range.second;
    while (hi > low + 1) {
        size_t mid = (hi + low) / 2;
        if (position < positions.get(mid)) {
            hi = mid;
        }
        else {
            low = mid;
        }
    }
    
    // unpack the integers at the same index into a step
    step_handle_t step;
    as_integers(step)[0] = steps_0.get(low);
    as_integers(step)[1] = steps_1.get(low);
    return step;
}

handle_t PackedPositionOverlay::get_underlying_handle(const handle_t& handle) const {
    return handle;
}

void PackedPositionOverlay::index_path_positions() {
    
    size_t cumul_path_size = 0;
    
    for_each_path_handle([&](const path_handle_t& path_handle) {
        cumul_path_size += get_step_count(path_handle);
    });
    
    // resize the vectors to the number of step handles
    steps_0.resize(cumul_path_size);
    steps_1.resize(cumul_path_size);
    positions.resize(cumul_path_size);
    step_positions.resize(cumul_path_size);
    
    // make a perfect minimal hash over the step handles
    step_hash = new boomphf::mphf<step_handle_t, StepHash>(cumul_path_size, BBHashHelper(graph), get_thread_count(), 2.0, false, false);
    
    size_t i = 0;
    for_each_path_handle([&](const path_handle_t& path_handle) {
        // make a range corresponding to this path handle
        auto& range = path_range[as_integer(path_handle)];
        range.first = i;
        size_t position = 0;
        for_each_step_in_path(path_handle, [&](const step_handle_t& step) {
            
            // fill in the position to step index
            steps_0.set(i, as_integers(step)[0]);
            steps_1.set(i, as_integers(step)[1]);
            positions.set(i, position);
            
            // fill in the step to position index
            step_positions.set(step_hash->lookup(step), position);
            
            position += get_length(get_handle_of_step(step));
            ++i;
        });
        range.second = i;
    });
}

uint64_t PackedPositionOverlay::StepHash::operator()(const step_handle_t& step, uint64_t seed) const {
    const int64_t* int_step = as_integers(step);
    uint64_t hsh1 = boomphf::SingleHashFunctor<int64_t>()(int_step[0], seed);
    uint64_t hsh2 = boomphf::SingleHashFunctor<int64_t>()(int_step[1], seed);
    // Boost combine for hash values
    return hsh1 ^ (hsh2 + 0x9e3779b9 + (hsh1<<6) + (hsh1>>2));
}

BBHashHelper::BBHashHelper(const PathHandleGraph* graph) : graph(graph) {
    path_handles.reserve(graph->get_path_count());
    graph->for_each_path_handle([&](const path_handle_t& path_handle) {
        if (!graph->is_empty(path_handle)) {
            // this path contains steps, we want to iterate over it
            path_handles.push_back(path_handle);
        }
    });
}

BBHashHelper::iterator BBHashHelper::begin() const {
    return iterator(this, 0);
}

BBHashHelper::iterator BBHashHelper::end() const {
    return iterator(this, path_handles.size());
}

BBHashHelper::iterator::iterator(const BBHashHelper* _iteratee, size_t _path_handle_idx) : iteratee(_iteratee), path_handle_idx(_path_handle_idx) {
    if (path_handle_idx < this->iteratee->path_handles.size()) {
        step = this->iteratee->graph->path_begin(this->iteratee->path_handles[path_handle_idx]);
    }
}

BBHashHelper::iterator& BBHashHelper::iterator::operator++() {
    step = iteratee->graph->get_next_step(step);
    if (step == iteratee->graph->path_begin(iteratee->path_handles[path_handle_idx]) ||
        step == iteratee->graph->path_end(iteratee->path_handles[path_handle_idx])) {
        // we either went off the end or looped around a circular path to the beginning again
        ++path_handle_idx;
        if (path_handle_idx < iteratee->path_handles.size()) {
            step = iteratee->graph->path_begin(iteratee->path_handles[path_handle_idx]);
        }
    }
    return *this;
}

step_handle_t BBHashHelper::iterator::operator*() const {
    return step;
}

bool BBHashHelper::iterator::operator==(const BBHashHelper::iterator& other) const {
    // on the end iterator, we don't care what the step is, only that we're past-the-last
    // path handle
    return (iteratee == other.iteratee
            && path_handle_idx == other.path_handle_idx
            && (step == other.step || path_handle_idx == iteratee->path_handles.size()));
}

bool BBHashHelper::iterator::operator!=(const BBHashHelper::iterator& other) const {
    return !(*this == other);
}
}
