/*
   Copyright (c) 2019 by The ThreadDB Project
   All Rights Reserved.

   ThreadDB undergoes the BSD License 2.0. You should have received a copy along with this program; if not, write to the ThreadDB Project.
   To obtain a full unlimited version contact thethreaddbproject(at)gmail.com.
   
   stlstdsample.cpp - Samples to explain threaddb STL std:: container support.
*/
   
#include <threaddbSTLWrapper.h>
#include <fstream>
#include <ios>
#include <random>
#include <iostream>
#include <list>
#include <sstream>
#include <map>

namespace tdb
{
    template<class T>
    class RandomGenerator
    {
    public:
        RandomGenerator(int Seed_p) :
            m_MT19937(Seed_p)
        {
        }

        T Rand(T Min_p, T Max_p)
        {
            std::uniform_int_distribution<T> distribution(Min_p, Max_p);
            return distribution(m_MT19937);
        }

    private:
        std::mt19937 m_MT19937;
    };
}

int main(int argc, char* argv[])
{
    std::string tmpFolderKey;
    std::string tmpFolderValue;

    if (argc < 3)
    {
        std::cout << "The testrun of threadDB requires two folder names to store the key and value temporary database files separately" << std::endl;
        exit(1);
    }

    tmpFolderKey = argv[1];
    tmpFolderKey.erase(tmpFolderKey.find_last_not_of("\\/") + 1);

    tmpFolderValue = argv[2];
    tmpFolderValue.erase(tmpFolderValue.find_last_not_of("\\/") + 1);

    tdb::database keysDB(1024 * 1024, 10); // for the key database use a package record size of 1 Mb to minimize disk seek operations
    tdb::database valuesDB(1024 * 1024, 10, tmpFolderValue.c_str()); // for the data items we allow also to flush the package headers

    std::cout << "Creating 8 threads" << std::endl;
    keysDB.NewThread(tmpFolderKey.c_str());
    keysDB.NewThread(tmpFolderKey.c_str());

    valuesDB.NewThread(tmpFolderValue.c_str());
    valuesDB.NewThread(tmpFolderValue.c_str());
    valuesDB.NewThread(tmpFolderValue.c_str());
    valuesDB.NewThread(tmpFolderValue.c_str());

    tdb::RandomGenerator<size_t> randomGenerator(1);
    size_t numberOfGeneratedEntries = 1000000;

    {
        std::string myString = "This is the new horizon approaching.";
        for (size_t iter = 0; iter < 10; ++iter)
            myString += myString;

        // [Example3.1 example]
        tdb::pool<std::string> variableKeysPool(keysDB);
        tdb::pool<std::string> variableValuesPool(valuesDB);

        std::map<tdb::key<tdb::ritem<std::string>>, tdb::ritem<std::string>> map;
        // [Example3.1 example]

        std::cout << "Start filling variable random access items" << std::endl;

        for (size_t idx(0); idx < numberOfGeneratedEntries; ++idx)
        {
            const size_t keyVal = randomGenerator.Rand(0, numberOfGeneratedEntries);

            std::stringstream os;
            os << keyVal;

            // [Example3.2 example]
            tdb::key<tdb::ritem<std::string>> key(os.str(), variableKeysPool);
            tdb::ritem<std::string> value(myString, variableValuesPool);

            map.insert(std::make_pair(key, value));
            // [Example3.2 example]

            if ((idx % 1000) == 0)
            {
                std::cout << "Entries processed: " << idx << std::endl;
            }
        }

        keysDB.Synchronize();
        valuesDB.Synchronize();

        std::cout << "Start reading variable random access items" << std::endl;

        for (size_t idx(0); idx < 3; ++idx)
        {
            size_t entryNr = 0;
            for (auto iter(map.begin()); iter != map.end(); ++iter, ++entryNr)
            {
                if (entryNr % 10000 == 0)
                {
                    std::ostringstream os;
                    // [Example3.3 example]
                    os << "Key : " << *iter->first.get() << " Value : " << *iter->second.get();
                    // [Example3.3 example]
                    fprintf(stdout, "%s\n", os.str().c_str()); fflush(stdout);
                }
            }
        }
    }

    {
        {
            // [Example4.1 example]
            tdb::pool<size_t> fixedKeysPool(keysDB,10);
            tdb::pool<size_t> fixedValuesPool(valuesDB,10);

            std::map<tdb::key<tdb::sitem<size_t>>, tdb::sitem<size_t>> map;
            // [Example4.1 example]

            std::cout << "Start filling fixed serial access items" << std::endl;

            for (size_t idx(0); idx < numberOfGeneratedEntries; ++idx)
            {
                const size_t keyVal = randomGenerator.Rand(0, numberOfGeneratedEntries);
                
                // [Example4.2 example]
                tdb::key<tdb::sitem<size_t>> key(keyVal, fixedKeysPool);
                tdb::sitem<size_t> value(idx, fixedValuesPool);

                map.insert(std::make_pair(key, value));
                // [Example4.2 example]

                if ((idx % 1000) == 0)
                {
                    std::cout << "Entries processed: " << idx << std::endl;
                }

            }

            std::cout << "Start reading fixed stream access items" << std::endl;

            for (size_t idx(0); idx < 3; ++idx)
            {
                for (auto iter(map.begin()); iter != map.end(); ++iter)
                {
                    std::ostringstream os;
                    os << "Key : " << *iter->first.get() << " Value : " << *iter->second.get();
                    fprintf(stdout, "%s\n", os.str().c_str()); fflush(stdout);
                }
            }

            {
                std::cout << "Indexed map access" << std::endl;

                std::map<tdb::key<tdb::sitem<size_t>>, tdb::sitem<size_t>> map;
                const tdb::key<tdb::sitem<size_t>> key(4711, fixedKeysPool);
                map[key] = tdb::sitem<size_t>(815, fixedValuesPool);

                std::ostringstream os;
                os << "Key : " << *key.get() << " Value : " << *map[key].get();

                map.erase(key);
            }
        }
    }

    {
        std::cout << "Vector data structure test" << std::endl;

        tdb::pool<size_t> fixedValuesPool(valuesDB, 10);

        std::vector< tdb::sitem <size_t> > vector;
        for (size_t idx(0); idx < numberOfGeneratedEntries; ++idx)
        {
            const size_t keyVal = randomGenerator.Rand(0, numberOfGeneratedEntries);
            vector.push_back(tdb::sitem<size_t>(keyVal, fixedValuesPool));

            if ((idx % 10000) == 0)
            {
                std::cout << "Entries processed: " << idx << std::endl;
            }
        }

        for (auto iter(vector.begin()); iter != vector.end(); ++iter)
        {
            std::ostringstream os;
            os << " Vector value : " << *iter->get();
            fprintf(stdout, "%s\n", os.str().c_str()); fflush(stdout);
        }

        vector.erase(vector.begin(),vector.end());
    }

    {
        std::cout << "List data structure test" << std::endl;

        tdb::pool<size_t> fixedValuesPool(valuesDB, 10);

        std::list< tdb::sitem <size_t> > list;
        size_t numberOfEntries = 100000;
        for (size_t idx(0); idx < numberOfEntries; ++idx)
        {
            const size_t keyVal = randomGenerator.Rand(0, numberOfGeneratedEntries);
            list.push_back(tdb::sitem<size_t>(keyVal, fixedValuesPool));
            list.push_front(tdb::sitem<size_t>(keyVal, fixedValuesPool));
            if ((idx % 10000) == 0)
            {
                std::cout << "Entries processed: " << idx << std::endl;
            }
        }

        for (auto iter(list.begin()); iter != list.end(); ++iter)
        {
            std::ostringstream os;
            os << " List value : " << *iter->get();
            fprintf(stdout, "%s\n", os.str().c_str()); fflush(stdout);
        }

        list.erase(list.begin(), list.end());
    }
}
