#include "helpers.h"

#include "../revision_meta/historical_snapshot.h"

#include <maps/libs/geolib/include/polygon.h>
#include <maps/libs/geolib/include/serialization.h>
#include <yandex/maps/wiki/revision/revisionsgateway.h>

#include <yandex/maps/wiki/unittest/localdb.h>

#include <boost/test/unit_test.hpp>

using namespace maps;
using namespace maps::wiki;
using namespace maps::wiki::revision;
using namespace maps::wiki::contours::revision_meta;
using namespace maps::wiki::contours::revision_meta::test;

namespace {

std::string wkt2wkb(const std::string& wkt)
{
    auto polyline = geolib3::WKT::read<geolib3::Polyline2>(wkt);
    std::stringstream wkb;
    geolib3::WKB::write(polyline, wkb);
    return wkb.str();
}

// add 3 contour objects and 1 dumb object with common rings/elements
const ObjectDataVector commit_1 = {
    {101, {{"cat:ad", "1"}}},
    {102, {{"cat:ad_fc", "1"}}},
    {103, {{"cat:ad_fc", "1"}}},
    {104, {{"cat:ad_el", "1"}}, wkt2wkb("LINESTRING(0 0, 0 10, 10 10)")},
    {105, {{"cat:ad_el", "1"}}, wkt2wkb("LINESTRING(10 10, 10 0, 0 0)")},
    {106, {{"cat:ad_el", "1"}}, wkt2wkb("LINESTRING(20 0, 20 10, 30 10)")},
    {107, {{"cat:ad_el", "1"}}, wkt2wkb("LINESTRING(30 10, 30 0, 20 0)")},
    {108, AD_AD_FC_PART_REL_ATTRS, RelationData{101, 102}},
    {109, AD_AD_FC_PART_REL_ATTRS, RelationData{101, 103}},
    {110, AD_FC_EL_PART_REL_ATTRS, RelationData{102, 104}},
    {111, AD_FC_EL_PART_REL_ATTRS, RelationData{102, 105}},
    {112, AD_FC_EL_PART_REL_ATTRS, RelationData{103, 106}},
    {113, AD_FC_EL_PART_REL_ATTRS, RelationData{103, 107}},

    {201, {{"cat:ad", "1"}}},
    {202, {{"cat:ad_fc", "1"}}},
    {203, {{"cat:ad_el", "1"}}, wkt2wkb("LINESTRING(40 0, 40 10, 50 10)")},
    {204, {{"cat:ad_el", "1"}}, wkt2wkb("LINESTRING(50 10, 50 0, 40 0)")},
    {205, AD_AD_FC_PART_REL_ATTRS, RelationData{201, 103}},
    {206, AD_AD_FC_PART_REL_ATTRS, RelationData{201, 202}},
    {207, AD_FC_EL_PART_REL_ATTRS, RelationData{202, 203}},
    {208, AD_FC_EL_PART_REL_ATTRS, RelationData{202, 204}},

    {301, {{"cat:ad", "1"}}},
    {302, {{"cat:ad_fc", "1"}}},
    {303, {{"cat:ad_el", "1"}}, wkt2wkb("LINESTRING(60 0, 60 10, 70 10)")},
    {304, {{"cat:ad_el", "1"}}, wkt2wkb("LINESTRING(70 10, 70 0, 60 0)")},
    {305, AD_AD_FC_PART_REL_ATTRS, RelationData{301, 202}},
    {306, AD_AD_FC_PART_REL_ATTRS, RelationData{301, 302}},
    {307, AD_FC_EL_PART_REL_ATTRS, RelationData{302, 303}},
    {308, AD_FC_EL_PART_REL_ATTRS, RelationData{302, 304}},

    {401, {{"cat:dumb", "1"}}},
    {402, {{"rel:master", "dumb"}, {"rel:slave", "ad_el"}, {"rel:role", "dumb"}}, RelationData{401, 107}},
};

// remove dumb object
const ObjectDataVector commit_2 = {
    {{401, 1}, true},
    {{402, 1}, true},
};

// edit common element
const ObjectDataVector commit_3 = {
    {{203, 1}, wkt2wkb("LINESTRING(40 0, 50 10)")},
};

// make element private - remove one relation
const ObjectDataVector commit_4 = {
    {{305, 1}, true},
};

// edit private element
const ObjectDataVector commit_5 = {
    {{203, 3}, wkt2wkb("LINESTRING(40 0, 45 5, 50 10)")},
};

// add contour object with common ring
const ObjectDataVector commit_6 = {
    {501, {{"cat:ad", "1"}}},
    {502, {{"cat:ad_fc", "1"}}},
    {503, {{"cat:ad_el", "1"}}, wkt2wkb("LINESTRING(80 0, 80 10, 90 10)")},
    {504, {{"cat:ad_el", "1"}}, wkt2wkb("LINESTRING(90 10, 90 0, 80 0)")},
    {505, AD_AD_FC_PART_REL_ATTRS, RelationData{501, 302}},
    {506, AD_AD_FC_PART_REL_ATTRS, RelationData{501, 502}},
    {507, AD_FC_EL_PART_REL_ATTRS, RelationData{502, 503}},
    {508, AD_FC_EL_PART_REL_ATTRS, RelationData{502, 504}},
};

// remove contour object
const ObjectDataVector commit_7 = {
    {{101, 1}, true},
    {{102, 1}, true},
    {{108, 1}, true},
    {{109, 1}, true},
};

// make element common - restore relation
const ObjectDataVector commit_8 = {
    {{305, 4}, false},
};

std::ostream&
operator <<(std::ostream& stream, const TObjectIdSet& ids)
{
    for (const auto& id: ids) {
        stream << id << "; ";
    }
    return stream;
}

struct Test {
    TCommitId minCommit;
    TCommitId maxCommit;
    TObjectIdSet expected;
};

} // namespace

BOOST_FIXTURE_TEST_SUITE(diff_tests, unittest::MapsproDbFixture)

BOOST_AUTO_TEST_CASE(test_diff)
{
    fillDatabase(pool(), TRUNK_BRANCH_ID, TEST_UID, COMMIT_ATTRS,
        { commit_1
        , commit_2
        , commit_3
        , commit_4
        , commit_5
        , commit_6
        , commit_7
        , commit_8
        });
    const Test TESTS[] =
        { {1, 1, {101, 201, 301}} // don't count adding dumb object
        , {2, 2, {}} // don't count removing dumb object
        , {3, 3, {201, 301}} // editing of common element affects 2 contour objects
        , {4, 4, {301}} // removing relation affects 1 contour objects
        , {5, 5, {201}} // editing of private element affects 1 contour object
        , {6, 6, {501}} // adding 1 contour object, but many parts
        , {7, 7, {101}} // removing 1 contour object
        , {8, 8, {301}} // restoring relation affects 1 contour object
        , {1, 8, {101, 201, 301, 501}} // all contour objects
        };
    auto txn = pool().masterReadOnlyTransaction();
    for (const auto& test: TESTS) {
        contours::revision_meta::HistoricalSnapshot snapshot
            ( test.minCommit
            , test.maxCommit
            , *txn
            , TRUNK_BRANCH_ID
            , getCategories()
            );
        auto diff = snapshot.affectedObjects
            ( RelationType::Master
            , [](const TCategoryId& cat) { return cat == "ad"; }
            );
        BOOST_CHECK_EQUAL_COLLECTIONS
            ( diff.begin(), diff.end()
            , test.expected.begin(), test.expected.end()
            );
        std::cout << test.minCommit << "-" << test.maxCommit << ": " << diff << "\n";
    }
}

BOOST_AUTO_TEST_SUITE_END()
