#include <iostream>
#include <chrono>
#include <map>
#include <string>
#include <random>
#include <algorithm>
#include <unordered_set>
#include <set>

template <class T, class ValueType>
class StrongType
{
public:
    inline explicit operator ValueType() const { return _value;}
    inline bool operator == (const StrongType &other) const
    {
        return _value == other._value;
    }
    inline bool operator != (const StrongType &other) const
    {
        return _value != other._value;
    }
    inline bool operator < (const StrongType &other) const
    {
        return _value < other._value;;
    }
    inline bool operator > (const StrongType &other) const
    {
        return _value > other._value;
    }
    inline bool operator <= (const StrongType &other) const
    {
        return _value <= other._value;
    }
    inline bool operator >= (const StrongType &other) const
    {
        return _value >= other._value;
    }

protected:
    explicit StrongType(ValueType value):_value(value) {}

private:
    ValueType _value;
};

template <class T, class ValueType = int>
class StringCache
{
public:
    static_assert(std::is_integral<ValueType>::value, "not integral type");

    class Type: public StrongType<T,ValueType>
    {
        friend class StringCache;
    public:
        explicit operator bool() const { return static_cast<ValueType>(*this)!=0; }

    private:
        explicit Type(ValueType value):StrongType<T,ValueType>(value){}
    };

    static Type get(const std::string &value)
    {
        return Type(_values.insert(std::make_pair(value, _values.size() + 1)).first->second);
    }

    static Type find(const std::string &value)
    {
        std::map<std::string,int>::const_iterator it =_values.find(value);
        if(it == _values.end())
            return Type(0);
        else
            return Type(it->second);
    }

    static const std::string& to_string(const Type &type)
    {
        static const std::string empty;
        if(static_cast<ValueType>(type)>=_values.size())
            return empty;
        for(const auto &it:_values)
            if(it.second == static_cast<ValueType>(type))
                return it.first;
        return empty;
    }

private:
    static std::map<std::string,int> _values;
};

template <class T, class ValueType>
std::map<std::string,int> StringCache<T,ValueType>::_values;



class EventType:public StringCache<EventType>{};

class Script
{
public:
    Script(int val):_value(val)
    {

    }
    int execute() const
    {
        return _value;
    }
private:
    int _value;
};

class Object
{
public:
    int execute(const std::string &id) const
    {
        auto it = _events.find(id);
        if(it!=_events.end())
        {
            return it->second.execute();
        }
        return 0;
    }
    void addevent(const std::string &event, const Script &script)
    {
        _events.insert(std::make_pair(event, script));
    }
private:
    std::map<std::string, Script> _events;
};

class FastObject
{
public:
    int execute(EventType::Type id) const
    {
        auto it = _events.find(id);
        if(it!=_events.end())
        {
            return it->second.execute();
        }
        return 0;
    }
    void addevent(EventType::Type event, const Script &script)
    {
        _events.insert(std::make_pair(event, script));
    }
private:
    std::map<EventType::Type, Script> _events;
};

using TEventId = const std::string*;
class TEventStorer: public std::unordered_set<std::string> {
public:
    TEventId Add(const std::string& event) {
        return &(*this->insert(event).first);
    }
    TEventId Find(const std::string& event) {
        auto it = this->find(event);
        return it != this->end() ? &(*it) : nullptr;
    }
};

class SuperFastObject
{
public:
    int execute(TEventId event) const
    {
        auto it = _events.find(event);
        if(it!=_events.end())
        {
            return it->second.execute();
        }
        return 0;
    }
    void addevent(TEventId event, Script script)
    {
        _events.insert(make_pair(event, script));
    }
private:
    std::map<TEventId, Script> _events;
};

std::vector<std::string> eventIds= {
    "event00",
    "event01",
    "event02",
    "event03",
    "event04",
    "event05",
    "event06",
    "event07",
    "event08",
    "event09",
    "event00",
    "event11",
    "event12",
    "event13",
    "event14",
    "event15",
    "event16",
    "event17",
    "event18",
    "event19",
    "event20",
    "event21",
    "event22",
    "event23",
    "event24",
    "event25"
};

int main(int argc, const char * argv[])
{
    std::random_device rd;
    std::default_random_engine engine(rd());
    std::vector<int> ids{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19};

    std::vector<Object> objects;
    std::vector<FastObject> fast_objects;
    std::vector<SuperFastObject> super_fast_objects;
    TEventStorer event_storer;

    const static int max_objects = 1000;
    const static int iter_count = 1000;
    const static int repeat_count = 1;

    objects.reserve(max_objects);
    fast_objects.reserve(max_objects);
    super_fast_objects.reserve(max_objects);

    for(int i=0;i<max_objects;++i)
    {
        std::shuffle(ids.begin(), ids.end(), engine);
        Object obj1;
        FastObject obj2;
        SuperFastObject obj3;
        for(int j=0;j<10;++j) //add first 10 elements not all object has all events
        {
            obj1.addevent(eventIds[ids[j]], Script(j));
            obj2.addevent(EventType::get(eventIds[ids[j]]), Script(j));
            obj3.addevent(event_storer.Add(eventIds[ids[j]]), Script(j));
        }
        objects.push_back(obj1);
        fast_objects.push_back(obj2);
        super_fast_objects.push_back(obj3);
    }

    std::vector<std::string> events;
    std::vector<EventType::Type> fast_events;
    std::vector<TEventId> super_fast_events;

    events.reserve(eventIds.size()*iter_count);

    for(int i=0;i<iter_count;++i)
        for(const auto &it:eventIds)
        {
            events.push_back(it);
            fast_events.push_back(EventType::find(it));
            super_fast_events.push_back(event_storer.Find(it));
        }


    int ret1 = 0;
    int ret2 = 0;
    int ret3 = 0;

    {
        std::chrono::high_resolution_clock::time_point t = std::chrono::high_resolution_clock::now();
        for(int i=0;i<repeat_count;++i)
        for(const auto &event:events)
        {
            for(const auto &object:objects)
            {
                ret1 += object.execute(event);
            }
        }
        auto duration = std::chrono::high_resolution_clock::now() - t;
        std::cout << std::chrono::duration_cast<std::chrono::milliseconds>(duration).count() << ":" << ret1 << std::endl;
    }

    {
        std::chrono::high_resolution_clock::time_point t = std::chrono::high_resolution_clock::now();
        for(int i=0;i<repeat_count;++i)
        for(const auto &event:fast_events)
        {
            if(event) //possible that no one has this id
                for(const auto &object:fast_objects)
                {
                    ret2 += object.execute(event);
                }
        }
        auto duration = std::chrono::high_resolution_clock::now() - t;
        std::cout << std::chrono::duration_cast<std::chrono::milliseconds>(duration).count() << ":" << ret2 << std::endl;
    }

    {
        std::chrono::high_resolution_clock::time_point t = std::chrono::high_resolution_clock::now();
        for(int i=0;i<repeat_count;++i)
        for(auto&& event:super_fast_events)
        {
            if(event) //possible that no one has this id
                for(auto&& object:super_fast_objects)
                {
                    ret3 += object.execute(event);
                }
        }
        auto duration = std::chrono::high_resolution_clock::now() - t;
        std::cout << std::chrono::duration_cast<std::chrono::milliseconds>(duration).count() << ":" << ret3 << std::endl;
    }

    return 0;
}
