/*
 * PrefixJaccardScore.h
 *
 *  Created on: 26.09.2014
 *      Author: Michael Hamann
 */

#include <networkit/edgescores/PrefixJaccardScore.hpp>
#include <omp.h>

namespace NetworKit {

template <typename AttributeT>
PrefixJaccardScore<AttributeT>::PrefixJaccardScore(const Graph &G, const std::vector< AttributeT > &attribute) :
    EdgeScore<double>(G), inAttribute(attribute) {
}

template <typename AttributeT>
void PrefixJaccardScore<AttributeT>::run() {
    //this-> required to access members of base class, since this is a template class.
    if (!this->G.hasEdgeIds()) throw std::runtime_error("Error, edges need to be indexed first");

    this->scoreData.clear();
    this->scoreData.resize(this->G.upperEdgeIdBound());

    struct RankedEdge {
        node u;
        AttributeT att;
        count rank;

        RankedEdge(node u, AttributeT att, count rank) : u(u), att(att), rank(rank) {};

        bool operator<(const RankedEdge &other) const {
            return std::tie(rank, att, u) < std::tie(other.rank, other.att, other.u);
        };

        bool operator>(const RankedEdge &other) const {
            return std::tie(rank, att, u) > std::tie(other.rank, other.att, other.u);
        };
    };

    std::vector<size_t> rankedEdgeBegin(G.upperNodeIdBound() + 1);
    std::vector<RankedEdge> rankedEdges;
    rankedEdges.reserve(2*G.numberOfEdges());

    for (node u = 0; u < G.upperNodeIdBound(); ++u) {
        rankedEdgeBegin[u] = rankedEdges.size();
        if (G.hasNode(u)) {
            G.forEdgesOf(u, [&](node, node v, edgeid eid) {
                rankedEdges.emplace_back(v, inAttribute[eid], 0);
            });
        }
    }
    rankedEdgeBegin[G.upperNodeIdBound()] = rankedEdges.size();

    this->G.balancedParallelForNodes([&](node u) {
        if (this->G.degree(u) == 0) return;

        const auto beginIt = rankedEdges.begin() + rankedEdgeBegin[u];
        const auto endIt = rankedEdges.begin() + rankedEdgeBegin[u+1];

        std::sort(beginIt, endIt, std::greater<RankedEdge>());

        AttributeT curVal = beginIt->att;
        count curRank = 0;
        count numEqual = 0;
        for (auto it = beginIt; it != endIt; ++it) {
            if (curVal != it->att) {
                curRank += numEqual;
                curVal = it->att;
                numEqual = 1;
            } else {
                ++numEqual;
            }

            it->rank = curRank;
        }
    });

    std::vector<std::vector<bool>> uMarker(omp_get_max_threads(), std::vector<bool>(G.upperNodeIdBound(), false));
    auto vMarker = uMarker;

    this->G.parallelForEdges([&](node u, node v, edgeid eid) {
        count curRank = 0;
        double bestJaccard = 0;
        auto tid = omp_get_thread_num();

        auto uIt = rankedEdges.begin() + rankedEdgeBegin[u];
        auto vIt = rankedEdges.begin() + rankedEdgeBegin[v];
        const auto uEndIt = rankedEdges.begin() + rankedEdgeBegin[u+1];
        const auto vEndIt = rankedEdges.begin() + rankedEdgeBegin[v+1];

        count commonNeighbors = 0;
        count uNeighbors = 0;
        count vNeighbors = 0;

        while (uIt != uEndIt || vIt != vEndIt) {
            while (uIt != uEndIt && curRank == uIt->rank) {
                if (uIt->u == v) {
                    ++uIt;
                    continue;
                }

                if (vMarker[tid][uIt->u]) {
                    vMarker[tid][uIt->u] = false;
                    ++commonNeighbors;
                    --vNeighbors;
                } else {
                    uMarker[tid][uIt->u] = true;
                    ++uNeighbors;
                }

                ++uIt;
            }

            while (vIt != vEndIt && curRank == vIt->rank) {
                if (vIt->u == u) {
                    ++vIt;
                    continue;
                }

                if (uMarker[tid][vIt->u]) {
                    uMarker[tid][vIt->u] = false;
                    ++commonNeighbors;
                    --uNeighbors;
                } else {
                    vMarker[tid][vIt->u] = true;
                    ++vNeighbors;
                }

                ++vIt;
            }

            bestJaccard = std::max(bestJaccard, commonNeighbors * 1.0 / (uNeighbors + vNeighbors + commonNeighbors));

            ++curRank;
        }

        G.forNeighborsOf(u, [&](node w) {
            uMarker[tid][w] = false;
        });

        G.forNeighborsOf(v, [&](node w) {
            vMarker[tid][w] = false;
        });

        this->scoreData[eid] = bestJaccard;
    });

    this->hasRun = true;
}

template <typename AttributeT>
double PrefixJaccardScore<AttributeT>::score(node, node) {
    throw std::runtime_error("Not implemented: Use scores() instead.");
}

template <typename AttributeT>
double PrefixJaccardScore<AttributeT>::score(edgeid) {
    throw std::runtime_error("Not implemented: Use scores() instead.");
}

template class PrefixJaccardScore<double>;
template class PrefixJaccardScore<count>;

}
