/*
 * DynamicsGTest.cpp
 *
 *  Created on: 24.12.2013
 *      Author: cls
 */

#include <gtest/gtest.h>

#include <networkit/auxiliary/Log.hpp>
#include <networkit/dynamics/DGSStreamParser.hpp>
#include <networkit/dynamics/DGSWriter.hpp>
#include <networkit/dynamics/GraphDifference.hpp>
#include <networkit/dynamics/GraphEvent.hpp>
#include <networkit/dynamics/GraphEventProxy.hpp>
#include <networkit/dynamics/GraphUpdater.hpp>

namespace NetworKit {

class DynamicsGTest : public testing::Test {};

TEST_F(DynamicsGTest, testDGSStreamWriter) {
    // writing each possible GraphEvent
    std::vector<NetworKit::GraphEvent> stream = {
        GraphEvent::NODE_ADDITION,     GraphEvent::NODE_REMOVAL, GraphEvent::NODE_RESTORATION,
        GraphEvent::EDGE_ADDITION,     GraphEvent::EDGE_REMOVAL, GraphEvent::EDGE_WEIGHT_INCREMENT,
        GraphEvent::EDGE_WEIGHT_UPDATE};

    DGSWriter writer;
    writer.write(stream, "input/example_write.dgs");

    // expecting to read the same stream
    DGSStreamParser parser("input/example_write.dgs");
    auto stream_parsed = parser.getStream();
    for (index i = 0; i < stream.size(); ++i) {
        EXPECT_EQ(stream[i].type, stream_parsed[i].type);
    }
}

TEST_F(DynamicsGTest, testDGSStreamParser) {
    DGSStreamParser parser("input/example2.dgs");
    auto stream = parser.getStream();
    ASSERT_EQ(stream.size(), 16);
    ASSERT_EQ(stream[0].type, GraphEvent::NODE_ADDITION);
    ASSERT_EQ(stream[1].type, GraphEvent::NODE_ADDITION);
    ASSERT_EQ(stream[2].type, GraphEvent::EDGE_ADDITION);
    ASSERT_EQ(stream[3].type, GraphEvent::TIME_STEP);
    ASSERT_EQ(stream[4].type, GraphEvent::EDGE_WEIGHT_UPDATE);
    ASSERT_EQ(stream[5].type, GraphEvent::EDGE_REMOVAL);
    ASSERT_EQ(stream[6].type, GraphEvent::NODE_REMOVAL);
    ASSERT_EQ(stream[7].type, GraphEvent::NODE_REMOVAL);
    ASSERT_EQ(stream[8].type, GraphEvent::NODE_ADDITION);
    ASSERT_EQ(stream[9].type, GraphEvent::NODE_ADDITION);
    ASSERT_EQ(stream[10].type, GraphEvent::EDGE_ADDITION);
    ASSERT_EQ(stream[11].type, GraphEvent::NODE_ADDITION);
    ASSERT_EQ(stream[12].type, GraphEvent::EDGE_ADDITION);
    ASSERT_EQ(stream[13].type, GraphEvent::NODE_ADDITION);
    ASSERT_EQ(stream[14].type, GraphEvent::NODE_REMOVAL);
    ASSERT_EQ(stream[15].type, GraphEvent::NODE_RESTORATION);

    // apply updates
    Graph G(0, true);
    GraphUpdater updater(G);
    updater.update(stream);
    ASSERT_EQ(G.numberOfNodes(), 4);
    ASSERT_EQ(G.numberOfEdges(), 2);
}

TEST_F(DynamicsGTest, debugDGSStreamParserOnRealGraph) {
    std::string path;
    std::cout << "enter .dgs file path: ";
    std::cin >> path;
    DGSStreamParser parser(path);
    auto stream = parser.getStream();
}

TEST_F(DynamicsGTest, testGraphEventIncrement) {
    Graph G(2, true, false); // undirected
    Graph H(2, true, true);  // directed
    G.addEdge(0, 1, 3.14);
    H.addEdge(0, 1, 3.14);
    GraphEvent event(GraphEvent::EDGE_WEIGHT_INCREMENT, 0, 1, 2.1);
    std::vector<GraphEvent> eventstream(1);
    eventstream.push_back(event);
    GraphUpdater Gupdater(G);
    GraphUpdater Hupdater(H);
    Gupdater.update(eventstream);
    Hupdater.update(eventstream);
    EXPECT_EQ(G.weight(0, 1), 5.24);
    EXPECT_EQ(H.weight(0, 1), 5.24);
}

TEST_F(DynamicsGTest, testGraphEventOperators) {
    // checking comparison operators
    GraphEvent add1(GraphEvent::NODE_ADDITION, 1);
    GraphEvent add2(GraphEvent::NODE_ADDITION, 2);
    EXPECT_LT(add1, add2);
    EXPECT_LE(add1, add2);

    EXPECT_GT(add2, add1);
    EXPECT_GE(add2, add1);

    EXPECT_NE(add1, add2);
    EXPECT_EQ(add1, add1);
}

TEST_F(DynamicsGTest, testGraphEventProxy) {
    Graph G(3, true);
    G.addEdge(1, 2);

    GraphEventProxy GEP(G);
    GEP.addNode();
    GEP.addEdge(2, 3);
    EXPECT_EQ(G.numberOfNodes(), 4);
    EXPECT_EQ(G.numberOfEdges(), 2);

    GEP.setWeight(2, 3, 1.5);
    EXPECT_DOUBLE_EQ(G.weight(2, 3), 1.5);
    GEP.incrementWeight(2, 3, 1.5);
    EXPECT_DOUBLE_EQ(G.weight(2, 3), 3.0);

    GEP.removeEdge(2, 3);
    EXPECT_EQ(G.numberOfEdges(), 1);
    GEP.removeNode(3);
    EXPECT_EQ(G.numberOfNodes(), 3);
}

namespace {
// helper methods
std::string edits_to_string(const std::vector<GraphEvent> &events) {
    std::stringstream ss;
    ss << "[";
    for (size_t i = 0; i < events.size(); ++i) {
        ss << events[i].toString();
        if (i < events.size() - 1) {
            ss << ", ";
        }
    }
    ss << "]";
    return ss.str();
}

void expect_graph_equals(const Graph &G1, const Graph &G2) {
    EXPECT_EQ(G1.numberOfNodes(), G2.numberOfNodes());
    EXPECT_EQ(G1.numberOfEdges(), G2.numberOfEdges());

    GraphDifference diff1(G1, G2);
    diff1.run();
    EXPECT_EQ(diff1.getNumberOfEdits(), 0);
    EXPECT_TRUE(diff1.getEdits().empty()) << edits_to_string(diff1.getEdits());

    GraphDifference diff2(G2, G2);
    diff2.run();
    EXPECT_EQ(diff2.getNumberOfEdits(), 0);
    EXPECT_TRUE(diff2.getEdits().empty()) << edits_to_string(diff2.getEdits());
}
} // namespace

TEST_F(DynamicsGTest, testGraphDifference) {
    Graph G1(11, false, false);
    Graph G2(8, false, false);

    G1.addEdge(2, 4);
    G1.addEdge(2, 6);
    G1.addEdge(9, 10);
    G1.addEdge(4, 10);
    G1.removeNode(3);
    G1.removeNode(8);

    G2.addEdge(3, 2);
    G2.removeNode(4);

    {
        GraphDifference diff(G1, G2);
        diff.run();

        EXPECT_EQ(diff.getNumberOfEdgeRemovals(), 4) << edits_to_string(diff.getEdits());
        EXPECT_EQ(diff.getNumberOfNodeRemovals(), 3) << edits_to_string(diff.getEdits());
        EXPECT_EQ(diff.getNumberOfNodeRestorations(), 1) << edits_to_string(diff.getEdits());
        EXPECT_EQ(diff.getNumberOfEdgeAdditions(), 1) << edits_to_string(diff.getEdits());

        Graph H = G1;
        GraphUpdater Hupdater(H);
        Hupdater.update(diff.getEdits());
        EXPECT_TRUE(H.hasEdge(2, 3));

        expect_graph_equals(H, G2);
    }

    {
        GraphDifference diff(G2, G1);
        diff.run();

        EXPECT_EQ(diff.getNumberOfEdgeAdditions(), 4) << edits_to_string(diff.getEdits());
        EXPECT_EQ(diff.getNumberOfNodeRemovals(), 1) << edits_to_string(diff.getEdits());
        EXPECT_EQ(diff.getNumberOfNodeAdditions(), 2) << edits_to_string(diff.getEdits());
        EXPECT_EQ(diff.getNumberOfNodeRestorations(), 1) << edits_to_string(diff.getEdits());
        EXPECT_EQ(diff.getNumberOfEdgeRemovals(), 1) << edits_to_string(diff.getEdits());

        Graph H = G2;
        GraphUpdater Hupdater(H);
        Hupdater.update(diff.getEdits());

        expect_graph_equals(H, G1);
    }
}

TEST_F(DynamicsGTest, testGraphDifferenceDirectedWeighted) {
    Graph G1(8, true, true);
    Graph G2(8, true, true);

    G1.addEdge(0, 1, 0.5);
    G1.addEdge(1, 0, 2);
    G1.addEdge(2, 3, 2.0);

    G2.addEdge(1, 0, 0.5);
    G2.addEdge(3, 2, 1.0);
    G2.addEdge(3, 4, 2.5);

    for (const auto &gs : std::vector<std::pair<Graph, Graph>>({{G1, G2}, {G2, G1}})) {
        const Graph &g1 = gs.first;
        const Graph &g2 = gs.second;

        GraphDifference diff(g1, g2);
        diff.run();

        EXPECT_EQ(diff.getNumberOfEdgeRemovals(), 2) << edits_to_string(diff.getEdits());
        EXPECT_EQ(diff.getNumberOfEdgeAdditions(), 2) << edits_to_string(diff.getEdits());
        EXPECT_EQ(diff.getNumberOfEdgeWeightUpdates(), 1) << edits_to_string(diff.getEdits());
        EXPECT_EQ(diff.getNumberOfEdits(), 5) << edits_to_string(diff.getEdits());

        Graph H = g1;
        GraphUpdater Hupdater(H);
        Hupdater.update(diff.getEdits());

        expect_graph_equals(H, g2);
    }
}

} /* namespace NetworKit */
