#include <gtest/gtest.h>

#include <limits>
#include <map>
#include <set>

#include "base.h"
#include "storage.h"
#include "cursor.h"

class DuplicatesTest : public ::testing::Test {
protected:
    DuplicatesTest():
        ::testing::Test(),
        tu1(db->getStorage<int16_t, uint16_t>("sameSizeInts")),
        tu2(db->getStorage<std::string, int8_t>("stringInt")),
        tu3(db->getStorage<float, float>("floatFloat")),
        tu4(db->getStorage<uint16_t, double>("intDouble")),
        tu5(db->getStorage<float, int64_t>("floatLong")) {}

    ~DuplicatesTest() {}

    uint32_t getTU1Flags() const {return tu1->flags();}
    uint32_t getTU2Flags() const {return tu2->flags();}
    uint32_t getTU3Flags() const {return tu3->flags();}
    uint32_t getTU4Flags() const {return tu4->flags();}
    uint32_t getTU5Flags() const {return tu5->flags();}

    static void SetUpTestSuite() {
        if (db == nullptr) {
            db = new LMDBAL::Base("testBase");
            db->addStorage<int16_t, uint16_t>("sameSizeInts", true);
            db->addStorage<std::string, int8_t>("stringInt", true);
            db->addStorage<float, float>("floatFloat", true);
            db->addStorage<uint16_t, double>("intDouble", true);
            db->addStorage<float, int64_t>("floatLong", true);

            db->open();
        }
    }

    static void TearDownTestSuite() {
        db->close();
        db->removeDirectory();
        delete db;
        db = nullptr;
    }

    static LMDBAL::Base* db;

    LMDBAL::Storage<int16_t, uint16_t>* tu1;
    LMDBAL::Storage<std::string, int8_t>* tu2;
    LMDBAL::Storage<float, float>* tu3;
    LMDBAL::Storage<uint16_t, double>* tu4;
    LMDBAL::Storage<float, int64_t>* tu5;
};

LMDBAL::Base* DuplicatesTest::db = nullptr;

TEST_F(DuplicatesTest, Flags) {
    uint32_t tu1Flags = getTU1Flags();
    uint32_t tu2Flags = getTU2Flags();
    uint32_t tu3Flags = getTU3Flags();
    uint32_t tu4Flags = getTU4Flags();
    uint32_t tu5Flags = getTU5Flags();

    EXPECT_TRUE(tu1Flags & MDB_INTEGERKEY);
    EXPECT_TRUE(tu1Flags & MDB_DUPSORT);
    EXPECT_TRUE(tu1Flags & MDB_DUPFIXED);
    EXPECT_FALSE(tu1Flags & MDB_INTEGERDUP);

    EXPECT_FALSE(tu2Flags & MDB_INTEGERKEY);
    EXPECT_TRUE(tu2Flags & MDB_DUPSORT);
    EXPECT_TRUE(tu2Flags & MDB_DUPFIXED);
    EXPECT_FALSE(tu2Flags & MDB_INTEGERDUP);

    EXPECT_FALSE(tu3Flags & MDB_INTEGERKEY);
    EXPECT_TRUE(tu3Flags & MDB_DUPSORT);
    EXPECT_TRUE(tu3Flags & MDB_DUPFIXED);
    EXPECT_FALSE(tu3Flags & MDB_INTEGERDUP);

    EXPECT_TRUE(tu4Flags & MDB_INTEGERKEY);
    EXPECT_TRUE(tu4Flags & MDB_DUPSORT);
    EXPECT_TRUE(tu4Flags & MDB_DUPFIXED);
    EXPECT_FALSE(tu4Flags & MDB_INTEGERDUP);

    EXPECT_FALSE(tu5Flags & MDB_INTEGERKEY);
    EXPECT_TRUE(tu5Flags & MDB_DUPSORT);
    EXPECT_TRUE(tu5Flags & MDB_DUPFIXED);
    EXPECT_TRUE(tu5Flags & MDB_INTEGERDUP);
}

TEST_F(DuplicatesTest, Adding) {
    tu1->addRecord(1, 1);
    tu1->addRecord(2, 2);
    tu1->addRecord(2, 1);
    tu1->addRecord(1, 2);
    EXPECT_THROW(tu1->addRecord(1, 1), LMDBAL::Exist);
    EXPECT_THROW(tu1->addRecord(1, 2), LMDBAL::Exist);
    EXPECT_THROW(tu1->addRecord(2, 2), LMDBAL::Exist);

    EXPECT_EQ(tu1->count(), 4);
    EXPECT_EQ(tu1->getRecord(1), 1);
    EXPECT_EQ(tu1->getRecord(2), 1);

    tu2->addRecord("brass boulers", -54);
    tu2->addRecord("grief ", 61);
    tu2->addRecord("grief ", 19);
    tu2->addRecord("grief ", 32);
    tu2->addRecord("miracles of a lunch", 44);
    tu2->addRecord("miracles of a lunch", 102);
    tu2->addRecord("miracles of a lunch", -72);

    EXPECT_THROW(tu2->addRecord("grief ", 19), LMDBAL::Exist);
    EXPECT_THROW(tu2->addRecord("brass boulers", -54), LMDBAL::Exist);
    EXPECT_EQ(tu2->count(), 7);
    EXPECT_EQ(tu2->getRecord("grief "), 19);
    EXPECT_EQ(tu2->getRecord("miracles of a lunch"), 44);       //apparently ints are compared as uints

    tu3->addRecord(7.2, 697);
    tu3->addRecord(5119, -998.53);
    tu3->addRecord(7.2001, 4);
    tu3->addRecord(7.2, -113);
    tu3->addRecord(7.2, -53.5478);
    float tu3ds = 0.432924;
    tu3->addRecord(5119, tu3ds);

    EXPECT_THROW(tu3->addRecord(5119, -998.53), LMDBAL::Exist);
    EXPECT_THROW(tu3->addRecord(7.2001, 4), LMDBAL::Exist);
    tu3->addRecord(7.20001, 4.00000001);        //not sure how exactly, but it works

    EXPECT_EQ(tu3->count(), 7);

    std::set<float> res72({-113, -53.5478, 697, 4, 4.00000001});
    EXPECT_EQ(res72.count(tu3->getRecord(7.2)), 1);

    float tu3dd = tu3->getRecord(5119);
    EXPECT_TRUE(tu3ds == tu3dd);
    EXPECT_EQ(tu3ds, tu3dd);

    tu4->addRecord(327, 463.28348);
    tu4->addRecord(327, 79.624923);
    tu4->addRecord(172, 0.00001);
    tu4->addRecord(172, 0.00000001);
    EXPECT_THROW(tu4->addRecord(172, 0.00000001), LMDBAL::Exist);
    EXPECT_THROW(tu4->addRecord(172, 0.00001), LMDBAL::Exist);
    EXPECT_THROW(tu4->addRecord(327, 79.624923), LMDBAL::Exist);

    EXPECT_EQ(tu4->count(), 4);

    std::set<double> res327({463.28348, 79.624923});
    std::set<double> res172({0.00001, 0.00000001});
    EXPECT_EQ(res172.count(tu4->getRecord(172)), 1);
    EXPECT_EQ(res327.count(tu4->getRecord(327)), 1);

    tu5->addRecord(-84.7, 45656753);
    EXPECT_THROW(tu5->addRecord(-84.7, 45656753), LMDBAL::Exist);
    tu5->addRecord(-84.7, 45656754);
    int64_t intMax = std::numeric_limits<int32_t>::max();
    int64_t intMin = std::numeric_limits<int32_t>::min();
    int64_t longMax = std::numeric_limits<int64_t>::max();
    int64_t longMin = std::numeric_limits<int64_t>::min();

    tu5->addRecord(52.87, intMax);
    EXPECT_THROW(tu5->addRecord(52.87, intMax), LMDBAL::Exist);
    tu5->addRecord(52.87, intMin);
    EXPECT_THROW(tu5->addRecord(52.87, intMin), LMDBAL::Exist);
    tu5->addRecord(52.87, longMax);
    EXPECT_THROW(tu5->addRecord(52.87, longMax), LMDBAL::Exist);
    tu5->addRecord(52.87, longMin);
    EXPECT_THROW(tu5->addRecord(52.87, longMin), LMDBAL::Exist);

    EXPECT_EQ(tu5->count(), 6);
    EXPECT_EQ(tu5->getRecord(-84.7), 45656753);
    EXPECT_EQ(tu5->getRecord(52.87), intMax);
}

TEST_F(DuplicatesTest, Forcing) {
    LMDBAL::SizeType tu1Size = tu1->count();
    tu1->addRecord(-56, 71);
    tu1->addRecord(-56, 274);
    tu1->addRecord(-56, 732);

    std::set<uint16_t> res56({71, 274, 732});
    EXPECT_EQ(tu1->count(), tu1Size += 3);
    EXPECT_TRUE(tu1->forceRecord(-56, 322));
    res56.insert(322);
    EXPECT_EQ(tu1->count(), tu1Size += 1);
    EXPECT_EQ(res56.count(tu1->getRecord(-56)), 1);

    res56.insert(14);
    EXPECT_TRUE(tu1->forceRecord(-56, 14));
    EXPECT_EQ(tu1->count(), tu1Size += 1);
    EXPECT_EQ(res56.count(tu1->getRecord(-56)), 1);

    EXPECT_FALSE(tu1->forceRecord(-56, 274));
    EXPECT_EQ(tu1->count(), tu1Size);

    LMDBAL::SizeType tu2Size = tu2->count();
    tu2->addRecord("printable", -2);
    tu2->addRecord("printable", 4);
    EXPECT_EQ(tu2->count(), tu2Size += 2);
    EXPECT_TRUE(tu2->forceRecord("printable", 18));
    EXPECT_EQ(tu2->count(), tu2Size += 1);
    EXPECT_EQ(tu2->getRecord("printable"), 4);
    EXPECT_TRUE(tu2->forceRecord("printable", 3));
    EXPECT_EQ(tu2->count(), tu2Size += 1);
    EXPECT_EQ(tu2->getRecord("printable"), 3);
    EXPECT_FALSE(tu2->forceRecord("printable", 4));
    EXPECT_EQ(tu2->count(), tu2Size);

    LMDBAL::SizeType tu3Size = tu3->count();
    tu3->addRecord(17.3, 93.21);
    tu3->addRecord(17.3, 6.6);
    tu3->addRecord(17.3, 105.1);
    std::set<float> res17({93.21, 6.6, 105.1});

    EXPECT_EQ(tu3->count(), tu3Size += 3);
    EXPECT_TRUE(tu3->forceRecord(17.3, 74.9));
    res17.insert(74.9);
    EXPECT_EQ(tu3->count(), tu3Size += 1);
    EXPECT_EQ(res17.count(tu3->getRecord(17.3)), 1);

    EXPECT_TRUE(tu3->forceRecord(17.3, 5.1));
    res17.insert(5.1);
    EXPECT_EQ(tu3->count(), tu3Size += 1);
    EXPECT_EQ(res17.count(tu3->getRecord(17.3)), 1);

    EXPECT_FALSE(tu3->forceRecord(17.3, 93.21));
    EXPECT_EQ(tu3->count(), tu3Size);

    LMDBAL::SizeType tu4Size = tu4->count();
    tu4->addRecord(84, -359.109);
    tu4->addRecord(84, 2879.654);
    std::set<double> res84({-359.109, 2879.654});

    EXPECT_EQ(tu4->count(), tu4Size += 2);
    EXPECT_TRUE(tu4->forceRecord(84, 72.9));
    res84.insert(72.9);
    EXPECT_EQ(tu4->count(), tu4Size += 1);
    EXPECT_EQ(res84.count(tu4->getRecord(84)), 1);

    EXPECT_TRUE(tu4->forceRecord(84, 2679.5));
    res84.insert(2679.5);
    EXPECT_EQ(tu4->count(), tu4Size += 1);
    EXPECT_EQ(res84.count(tu4->getRecord(84)), 1);

    EXPECT_FALSE(tu4->forceRecord(84, -359.109));
    EXPECT_EQ(tu4->count(), tu4Size);

    LMDBAL::SizeType tu5Size = tu5->count();
    tu5->addRecord(0.45, -85645);
    tu5->addRecord(0.45, 10573);
    tu5->addRecord(0.45, 573);
    tu5->addRecord(0.45, 73285);
    EXPECT_EQ(tu5->count(), tu5Size += 4);
    EXPECT_TRUE(tu5->forceRecord(0.45, -473));
    EXPECT_EQ(tu5->count(), tu5Size += 1);
    EXPECT_EQ(tu5->getRecord(0.45), 573);
    EXPECT_TRUE(tu5->forceRecord(0.45, 394));
    EXPECT_EQ(tu5->count(), tu5Size += 1);
    EXPECT_EQ(tu5->getRecord(0.45), 394);
    EXPECT_FALSE(tu5->forceRecord(0.45, 10573));
    EXPECT_EQ(tu5->count(), tu5Size);
}

TEST_F(DuplicatesTest, Changing) {
    LMDBAL::SizeType tu1Size = tu1->count();
    EXPECT_THROW(tu1->changeRecord(-31, 53), LMDBAL::NotFound);
    EXPECT_EQ(tu1->count(), tu1Size);
    tu1->addRecord(-31, 53);
    EXPECT_EQ(tu1->count(), tu1Size += 1);
    EXPECT_EQ(tu1->getRecord(-31), 53);
    tu1->changeRecord(-31, 53);         //should just do nothing usefull, but work normally
    EXPECT_EQ(tu1->getRecord(-31), 53);
    tu1->changeRecord(-31, 19);
    EXPECT_EQ(tu1->count(), tu1Size);
    EXPECT_EQ(tu1->getRecord(-31), 19);
    tu1->addRecord(-31, 60);
    EXPECT_EQ(tu1->count(), tu1Size += 1);
    EXPECT_EQ(tu1->getRecord(-31), 19);
    tu1->changeRecord(-31, 16);
    EXPECT_EQ(tu1->count(), tu1Size);
    EXPECT_EQ(tu1->getRecord(-31), 16);
    tu1->changeRecord(-31, 203);
    EXPECT_EQ(tu1->count(), tu1Size);
    EXPECT_EQ(tu1->getRecord(-31), 60);
    EXPECT_THROW(tu1->changeRecord(-31, 203), LMDBAL::Exist);

    LMDBAL::SizeType tu2Size = tu2->count();
    EXPECT_THROW(tu2->changeRecord("jeremy spins", -5), LMDBAL::NotFound);
    EXPECT_EQ(tu2->count(), tu2Size);
    tu2->addRecord("jeremy spins", -5);
    EXPECT_EQ(tu2->count(), tu2Size += 1);
    EXPECT_EQ(tu2->getRecord("jeremy spins"), -5);
    tu2->changeRecord("jeremy spins", -5);         //should just do nothing usefull, but work normally
    EXPECT_EQ(tu2->getRecord("jeremy spins"), -5);
    tu2->changeRecord("jeremy spins", 11);
    EXPECT_EQ(tu2->count(), tu2Size);
    EXPECT_EQ(tu2->getRecord("jeremy spins"), 11);
    tu2->addRecord("jeremy spins", 24);
    EXPECT_EQ(tu2->count(), tu2Size += 1);
    EXPECT_EQ(tu2->getRecord("jeremy spins"), 11);
    tu2->changeRecord("jeremy spins", 4);
    EXPECT_EQ(tu2->count(), tu2Size);
    EXPECT_EQ(tu2->getRecord("jeremy spins"), 4);
    tu2->changeRecord("jeremy spins", -7);
    EXPECT_EQ(tu2->count(), tu2Size);
    EXPECT_EQ(tu2->getRecord("jeremy spins"), 24);  //cuz it's compared as usigned down there
    EXPECT_THROW(tu2->changeRecord("jeremy spins", -7), LMDBAL::Exist);

    LMDBAL::SizeType tu3Size = tu3->count();
    std::set<float> res26;
    EXPECT_THROW(tu3->changeRecord(26.7, 68.22), LMDBAL::NotFound);
    EXPECT_EQ(tu3->count(), tu3Size);
    tu3->addRecord(26.7, 68.22);
    EXPECT_EQ(tu3->count(), tu3Size += 1);
    EXPECT_EQ(tu3->getRecord(26.7), 68.22f);
    tu3->changeRecord(26.7, 68.22);         //should just do nothing usefull, but work normally
    EXPECT_EQ(tu3->getRecord(26.7), 68.22f);
    tu3->changeRecord(26.7, 23.18);
    res26.insert(23.18);
    EXPECT_EQ(tu3->count(), tu3Size);
    EXPECT_EQ(tu3->getRecord(26.7), 23.18f);
    tu3->addRecord(26.7, 22.16);
    res26.insert(22.16);
    EXPECT_EQ(tu3->count(), tu3Size += 1);
    EXPECT_EQ(res26.count(tu3->getRecord(26.7)), 1);
    tu3->changeRecord(26.7, 21.7);
    EXPECT_EQ(tu3->count(), tu3Size);
    EXPECT_EQ(tu3->getRecord(26.7), 21.7f);
    tu3->changeRecord(26.7, 54.33);
    EXPECT_EQ(tu3->count(), tu3Size);
    EXPECT_EQ(tu3->getRecord(26.7), 22.16f);
    EXPECT_THROW(tu3->changeRecord(26.7, 54.33), LMDBAL::Exist);

    LMDBAL::SizeType tu4Size = tu4->count();
    EXPECT_THROW(tu4->changeRecord(852, 6795.349), LMDBAL::NotFound);
    EXPECT_EQ(tu4->count(), tu4Size);
    tu4->addRecord(852, 6795.349);
    EXPECT_EQ(tu4->count(), tu4Size += 1);
    EXPECT_EQ(tu4->getRecord(852), 6795.349);
    tu4->changeRecord(852, 6795.349);         //should just do nothing usefull, but work normally
    EXPECT_EQ(tu4->getRecord(852), 6795.349);
    tu4->changeRecord(852, 13.54);
    std::set<double> res852({13.54});
    EXPECT_EQ(tu4->count(), tu4Size);
    EXPECT_EQ(tu4->getRecord(852), 13.54);
    tu4->addRecord(852, 213.85);
    res852.insert(213.85);
    EXPECT_EQ(tu4->count(), tu4Size += 1);
    EXPECT_EQ(res852.count(tu4->getRecord(852)), 1);
    tu4->changeRecord(852, 236.21);
    res852.insert(236.21);
    EXPECT_EQ(tu4->count(), tu4Size);
    EXPECT_EQ(tu4->getRecord(852), 236.21);
    tu4->changeRecord(852, 46324.1135);
    res852.insert(46324.1135);
    EXPECT_EQ(tu4->count(), tu4Size);
    EXPECT_EQ(res852.count(tu4->getRecord(852)), 1);
    EXPECT_THROW(tu4->changeRecord(852, 46324.1135), LMDBAL::Exist);
}

TEST_F(DuplicatesTest, GettingAllRecords) {
    LMDBAL::Transaction txn = db->beginReadOnlyTransaction();
    bool cycle;
    LMDBAL::SizeType iterations;

    std::map<int16_t, uint16_t> m1;
    std::set<int16_t> k1;
    LMDBAL::Cursor<int16_t, uint16_t> c1 = tu1->createCursor();
    tu1->readAll(m1, txn);
    c1.open(txn);

    cycle = false;
    iterations = 0;
    do {
        try {
            std::pair<int16_t, uint16_t> pair = c1.next();
            cycle = true;
            std::pair<std::set<int16_t>::const_iterator, bool> probe = k1.insert(pair.first);
            if (probe.second) {
                uint16_t valueAll = m1.at(pair.first);
                uint16_t valueGet;
                EXPECT_NO_THROW(tu1->getRecord(pair.first, valueGet, txn));
                EXPECT_EQ(valueAll, valueGet);
            }
            ++iterations;
        } catch (const LMDBAL::NotFound& e) {
            cycle = false;
        }
    } while (cycle);

    EXPECT_EQ(iterations, tu1->count(txn));
    EXPECT_EQ(k1.size(), m1.size());
    EXPECT_NE(iterations, 0);
    EXPECT_NE(k1.size(), 0);
    c1.drop();


    std::map<std::string, int8_t> m2;
    std::set<std::string> k2;
    LMDBAL::Cursor<std::string, int8_t> c2 = tu2->createCursor();
    tu2->readAll(m2, txn);
    c2.open(txn);

    cycle = false;
    iterations = 0;
    do {
        try {
            std::pair<std::string, int8_t> pair = c2.next();
            cycle = true;
            std::pair<std::set<std::string>::const_iterator, bool> probe = k2.insert(pair.first);
            if (probe.second) {
                int8_t valueAll = m2.at(pair.first);
                int8_t valueGet;
                EXPECT_NO_THROW(tu2->getRecord(pair.first, valueGet, txn));
                EXPECT_EQ(valueAll, valueGet);
            }
            ++iterations;
        } catch (const LMDBAL::NotFound& e) {
            cycle = false;
        }
    } while (cycle);

    EXPECT_EQ(iterations, tu2->count(txn));
    EXPECT_EQ(k2.size(), m2.size());
    EXPECT_NE(iterations, 0);
    EXPECT_NE(k2.size(), 0);
    c2.drop();


    std::map<float, float> m3;
    std::set<float> k3;
    LMDBAL::Cursor<float, float> c3 = tu3->createCursor();
    tu3->readAll(m3, txn);
    c3.open(txn);

    cycle = false;
    iterations = 0;
    do {
        try {
            std::pair<float, float> pair = c3.next();
            cycle = true;
            std::pair<std::set<float>::const_iterator, bool> probe = k3.insert(pair.first);
            if (probe.second) {
                float valueAll = m3.at(pair.first);
                float valueGet;
                EXPECT_NO_THROW(tu3->getRecord(pair.first, valueGet, txn));
                EXPECT_EQ(valueAll, valueGet);
            }
            ++iterations;
        } catch (const LMDBAL::NotFound& e) {
            cycle = false;
        }
    } while (cycle);

    EXPECT_EQ(iterations, tu3->count(txn));
    EXPECT_EQ(k3.size(), m3.size());
    EXPECT_NE(iterations, 0);
    EXPECT_NE(k3.size(), 0);
    c3.drop();


    std::map<uint16_t, double> m4;
    std::set<uint16_t> k4;
    LMDBAL::Cursor<uint16_t, double> c4 = tu4->createCursor();
    tu4->readAll(m4, txn);
    c4.open(txn);

    cycle = false;
    iterations = 0;
    do {
        try {
            std::pair<uint16_t, double> pair = c4.next();
            cycle = true;
            std::pair<std::set<uint16_t>::const_iterator, bool> probe = k4.insert(pair.first);
            if (probe.second) {
                double valueAll = m4.at(pair.first);
                double valueGet;
                EXPECT_NO_THROW(tu4->getRecord(pair.first, valueGet, txn));
                EXPECT_EQ(valueAll, valueGet);
            }
            ++iterations;
        } catch (const LMDBAL::NotFound& e) {
            cycle = false;
        }
    } while (cycle);
    c4.drop();

    EXPECT_EQ(iterations, tu4->count(txn));
    EXPECT_EQ(k4.size(), m4.size());
    EXPECT_NE(iterations, 0);
    EXPECT_NE(k4.size(), 0);

    txn.terminate();
}