#ifndef ICATALOGUE_H
#define ICATALOGUE_H

#include "model.h"

#include <set>
#include <map>

#include <wModel/vocabulary.h>
#include <utils/exception.h>

namespace M {
    class ICatalogue : public M::Model
    {
        Q_OBJECT
    protected:
        class AbstractIndex;
    public:
        ICatalogue(const W::Address p_address, QObject* parent = 0);
        ~ICatalogue();
        
        virtual uint64_t addElement(const W::Vocabulary& record);
        virtual void removeElement(uint64_t id);
        virtual W::Vocabulary* getElement(uint64_t id) = 0;
        virtual void modifyElement(uint64_t id, const W::Vocabulary& newValue) = 0;
        virtual uint64_t size() const = 0;
        virtual void clear();
        
        virtual void addIndex(const W::String& fieldName, W::Object::objectType fieldType);
        const std::set<uint64_t>& find(const W::String& indexName, const W::Object& value) const;
        std::set<uint64_t> find(const W::Vocabulary& value) const;
        
        M::Model::ModelType getType() const override;
        static const M::Model::ModelType type = catalogue;
        void set(const W::Object & value) override;
        void set(W::Object * value) override;
        
        W::Handler* subscribeMember;
        handler(subscribeMember);
        
        static bool match(const W::Vocabulary& value, const W::Vocabulary& filter);
        static const std::set<uint64_t> empty;
        
    signals:
        void countChange(uint64_t count);
        
    protected:
        virtual std::set<uint64_t> getAll() const = 0;
        void h_subscribe(const W::Event & ev) override;
        
        handler(get);
        handler(add);
        handler(update);
        
        typedef std::map<W::String, AbstractIndex*> IndexMap;
        IndexMap indexes;
        
    private:
        uint64_t lastIndex;
        std::map<uint64_t, M::Vocabulary*> activeChildren;
        
        void processAddElement(uint64_t id, const W::Vocabulary& value, SMap::const_iterator subscriberIterator, uint64_t socketId);
        uint64_t getInsertingNeighbour(const W::Vocabulary& params, const W::Vocabulary& record, uint64_t id, const std::set<uint64_t>& allowed = empty) const;
        
    protected:
        class AbstractIndex {
        public:
            AbstractIndex(W::Object::objectType vt): valueType(vt) {}
            virtual ~AbstractIndex() {}
            
            virtual const std::set<uint64_t>& find(const W::Object& value) const = 0;
            virtual void add(const W::Object& value, uint64_t id) = 0;
            virtual void remove(const W::Object & value, uint64_t id) = 0;
            virtual void clear() = 0;
            virtual W::Vector sort(const std::set<uint64_t>& set, bool ascending) = 0;
            virtual uint64_t getNext(uint64_t id, const W::Object& value) = 0;
            virtual uint64_t getPrev(uint64_t id, const W::Object& value) = 0;
            
            W::Object::objectType valueType;
            
        protected:
            class TypeError : public Utils::Exception {
            public:
                TypeError(const std::string& name, const std::string& method, W::Object::objectType myType, W::Object::objectType valueType);
                
                std::string getMessage() const;
                
            private:
                std::string name;
                std::string method;
                W::Object::objectType myType;
                W::Object::objectType valueType;
            };
        };
        
        template <class T> 
        class Index : public AbstractIndex {
        public:
            Index();
            ~Index();
            
            const std::set<uint64_t>& find(const W::Object& value) const override;
            void add(const W::Object & value, uint64_t id) override;
            void remove(const W::Object & value, uint64_t id) override;
            void clear() override;
            W::Vector sort(const std::set<uint64_t> & set, bool ascending) override;
            uint64_t getNext(uint64_t id, const W::Object& value) override;
            uint64_t getPrev(uint64_t id, const W::Object& value) override;
            
        private:
            typedef std::map<T, std::set<uint64_t>> Map;
            
            Map values;
            
        };
    };
    
    
    
    template<class T> 
    ICatalogue::Index<T>::Index():
        ICatalogue::AbstractIndex(T::type),
        values()
    {
    }

    template<class T> 
    ICatalogue::Index<T>::~Index()
    {
    }

    template<class T> 
    const std::set<uint64_t> & ICatalogue::Index<T>::find(const W::Object& value) const
    {
        if (value.getType() != valueType) {
            throw new TypeError("Unknown", "find", valueType, value.getType());             //todo replace that unknown stuff, find a way to provide index name
        }
            
        const T& val = static_cast<const T&>(value);
        typename std::map<T, std::set<uint64_t>>::const_iterator itr = values.find(val);
        
        if (itr == values.end()) {
            return ICatalogue::empty;
        } else {
            return itr->second;
        }
    }

    template<class T> 
    void ICatalogue::Index<T>::add(const W::Object& value, uint64_t id)
    {
        if (value.getType() != valueType) {
            throw new TypeError("Unknown", "add", valueType, value.getType());
        }
        
        const T& val = static_cast<const T&>(value);
        typename std::map<T, std::set<uint64_t>>::iterator itr = values.find(val);
        if (itr == values.end()) {
            itr = values.insert(std::make_pair(val, std::set<uint64_t>())).first;
        }
        itr->second.insert(id);
    }
    
    template<class T> 
    void ICatalogue::Index<T>::remove(const W::Object& value, uint64_t id) 
    {
        if (value.getType() != valueType) {
            throw new TypeError("Unknown", "remove", valueType, value.getType());
        }
        const T& val = static_cast<const T&>(value);
        typename std::map<T, std::set<uint64_t>>::iterator itr = values.find(val);
        if (itr != values.end()) {
            std::set<uint64_t>& set = itr->second;
            if (set.size() == 1) {
                values.erase(itr);
            } else {
                std::set<uint64_t>::const_iterator hint = set.find(id);
                set.erase(hint);
            }
        }
    }
    
    template<class T> 
    void ICatalogue::Index<T>::clear() 
    {
        values.clear();
    }
    
    template<class T> 
    W::Vector ICatalogue::Index<T>::sort(const std::set<uint64_t> & set, bool ascending)        //TODO this needs an optimization
    {
        W::Vector res;
        std::set<uint64_t>::const_iterator sEnd = set.end();
        uint64_t size = set.size();
        
        if (size == 0) {
            return res;
        } else if (size == 1) {
            res.push(W::Uint64(*(set.begin())));
            return res;
        }
        if (ascending) {
            typename std::map<T, std::set<uint64_t>>::const_iterator itr = values.begin();
            typename std::map<T, std::set<uint64_t>>::const_iterator end = values.end();
            
            for (; itr != end; ++itr) {
                if (size == res.size()) {
                    break;
                }
                const std::set<uint64_t>& chunk = itr->second;
                
                std::set<uint64_t>::const_iterator cItr = chunk.begin();
                std::set<uint64_t>::const_iterator cEnd = chunk.end();
                for (; cItr != cEnd; ++cItr) {
                    uint64_t id = *cItr;
                    if (set.find(id) != sEnd) {
                        res.push(W::Uint64(id));
                    }
                }
            }
        } else {
            typename std::map<T, std::set<uint64_t>>::reverse_iterator itr = values.rbegin();
            typename std::map<T, std::set<uint64_t>>::reverse_iterator end = values.rend();
            
            for (; itr != end; ++itr) {
                if (size == res.size()) {
                    break;
                }
                const std::set<uint64_t>& chunk = itr->second;
                
                std::set<uint64_t>::const_iterator cItr = chunk.begin();
                std::set<uint64_t>::const_iterator cEnd = chunk.end();
                for (; cItr != cEnd; ++cItr) {
                    uint64_t id = *cItr;
                    if (set.find(id) != sEnd) {
                        res.push(W::Uint64(id));
                    }
                }
            }
        }
        
        return res;
    }
    
    template<class T> 
    uint64_t ICatalogue::Index<T>::getNext(uint64_t id, const W::Object& value) 
    {
        if (value.getType() != valueType) {
            throw new TypeError("Unknown", "getNext", valueType, value.getType());
        }
        const T& val = static_cast<const T&>(value);
        typename std::map<T, std::set<uint64_t>>::iterator itr = values.find(val);
        if (itr == values.end()) {
            throw 2;    //this is not suppose to happen!
        }
        const std::set<uint64_t>& set = itr->second;
        std::set<uint64_t>::const_iterator sItr = set.find(id);
        if (sItr == set.end()) {
            throw 2;     //not suppose to happen!
        }
        ++sItr;
        if (sItr == set.end()) {
            ++itr;
            bool found = false;
            while (itr != values.end()) {
                if (itr->second.size() != 0) {
                    sItr = set.begin();
                    found = true;
                    break;
                }
                ++itr;
            }
            if (!found) {
                return 0;
            }
        }
        return *sItr;
    }
    
    template<class T> 
    uint64_t ICatalogue::Index<T>::getPrev(uint64_t id, const W::Object& value) 
    {
        if (value.getType() != valueType) {
            throw new TypeError("Unknown", "getPrev", valueType, value.getType());
        }
        const T& val = static_cast<const T&>(value);
        typename std::map<T, std::set<uint64_t>>::iterator itr = values.find(val);
        if (itr == values.end()) {
            throw 2;    //this is not suppose to happen!
        }
        const std::set<uint64_t>& set = itr->second;
        std::set<uint64_t>::const_iterator sItr = set.find(id);
        if (sItr == set.end()) {
            throw 2;     //not suppose to happen!
        }
        if (sItr == set.begin()) {
            bool found = false;
            while (itr != values.begin()) {
                --itr;
                if (itr->second.size() != 0) {
                    sItr = set.end();
                    --sItr;
                    break;
                }
            }
            if (!found) {
                return 0;
            }
        } else {
            --sItr;
        }
        return *sItr;
    }
}

#endif // ICATALOGUE_H