#include "graph_creator.h"

#include <library/cpp/testing/gtest/gtest.h>
#include <maps/wikimap/mapspro/services/mrc/long_tasks/tasks_gen/lib/split_path.h>

#include <iostream>
#include <set>
#include <sstream>

namespace maps::mrc::gen_targets::tests {

std::string toString(const LoopsPaths& loops)
{
    std::stringstream out;
    out << "[";
    for (const auto& loop : loops) {
        out << "[";
        for (const auto& loopEdge : loop) {
            out << "{" << loopEdge.edgeId << ", " << loopEdge.loopId << "}, ";
        }
        out << "], ";
    }
    out << "]";
    return out.str();
}

// returns true if two set of tasks are equal
bool isEqual(const LoopsPaths& loopsGot, const LoopsPaths& loopsExpected)
{
    if (loopsGot.size() == loopsExpected.size()) {
        bool isEqual = true;
        for (const auto& loopExpected : loopsExpected) {
            if (std::find(loopsGot.begin(), loopsGot.end(), loopExpected)
                == loopsGot.end()) {
                isEqual = false;
            }
        }
        if (isEqual) {
            return true;
        }
    }

    std::cerr
        << "\nTasks are not equal:\n"
        << "exp: " << toString(loopsExpected) << "\n"
        << "got: " << toString(loopsGot) << "\n";
    return false;
}


TEST(split_path, test_nothing_to_split)
{
    //  graph:
    //     --5->
    //     ^    |
    //     |    6
    //     4    |
    //     |    V
    //      <-2--
    //
    // loop length is 4000m

    GraphCreator graphCreator;
    graphCreator
        .addEdge(2, {1, 0}, {2, 0}, {4})
        .addEdge(4, {1, 0}, {1, 1}, {5})
        .addEdge(5, {1, 1}, {2, 1}, {6})
        .addEdge(6, {2, 1}, {2, 0}, {2});
    RoadNetworkData roadNetwork = graphCreator.createGraph();

    LoopsPath path{{4, 1}, {5, 1}, {6, 1}, {2, 1}};

    auto splitTasks = [&](double minTaskLength,
                          TaskSplitter::Overhead allowedOverhead){
        return TaskSplitter(roadNetwork, path, minTaskLength,
                            allowedOverhead,
                            graphCreator.getTargetEdges()).getResult();
    };

    // running splitTasks on single loop always returns that loop

    auto tasks = splitTasks(1, TaskSplitter::Overhead::NoOverhead);
    EXPECT_TRUE(isEqual(tasks, LoopsPaths{path}));

    tasks = splitTasks(1000000, TaskSplitter::Overhead::NoOverhead);
    EXPECT_TRUE(isEqual(tasks, {path}));

    tasks = splitTasks(1, TaskSplitter::Overhead::SmallOverhead);
    EXPECT_TRUE(isEqual(tasks, {path}));

    tasks = splitTasks(1000000, TaskSplitter::Overhead::SmallOverhead);
    EXPECT_TRUE(isEqual(tasks, {path}));

    tasks = splitTasks(1, TaskSplitter::Overhead::BigOverhead);
    EXPECT_TRUE(isEqual(tasks, {path}));

    tasks = splitTasks(1000000, TaskSplitter::Overhead::BigOverhead);
    EXPECT_TRUE(isEqual(tasks, {path}));
}

TEST(split_path, test_2_loops)
{
    //  graph:
    //          --8->
    //          ^    |
    //          |    9
    //          7    |
    //          |    V
    //     --5-><-10--
    //     ^    |
    //     |    6
    //     4    |
    //     |    V
    //      <-2--
    //
    // each loop length is 4000m

    GraphCreator graphCreator;
    graphCreator
        .addEdge(2, {1, 0}, {2, 0}, {4})
        .addEdge(4, {1, 0}, {1, 1}, {5})
        .addEdge(5, {1, 1}, {2, 1}, {6, 7})
        .addEdge(6, {2, 1}, {2, 0}, {2})
        .addEdge(7, {2, 1}, {2, 2}, {8})
        .addEdge(8, {2, 2}, {3, 2}, {9})
        .addEdge(9, {3, 2}, {3, 1}, {10})
        .addEdge(10, {3, 1}, {2, 1}, {7, 6});
    RoadNetworkData roadNetwork = graphCreator.createGraph();

    LoopsPath path{{4, 1}, {5, 1}, {7, 2}, {8, 2}, {9, 2}, {10, 2}, {6, 1}, {2, 1}};

    auto splitTasks = [&](double minTaskLength,
                          TaskSplitter::Overhead allowedOverhead){
        return TaskSplitter(roadNetwork, path, minTaskLength,
                            allowedOverhead,
                            graphCreator.getTargetEdges()).getResult();
    };

    LoopsPath task1{{4, 1}, {5, 1}, {6, 1}, {2, 1}};
    LoopsPath task2{{7, 2}, {8, 2}, {9, 2}, {10, 2}};

    // splitTask with minTaskLength=3000 and any allowedOverhead
    // should return 2 tasks because all loops are smaller than 3000
    auto tasks = splitTasks(3000, TaskSplitter::Overhead::NoOverhead);
    EXPECT_TRUE(isEqual(tasks, {task2, task1}));

    tasks = splitTasks(3000, TaskSplitter::Overhead::SmallOverhead);
    EXPECT_TRUE(isEqual(tasks, {task2, task1}));

    tasks = splitTasks(3000, TaskSplitter::Overhead::BigOverhead);
    EXPECT_TRUE(isEqual(tasks, {task2, task1}));

    // splitTasks with minTaskLength=5000 should not split path
    // because loops are smaller than 5000
    tasks = splitTasks(5000, TaskSplitter::Overhead::NoOverhead);
    EXPECT_TRUE(isEqual(tasks, {path}));
    tasks = splitTasks(5000, TaskSplitter::Overhead::SmallOverhead);
    EXPECT_TRUE(isEqual(tasks, {path}));
    tasks = splitTasks(5000, TaskSplitter::Overhead::BigOverhead);
    EXPECT_TRUE(isEqual(tasks, {path}));
}

TEST(split_path, test_split_with_small_overhead)
{
    //  graph:
    //          --8->
    //          ^    |
    //          |    9
    //          7    |
    //          |    V
    //     --5-><-10--
    //     <-11-
    //     ^    |
    //     |    6
    //     4    |
    //     |    V
    //      <-2--
    //
    // maneuver 10-7 is not allowed
    //
    // each loop length is 4000m

    GraphCreator graphCreator;
    graphCreator
        .addEdge(1, {0, 0}, {1, 0}, {4})
        .addEdge(2, {1, 0}, {2, 0}, {4})
        .addEdge(3, {2, 0}, {3, 0}, {})
        .addEdge(4, {1, 0}, {1, 1}, {5})
        .addEdge(5, {1, 1}, {2, 1}, {6, 7, 11})
        .addEdge(6, {2, 1}, {2, 0}, {2, 3})
        .addEdge(7, {2, 1}, {2, 2}, {8})
        .addEdge(8, {2, 2}, {3, 2}, {9})
        .addEdge(9, {3, 2}, {3, 1}, {10})
        .addEdge(10, {3, 1}, {2, 1}, {11, 6})
        .addEdge(11, {2, 1}, {1, 1}, {5});
    RoadNetworkData roadNetwork = graphCreator.createGraph();

    LoopsPath path{{4, 1}, {5, 1}, {7, 2}, {8, 2}, {9, 2}, {10, 2}, {6, 1}, {2, 1}};

    auto splitTasks = [&](double minTaskLength,
                          TaskSplitter::Overhead allowedOverhead){
        return TaskSplitter(roadNetwork, path, minTaskLength,
                            allowedOverhead,
                            graphCreator.getTargetEdges()).getResult();
    };

    LoopsPath task1{{4, 1}, {5, 1}, {6, 1}, {2, 1}};
    LoopsPath task2{{7, 2}, {8, 2}, {9, 2}, {10, 2}, {11, 2}, {5, 2}};

    // it is impossible to split path into 2 tasks without overhead because of
    // prohibited maneuvers
    auto tasks = splitTasks(3000, TaskSplitter::Overhead::NoOverhead);
    EXPECT_TRUE(isEqual(tasks, {path}));

    // splitTask with mintaskLength=3000 and any non-zero allowedOverhead
    // should return 2 tasks
    tasks = splitTasks(3000, TaskSplitter::Overhead::SmallOverhead);
    EXPECT_TRUE(isEqual(tasks, {task2, task1}));
    tasks = splitTasks(3000, TaskSplitter::Overhead::BigOverhead);
    EXPECT_TRUE(isEqual(tasks, {task2, task1}));

    // splitTasks with mintaskLength=5000 should not split path
    // because loops are smaller than 5000
    tasks = splitTasks(5000, TaskSplitter::Overhead::NoOverhead);
    EXPECT_TRUE(isEqual(tasks, {path}));
    tasks = splitTasks(5000, TaskSplitter::Overhead::SmallOverhead);
    EXPECT_TRUE(isEqual(tasks, {path}));
    tasks = splitTasks(5000, TaskSplitter::Overhead::BigOverhead);
    EXPECT_TRUE(isEqual(tasks, {path}));
}

TEST(split_path, test_split_3_loops)
{
    //  graph:
    // <-12-      --8->
    // |    ^    ^    |
    // 13   |    |    9
    // |    11   7    |
    // v    |    |    V
    // -14->--5-><-10--
    //      <-15--
    //      ^    |
    //      4    |
    //      |    6
    //      |    v
    //       <-2--
    //
    // edges 4 and 6 have double length
    // edge 15 is not target
    //
    // bottom loop length is 6000m, other loops length is 4000m
    GraphCreator graphCreator;
    graphCreator
        .addEdge(2, {1, -1}, {2, -1}, {4})
        .addEdge(4, {1, -1}, {1, 1}, {5, 11})
        .addEdge(5, {1, 1}, {2, 1}, {6, 7, 15})
        .addEdge(6, {2, 1}, {2, -1}, {2})
        .addEdge(7, {2, 1}, {2, 2}, {8})
        .addEdge(8, {2, 2}, {3, 2}, {9})
        .addEdge(9, {3, 2}, {3, 1}, {10})
        .addEdge(10, {3, 1}, {2, 1}, {7, 6, 15})
        .addEdge(11, {1, 1}, {1, 2}, {12})
        .addEdge(12, {1, 2}, {0, 2}, {13})
        .addEdge(13, {0, 2}, {0, 1}, {14})
        .addEdge(14, {0, 1}, {1, 1}, {11, 5})
        .addEdge(15, {2, 1}, {1, 1}, {5, 11}, false);
    RoadNetworkData roadNetwork = graphCreator.createGraph();

    LoopsPath path{{4, 1}, {11, 2}, {12, 2}, {13, 2}, {14, 2}, {5, 1},
                   {7, 3}, {8, 3}, {9, 3}, {10, 3}, {6, 1}, {2, 1}};

    auto splitTasks = [&](double minTaskLength,
                          TaskSplitter::Overhead allowedOverhead){
        return TaskSplitter(roadNetwork, path, minTaskLength,
                            allowedOverhead,
                            graphCreator.getTargetEdges()).getResult();
    };

    LoopsPath task1{{4, 1}, {5, 1}, {6, 1}, {2, 1}};
    LoopsPath task2{{11, 2}, {12, 2}, {13, 2}, {14, 2}};
    LoopsPath task3{{7, 3}, {8, 3}, {9, 3}, {10, 3}};
    LoopsPath task1_3{{4, 1}, {5, 1}, {7, 3}, {8, 3}, {9, 3},
                      {10, 3}, {6, 1}, {2, 1}};;
    LoopsPath task1_2{{4, 1}, {11, 2}, {12, 2}, {13, 2}, {14, 2},
                      {5, 1}, {6, 1}, {2, 1}};
    LoopsPath task2_3{{11, 2}, {12, 2}, {13, 2}, {14, 2}, {5, 1},
                      {7, 3}, {8, 3}, {9, 3}, {10, 3}, {15, 2}};

    // task splitter should split path into 3 tasks
    auto tasks = splitTasks(3500, TaskSplitter::Overhead::NoOverhead);
    EXPECT_TRUE(isEqual(tasks, {task2, task3, task1}));

    // task splitter should split path into 3 tasks
    tasks = splitTasks(3500, TaskSplitter::Overhead::SmallOverhead);
    EXPECT_TRUE(isEqual(tasks, {task2, task3, task1}));

    // with allowedOverhead=BigOverhead task splitter always splits
    // only into 2 tasks
    tasks = splitTasks(3500, TaskSplitter::Overhead::BigOverhead);
    if (tasks == LoopsPaths{task2, task1_3}) {
        EXPECT_TRUE(isEqual(tasks, {task2, task1_3}));
    } else {
        EXPECT_TRUE(isEqual(tasks, {task3, task1_2}));
    }

    // task 2 and task 3 are smaller than 4800m, so the path should
    // not be splitted in the tasks joints
    tasks = splitTasks(4800, TaskSplitter::Overhead::NoOverhead);
    EXPECT_TRUE(isEqual(tasks, {path}));
    tasks = splitTasks(4800, TaskSplitter::Overhead::SmallOverhead);
    EXPECT_TRUE(isEqual(tasks, {path}));

    // task splitter with allowedOverhead=BigOverhead can split in any
    // point.
    // the only possible splitting with tasks > 4800m is {task1 and task2_3}
    tasks = splitTasks(4800, TaskSplitter::Overhead::BigOverhead);
    EXPECT_TRUE(isEqual(tasks, {task2_3, task1}));
}

TEST(split_path, test_split_transitive_loops)
{
    //  graph:
    //       <-12-
    //      |    ^
    //      13   |
    //      |    11
    //      v    |
    //      -14-->
    //            --8->-15->
    //           ^    |
    //           |    9
    //           7    |
    //           |    V
    //      --5-><-10--
    //      ^    |
    //      |    6
    //      4    |
    //      |    V
    // --1-> <-2-- --3->
    //
    // each loop length is 4000m

    GraphCreator graphCreator;
    graphCreator
        .addEdge(1, {0, 0}, {1, 0}, {4})
        .addEdge(2, {1, 0}, {2, 0}, {4})
        .addEdge(3, {2, 0}, {3, 0}, {})
        .addEdge(4, {1, 0}, {1, 1}, {5})
        .addEdge(5, {1, 1}, {2, 1}, {6, 7})
        .addEdge(6, {2, 1}, {2, 0}, {2, 3})
        .addEdge(7, {2, 1}, {2, 2}, {8, 11})
        .addEdge(8, {2, 2}, {3, 2}, {9})
        .addEdge(9, {3, 2}, {3, 1}, {10})
        .addEdge(10, {3, 1}, {2, 1}, {7, 6})
        .addEdge(11, {1, 1}, {1, 2}, {12})
        .addEdge(12, {1, 2}, {0, 2}, {13})
        .addEdge(13, {0, 2}, {0, 1}, {14})
        .addEdge(14, {0, 1}, {1, 1}, {11, 8})
        .addEdge(15, {3, 2}, {4, 2}, {});
    RoadNetworkData roadNetwork = graphCreator.createGraph();

    LoopsPath path{{4, 1}, {5, 1}, {7, 2}, {11, 3}, {12, 3}, {13, 3},
                   {14, 3}, {8, 2}, {9, 2}, {10, 2}, {6, 1}, {2, 1}};
    LoopsPath task1{{4, 1}, {5, 1}, {6, 1}, {2, 1}};
    LoopsPath task2{{7, 2}, {8, 2}, {9, 2}, {10, 2}};
    LoopsPath task3{{11, 3}, {12, 3}, {13, 3}, {14, 3}};

    auto splitTasks = [&](double minTaskLength,
                          TaskSplitter::Overhead allowedOverhead){
        return TaskSplitter(roadNetwork, path, minTaskLength,
                            allowedOverhead,
                            graphCreator.getTargetEdges()).getResult();
    };

    // task splitter should split path into 3 tasks
    auto tasks = splitTasks(3000, TaskSplitter::Overhead::NoOverhead);
    EXPECT_TRUE(isEqual(tasks, {task3, task2, task1}));
    tasks = splitTasks(3000, TaskSplitter::Overhead::SmallOverhead);
    EXPECT_TRUE(isEqual(tasks, {task3, task2, task1}));
}

TEST(split_path, test_combine_splitting_methods)
{
    //  graph:
    //
    // <-22-      --18->
    // 23   ^    ^    19  task4
    // v    21   17    v
    // -24->--16-><-20--  --- BigOverhead split
    //       <-12-            (because top loops are smaller than minTaskLength)
    //      |    ^
    //      13   |        task3
    //      |    11
    //      v    |
    //      -14--><-15-   --- SmallOverhead split
    //            --8->       (because 14->11 is not allwed)
    //           ^    |
    //           |    9   task2
    //           7    |
    //           |    V
    //      --5-><-10--   --- NoOverhead split
    //      ^    |
    //      |    6
    //      4    |        task1
    //      |    V
    //       <-2--
    //
    // edges 15, 16 are not targets
    //
    // top loops length is 2500m, other loops length is 4000m
    GraphCreator graphCreator;
    graphCreator
        .addEdge(2, {1, 0}, {2, 0}, {4})
        .addEdge(4, {1, 0}, {1, 1}, {5})
        .addEdge(5, {1, 1}, {2, 1}, {6, 7})
        .addEdge(6, {2, 1}, {2, 0}, {2})
        .addEdge(7, {2, 1}, {2, 2}, {8, 11})
        .addEdge(8, {2, 2}, {3, 2}, {9, 15})
        .addEdge(9, {3, 2}, {3, 1}, {10})
        .addEdge(10, {3, 1}, {2, 1}, {7, 6})
        .addEdge(11, {1, 1}, {1, 2}, {12, 17})
        .addEdge(12, {1, 2}, {0, 2}, {13, 16})
        .addEdge(13, {0, 2}, {0, 1}, {14})
        .addEdge(14, {0, 1}, {1, 1}, {8})
        .addEdge(15, {3, 2}, {2, 2}, {11, 8}, false)
        .addEdge(16, {1, 3}, {2, 3}, {12, 17}, false)
        .addEdge(17, {2, 3}, {2, 3.25}, {18})
        .addEdge(18, {2, 3.25}, {3, 3.25}, {19})
        .addEdge(19, {3, 3.25}, {3, 3}, {20})
        .addEdge(20, {3, 3}, {2, 3}, {17, 12})
        .addEdge(21, {1, 3}, {1, 3.25}, {22})
        .addEdge(22, {1, 3.25}, {0, 3.25}, {23})
        .addEdge(23, {0, 3.25}, {0, 3}, {24})
        .addEdge(24, {0, 3}, {1, 3}, {21, 16, 13});

    RoadNetworkData roadNetwork = graphCreator.createGraph();

    LoopsPath path{{4, 1}, {5, 1}, {7, 2}, {11, 3}, {17, 4}, {18, 4},
                   {19, 4}, {20, 4}, {12, 3}, {21, 5}, {22, 5}, {23, 5},
                   {24, 5}, {13, 3}, {14, 3}, {8, 2}, {9, 2},
                   {10, 2}, {6, 1}, {2, 1}};
    LoopsPath task1{{4, 1}, {5, 1}, {6, 1}, {2, 1}};
    LoopsPath task2{{7, 2}, {8, 2}, {9, 2}, {10, 2}};
    LoopsPath task3{{11, 3}, {12, 3}, {13, 3}, {14, 3}, {8, 3}, {15, 3}};
    LoopsPath task4{{17, 4}, {18, 4}, {19, 4}, {20, 4}, {12, 3}, {21, 5},
                    {22, 5}, {23, 5}, {24, 5}, {16, 4}};

    // splitLoopRouteIntoTasks calls splitTasks in all the
    // allowedOverhead modes, the path should be splitted into 4 tasks
    auto tasks = splitLoopRouteIntoTasks(roadNetwork, path, 3800);
    EXPECT_TRUE(isEqual(tasks, {task4, task1, task2, task3}));
}

} // namespace maps::mrc::gen_targets::tests
