#pragma once

#include "Zeit.h"
#include "HashMap.h"

namespace Framework
{
    enum class CacheCleanupStrategy
    {
        LONGEST_NOT_USED,
        OLDEST,
        RANDOM
    };

    template <typename T>
    struct CacheEntry
    {
        __int64 created;
        __int64 lastUsed;
        T value;
    };

    template<typename K, typename T>
    class CacheIterator
    {
    private:
        MapIterator<K, CacheEntry<T>> delegate;

    public:
        CacheIterator( MapIterator<K, CacheEntry<T>> delegate )
            : delegate( delegate )
        {}

        CacheIterator( const CacheIterator< K, T >& it )
        {
            delegate = it.delegate;
        }

        CacheIterator< K, T >& operator=( CacheIterator< K, T > r )
        {
            delegate = r.delegate;
            return *this;
        }

        bool hasNext()
        {
            return delegate.hasNext();
        }

        MapEntry<K, T> operator*()
        {
            return MapEntry<K, T>( delegate.operator->().getKey(), delegate.operator->().getValue().value );
        }

        CacheIterator< K, T > next()
        {
            return CacheIterator( delegate.next() );
        }

        operator bool()
        {
            return delegate;
        }

        operator MapEntry<K, T>()
        {
            return MapEntry<K, T>( delegate->getKey(), delegate->getValue().value );
        }

        CacheIterator< K, T >& operator++() //! prefix
        {
            ++delegate;
            return *this;
        }

        CacheIterator< K, T > operator++( int ) //! postfix
        {
            CacheIterator< K, T > temp( *this );
            ++delegate;
            return temp;
        }

        MapEntry< K, T > operator->()
        {
            return MapEntry<K, T>( delegate->getKey(), delegate->getValue().value );
        }

        void set( MapEntry<K, T> val )
        {
            time_t zeit = time( 0 );
            delegate.set( MapEntry<K, CacheEntry<T>>( val.getKey(), CacheEntry<T>{ zeit, zeit, val.getValue() } ) );
        }

        bool operator!=( CacheIterator< K, T >& r )
        {
            return delegate != r.delegate;
        }
    };

    template <typename K, typename T>
    class Cache : public virtual ReferenceCounter
    {
    private:
        HashMap<K, CacheEntry<T>>* cache;
        CacheCleanupStrategy cleanup;
        int maxSize;

        void removeOneElement()
        {
            switch( cleanup )
            {
            case CacheCleanupStrategy::LONGEST_NOT_USED:
            {
                K key;
                __int64 t = -1;
                for( MapEntry<K, CacheEntry<T>> e : *cache )
                {
                    if( t < 0 || t > e.getValue().lastUsed )
                    {
                        t = e.getValue().lastUsed;
                        key = e.getKey();
                    }
                }
                if( t >= 0 )
                    cache->remove( key );
                break;
            }
            case CacheCleanupStrategy::OLDEST:
            {
                K key;
                __int64 t = -1;
                for( MapEntry<K, CacheEntry<T>> e : *cache )
                {
                    if( t < 0 || t > e.getValue().created )
                    {
                        t = e.getValue().created;
                        key = e.getKey();
                    }
                }
                if( t >= 0 )
                    cache->remove( key );
                break;
            }
            case CacheCleanupStrategy::RANDOM:
            {
                int size = cache->getSize();
                int index = (int)(((double)rand() / RAND_MAX) * size);
                auto it = cache->begin();
                for( int i = 0; i < index; i++ )
                    ++it;
                cache->remove( it.operator->().getKey() );
                break;
            }
            }
        }

    public:
        Cache( int size, std::function<int( K )> hash, CacheCleanupStrategy cleanup )
            : ReferenceCounter(),
            cleanup( cleanup ),
            maxSize( size )
        {
            cache = new HashMap<K, CacheEntry<T>>( size, hash );
        }

        ~Cache()
        {
            cache->release();
        }

        void put( K key, T value )
        {
            time_t zeit = time( 0 );
            if( cache->getSize() >= maxSize && !cache->has( key ) )
                removeOneElement();
            cache->put( key, { zeit, zeit, value } );
        }

        void remove( K key )
        {
            cache->remove( key );
        }

        T get( K key ) const
        {
            CacheEntry<T> entry = cache->get( key );
            entry.lastUsed = time( 0 );
            cache->put( key, entry );
            return entry.value;
        }

        bool has( K key ) const
        {
            return cache->has( key );
        }

        int getCurrentSize() const
        {
            return cache->getSize();
        }

        int getMaxSize() const
        {
            return maxSize;
        }

        void reset() const
        {
            cache->leeren();
        }

        CacheIterator<K, T> begin()
        {
            return CacheIterator<K, T>( cache->begin() );
        }

        CacheIterator<K, T> end()
        {
            return CacheIterator<K, T>( cache->end() );
        }
    };
}