#pragma once
#include "common.hpp"
#include "test.hpp"
#include "stats.hpp"
#include <new>


template <class T> class TPool {
    protected:
        typedef struct tpool_node_s {
            T data; // at the beginning, s.t. it can be 1) externally 2) easily casted
            struct tpool_node_s* next;
        } tpool_node_t;

    private:
        tpool_node_t* head; ///< spare ones
        unsigned size; ///< only needed for upper spare limit

    public:
        TPool();
        ~TPool();

        T* pop(); ///< "allocate" new or cached one
        void push(T*); ///< "free"
        void purge();
};


template <class T> class TPoolTS: public TPool<T> {
    private:
        atomic_t lock;

    public:
        TPoolTS(): lock(0) {}
        INLINE T* pop() { spin_lock(&lock); T* rv = TPool<T>::pop(); spin_unlock(&lock); return rv; }
        INLINE void push(T* t) { spin_lock(&lock); TPool<T>::push(t); spin_unlock(&lock); }
        INLINE void purge() { spin_lock(&lock); TPool<T>::purge(); spin_unlock(&lock); }
};


template <class T> class CPool: public TPool<T> {
    public:
        INLINE T* pop() { return ::new (TPool<T>::pop()) T(); }
        INLINE void push(T* t) { t->~T(); TPool<T>::push(t); }
};


/** Provides interface with static pool and new/delete operators to inherit from */
template <class T> class PoolClass {
    private:
        static TPool<T> pool;

    public:
        static INLINE void* operator new(size_t nbytes) { assert(nbytes == sizeof(T)); return pool.pop(); }
        static INLINE void operator delete(void* p) { pool.push((T*)p); }
};
template <class T> TPool<T> PoolClass<T>::pool;


/** When a rare thread-safe push is needed only, for sync producer and possibly async consumer scenarios. */
template <class T> class TPoolPTS: public TPool<T> {
    private:
        typedef typename TPool<T>::tpool_node_t node_t;
        volatile node_t* pushed; // need volatile?

        INLINE void cleanup() {
            node_t* node;
            do {
                node = (node_t*)pushed;
                if (!node) return;
            } while (!atomic_swp(&pushed, node, NULL));
            do {
                node_t* tmp = node;
                node = node->next;
                TPool<T>::push((T*)tmp);
            } while (node);
        }

    public:
        TPoolPTS(): pushed(NULL) {}
        ~TPoolPTS() { cleanup(); }

        INLINE T* pop() {
            cleanup();
            return TPool<T>::pop();
        }

        INLINE void push_ts(T* t) {
            assert(!in_main_thread());
            node_t* const node = (node_t*)t;
            do {
                node->next = (node_t*)pushed;
            } while (!atomic_swp(&pushed, node->next, node));
        }

        INLINE void purge() {
            cleanup();
            TPool<T>::purge();
        }
};


template <class T> class CPoolPTS: public TPoolPTS<T> {
    public:
        INLINE T* pop() { return new (TPoolPTS<T>::pop()) T(); }
        INLINE void push(T* t) { t->~T(); TPoolPTS<T>::push(t); }
};


template <class T> class RefCount { // TODO: implement this by inheritance and delete operator overloading? TODO: what if on the stack? TODO: use smart pointers instead? TODO: use operator* and operator->?
    private:
        unsigned refcount;
        ~RefCount() {}
    public:
        INLINE RefCount(): refcount(1) {}
        INLINE RefCount(const T& _val): refcount(1), val(_val) {}
        INLINE void inc() { ++refcount; }
        INLINE unsigned dec() { if (!--refcount) { delete this; return 0; } else { return refcount; } }
        INLINE unsigned dec(TPool<RefCount<T> >* pool) { if (!--refcount) { pool->push(this); return 0; } else { return refcount; } }
        INLINE unsigned get() const { return refcount; }
        T val;
};


/** Not an actual slab-pool with pages atm, but a list of linked lists w/ free mem of a fixed size. */
class SlabPool {
    private:
        typedef struct slab_node_s {
            struct slab_node_s* next;
            int slot; // TODO: alignment?
            /* actual data here */
        } slab_node_t;
        slab_node_t* slots[SLAB_POOL_NUM];

    public:
        SlabPool();
        ~SlabPool();

        void* pop(size_t);
        INLINE void* cpop(size_t l) { return memset(pop(l), 0, l); }
        char* dup(const char*, size_t);
        INLINE char* dup(const char* s) { return dup(s, strlen(s)); }
        void push(void*);
};
extern SlabPool slab_pool; // TODO: some generic string class using this pool?


/** More a string container with automatic cleanup, does not check for lengths, may return NULL. */
class SlabString {
    private:
        size_t len;
        char* str;
    public:
        SlabString(): len(0), str(NULL) {}
        SlabString(const char* s): len(strlen(s)), str(slab_pool.dup(s, len)) {}
        SlabString(const char* s, size_t l): len(l), str(slab_pool.dup(s, len)) {}
        SlabString(size_t l): len(l), str((char*)slab_pool.pop(l)) { *str = '\0'; }
        ~SlabString() { if (str) slab_pool.push(str); }

        void INLINE dup(const char* s) { if (str) slab_pool.push(str); len = strlen(s); str = slab_pool.dup(s, len); }
        void INLINE dup(const char* s, size_t l) { if (str) slab_pool.push(str); len = l; str = slab_pool.dup(s, l); }

        const INLINE size_t length() const { return len; }
        const INLINE char* c_str() const { return str; }
        INLINE char* c_str() { return str; }
};


//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//


template <class T> TPool<T>::TPool(): head(NULL), size(0) {
}


template <class T> TPool<T>::~TPool() {
    purge();
}


template <class T> void TPool<T>::purge() {
    tpool_node_t* node;
    while ((node = head) != NULL) {
        head = head->next;
        free(node);
        --size;
    }
}


template <class T> void TPool<T>::push(T* data) {
    assert(data != NULL);
    #if MAX_POOL_SPARE == 0
        #ifndef DEBUG
            MESSAGE(pooling disabled)
        #endif
        free(data);
        return;
    #endif
    tpool_node_t* node = (tpool_node_t*)data;
    if (size >= MAX_POOL_SPARE/sizeof(T)) {
        free(node);
    } else {
        MAKE_MEM_UNDEFINED(data, sizeof(T));
        node->next = head;
        head = node;
        ++size;
    }
}


template <class T> T* TPool<T>::pop() {
    stats_inc(pool_allocs);
    #if MAX_POOL_SPARE == 0
        return talloc(T);
    #endif
    tpool_node_t* node;
    if (head) {
        node = head;
        head = head->next;
        --size;
        stats_inc(pool_hits);
    } else {
        node = talloc(tpool_node_t);
    }
    return &node->data;
}