/***************************************************************************
 *   crengine-ng                                                           *
 *   Copyright (C) 2007,2009-2011 Vadim Lopatin <coolreader.org@gmail.com> *
 *   Copyright (C) 2013 Konstantin Potapov <pkbo@users.sourceforge.net>    *
 *   Copyright (C) 2018 Aleksey Chernov <valexlin@gmail.com>               *
 *   Copyright (C) 2018,2020 poire-z <poire-z@users.noreply.github.com>    *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or         *
 *   modify it under the terms of the GNU General Public License           *
 *   as published by the Free Software Foundation; either version 2        *
 *   of the License, or (at your option) any later version.                *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the Free Software           *
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,            *
 *   MA 02110-1301, USA.                                                   *
 ***************************************************************************/

/**
 * \file lvrefcache.h
 * \brief Referenced objects cache.
 *
 * Allows to reuse objects with the same contents.
 */

#if !defined(__LV_REF_CACHE_H_INCLUDED__)
#define __LV_REF_CACHE_H_INCLUDED__

#include <lvmemman.h>
#include <lvref.h>
#include <lvarray.h>

/*
    Object cache

    Requirements: 
       sz parameter of constructor should be power of 2
       bool operator == (LVRef<T> & r1, LVRef<T> & r2 ) should be defined
       lUInt32 calcHash( LVRef<T> & r1 ) should be defined
*/

template <class ref_t>
class LVRefCache
{
    class LVRefCacheRec
    {
        ref_t style;
        lUInt32 hash;
        LVRefCacheRec* next;
        LVRefCacheRec(ref_t& s, lUInt32 h)
                : style(s)
                , hash(h)
                , next(NULL) { }
        friend class LVRefCache<ref_t>;
    };
private:
    int size;
    LVRefCacheRec** table;
public:
    // check whether equal object already exists if cache
    // if found, replace reference with cached value
    void cacheIt(ref_t& style) {
        lUInt32 hash = calcHash(style);
        lUInt32 index = hash & (size - 1);
        LVRefCacheRec** rr;
        rr = &table[index];
        while (*rr != NULL) {
            if (*(*rr)->style.get() == *style.get()) {
                style = (*rr)->style;
                return;
            }
            rr = &(*rr)->next;
        }
        *rr = new LVRefCacheRec(style, hash);
    }
    // garbage collector: remove unused entries
    void gc() {
        for (int index = 0; index < size; index++) {
            LVRefCacheRec** rr;
            rr = &table[index];
            while (*rr != NULL) {
                if ((*rr)->style.getRefCount() == 1) {
                    LVRefCacheRec* r = (*rr);
                    *rr = r->next;
                    delete r;
                } else {
                    rr = &(*rr)->next;
                }
            }
        }
    }
    LVRefCache(int sz) {
        size = sz;
        table = new LVRefCacheRec*[sz];
        for (int i = 0; i < sz; i++)
            table[i] = NULL;
    }
    ~LVRefCache() {
        LVRefCacheRec *r, *r2;
        for (int i = 0; i < size; i++) {
            for (r = table[i]; r;) {
                r2 = r;
                r = r->next;
                delete r2;
            }
        }
        delete[] table;
    }
};

template <class ref_t>
class LVIndexedRefCache
{
    // hash table item
    struct LVRefCacheRec
    {
        int index;
        ref_t style;
        lUInt32 hash;
        LVRefCacheRec* next;
        LVRefCacheRec(ref_t& s, lUInt32 h)
                : style(s)
                , hash(h)
                , next(NULL) { }
    };

    // index item
    struct LVRefCacheIndexRec
    {
        LVRefCacheRec* item;
        int refcount; // refcount, or next free index if item==NULL
    };
private:
    int size;
    LVRefCacheRec** table;

    LVRefCacheIndexRec* index;
    int indexsize;
    int nextindex;
    int freeindex;
    int numitems;

    int indexItem(LVRefCacheRec* rec) {
        int n;
        if (freeindex) {
            n = freeindex;
            freeindex = index[freeindex].refcount; // next free index
        } else {
            n = ++nextindex;
        }
        if (n >= indexsize) {
            // resize
            if (indexsize == 0)
                indexsize = size / 2;
            else
                indexsize *= 2;
            index = cr_realloc(index, indexsize);
            for (int i = nextindex + 1; i < indexsize; i++) {
                index[i].item = NULL;
                index[i].refcount = 0;
            }
        }
        rec->index = n;
        index[n].item = rec;
        index[n].refcount = 1;
        return n;
    }

    // remove item from hash table
    void removeItem(LVRefCacheRec* item) {
        lUInt32 hash = item->hash;
        lUInt32 tindex = hash & (size - 1);
        LVRefCacheRec** rr = &table[tindex];
        for (; *rr; rr = &(*rr)->next) {
            if (*rr == item) {
                LVRefCacheRec* tmp = *rr;
                *rr = (*rr)->next;
                delete tmp;
                numitems--;
                return;
            }
        }
        // not found!
    }
public:
    LVArray<ref_t>* getIndex() {
        LVArray<ref_t>* list = new LVArray<ref_t>(indexsize, ref_t());
        for (int i = 1; i < indexsize; i++) {
            if (index[i].item)
                list->set(i, index[i].item->style);
        }
        return list;
    }

    int length() {
        return numitems;
    }

    void release(ref_t r) {
        int i = find(r);
        if (i > 0)
            release(i);
    }

    void release(int n) {
        if (n < 1 || n > nextindex)
            return;
        if (index[n].item) {
            if ((--index[n].refcount) <= 0) {
                removeItem(index[n].item);
                // next free
                index[n].refcount = freeindex;
                index[n].item = NULL;
                freeindex = n;
            }
        }
    }

    // get by index
    ref_t get(int n) {
        if (n > 0 && n <= nextindex && index[n].item)
            return index[n].item->style;
        return ref_t();
    }

    // check whether equal object already exists if cache
    // if found, replace reference with cached value
    // returns index of item - use it to release reference
    bool cache(lUInt16& indexholder, ref_t& style) {
        int newindex = cache(style);
        // printf("newindex: %d  /  provided indexholder: %d\n", newindex, (int)indexholder);
        if (indexholder != newindex) {
            release(indexholder);
            indexholder = (lUInt16)newindex;
            return true;
        } else { // indexholder == newindex
            // Here, we want to decrement the refcount that has been incremented
            // by the above call "newindex = cache( style )", as it returned the
            // same index as the already referenced one.
            // In normal cases, when the item is already present in the cache
            // and referenced once, we are here with refcount=2, and we want to
            // put it back to refcount=1, as it should be.
            // In rare cases for some yet undertermined reason or bug, we may
            // have been called with a non-0 indexholder, but not previously
            // present in the cache, and we get the same value for newindex:
            // the cache item refcount is then 1. And we want it to stay 1 (as
            // it's really referenced once) so the value/pointer are not freed,
            // and some segmentation fault is avoided later.
            // So, we just don't release it if the refcount is 1 (we were
            // asked to cache something: so don't drop it!)
            // printf("released: refcount for %d = %d\n", indexholder, this->index[indexholder].refcount);
            if (this->index[indexholder].refcount > 1)
                release(indexholder);
            return false;
            // This returned boolean seems not used anywhere, so it's not
            // clear whether we should return true or false when the
            // item was not in the cache, and thus created - but the
            // indexholder (although not existing) stayed unchanged.
        }
    }

    bool addIndexRef(lUInt16 n) {
        if (n > 0 && n <= nextindex && index[n].item) {
            index[n].refcount++;
            return true;
        } else
            return false;
    }

    // check whether equal object already exists if cache
    // if found, replace reference with cached value
    // returns index of item - use it to release reference
    int cache(ref_t& style) {
        lUInt32 hash = calcHash(style);
        lUInt32 index = hash & (size - 1);
        LVRefCacheRec** rr;
        rr = &table[index];
        while (*rr != NULL) {
            if ((*rr)->hash == hash && *(*rr)->style.get() == *style.get()) {
                style = (*rr)->style;
                int n = (*rr)->index;
                this->index[n].refcount++;
                return n;
            }
            rr = &(*rr)->next;
        }
        *rr = new LVRefCacheRec(style, hash);
        numitems++;
        return indexItem(*rr);
    }

    // check whether equal object already exists if cache
    // if found, replace reference with cached value
    // returns index of item - use it to release reference
    int find(ref_t& style) {
        lUInt32 hash = calcHash(style);
        lUInt32 index = hash & (size - 1);
        LVRefCacheRec** rr;
        rr = &table[index];
        while (*rr != NULL) {
            if ((*rr)->hash == hash && *(*rr)->style.get() == *style.get()) {
                int n = (*rr)->index;
                return n;
            }
            rr = &(*rr)->next;
        }
        return 0;
    }

    /// from index array
    LVIndexedRefCache(LVArray<ref_t>& list)
            : index(NULL)
            , indexsize(0)
            , nextindex(0)
            , freeindex(0)
            , numitems(0) {
        setIndex(list);
    }

    int nearestPowerOf2(int n) {
        int res;
        for (res = 1; res < n; res <<= 1)
            ;
        return res;
    }

    /// init from index array
    void setIndex(LVArray<ref_t>& list) {
        clear();
        size = nearestPowerOf2(list.length() > 0 ? list.length() : 32);
        if (table)
            delete[] table;
        table = new LVRefCacheRec*[size];
        for (int i = 0; i < size; i++)
            table[i] = NULL;
        indexsize = list.length();
        nextindex = indexsize > 0 ? indexsize - 1 : 0;
        if (indexsize) {
            index = cr_realloc(index, indexsize);
            index[0].item = NULL;
            index[0].refcount = 0;
            for (int i = 1; i < indexsize; i++) {
                if (list[i].isNull()) {
                    // add free node
                    index[i].item = NULL;
                    index[i].refcount = freeindex;
                    freeindex = i;
                } else {
                    // add item
                    lUInt32 hash = calcHash(list[i]);
                    lUInt32 hindex = hash & (size - 1);
                    LVRefCacheRec* rec = new LVRefCacheRec(list[i], hash);
                    rec->index = i;
                    rec->next = table[hindex];
                    table[hindex] = rec;
                    index[i].item = rec;
                    index[i].refcount = 1;
                    numitems++;
                }
            }
        }
    }

    LVIndexedRefCache(int sz)
            : index(NULL)
            , indexsize(0)
            , nextindex(0)
            , freeindex(0)
            , numitems(0) {
        size = sz;
        table = new LVRefCacheRec*[sz];
        for (int i = 0; i < sz; i++)
            table[i] = NULL;
    }
    void clear(int sz = 0) {
        if (sz == -1)
            sz = size;
        LVRefCacheRec *r, *r2;
        for (int i = 0; i < size; i++) {
            for (r = table[i]; r;) {
                r2 = r;
                r = r->next;
                delete r2;
            }
            table[i] = NULL;
        }
        if (index) {
            free(index);
            index = NULL;
            indexsize = 0;
            nextindex = 0;
            freeindex = 0;
        }
        numitems = 0;
        if (sz) {
            size = sz;
            if (table)
                delete[] table;
            table = new LVRefCacheRec*[sz];
            for (int i = 0; i < sz; i++)
                table[i] = NULL;
        }
    }
    ~LVIndexedRefCache() {
        clear();
        delete[] table;
    }
};

template <typename keyT, class dataT>
class LVCacheMap
{
private:
    class Pair
    {
    public:
        keyT key;
        dataT data;
        int lastAccess;
    };
    Pair* buf;
    int size;
    int orig_size;
    int numitems;
    int lastAccess;
    void checkOverflow(int oldestAccessTime) {
        int i;
        if (oldestAccessTime == -1) {
            for (i = 0; i < size; i++)
                if (oldestAccessTime == -1 || buf[i].lastAccess > oldestAccessTime)
                    oldestAccessTime = buf[i].lastAccess;
        }
        if (oldestAccessTime > 1000000000) {
            int maxLastAccess = 0;
            for (i = 0; i < size; i++) {
                buf[i].lastAccess -= 1000000000;
                if (maxLastAccess == 0 || buf[i].lastAccess > maxLastAccess)
                    maxLastAccess = buf[i].lastAccess;
            }
            lastAccess = maxLastAccess + 1;
        }
    }
public:
    int length() {
        return numitems;
    }
    LVCacheMap(int maxSize)
            : size(maxSize)
            , orig_size(maxSize)
            , numitems(0)
            , lastAccess(1) {
        buf = new Pair[size];
        clear();
    }
    void reduceSize(int newSize) {
        if (newSize < orig_size) {
            clear();
            size = newSize;
        }
    }
    void restoreSize() {
        size = orig_size;
        clear();
    }
    void clear() {
        for (int i = 0; i < size; i++) {
            buf[i].key = keyT();
            buf[i].data = dataT();
            buf[i].lastAccess = 0;
        }
        numitems = 0;
    }
    bool get(keyT key, dataT& data) {
        for (int i = 0; i < size; i++) {
            if (buf[i].key == key) {
                data = buf[i].data;
                buf[i].lastAccess = ++lastAccess;
                if (lastAccess > 1000000000)
                    checkOverflow(-1);
                return true;
            }
        }
        return false;
    }
    bool remove(keyT key) {
        for (int i = 0; i < size; i++) {
            if (buf[i].key == key) {
                buf[i].key = keyT();
                buf[i].data = dataT();
                buf[i].lastAccess = 0;
                numitems--;
                return true;
            }
        }
        return false;
    }
    void set(keyT key, dataT data) {
        int oldestAccessTime = -1;
        int oldestIndex = 0;
        for (int i = 0; i < size; i++) {
            if (buf[i].key == key) {
                buf[i].data = data;
                buf[i].lastAccess = ++lastAccess;
                return;
            }
            int at = buf[i].lastAccess;
            if (at < oldestAccessTime || oldestAccessTime == -1) {
                oldestAccessTime = at;
                oldestIndex = i;
            }
        }
        checkOverflow(oldestAccessTime);
        if (buf[oldestIndex].key == keyT())
            numitems++;
        buf[oldestIndex].key = key;
        buf[oldestIndex].data = data;
        buf[oldestIndex].lastAccess = ++lastAccess;
        return;
    }
    ~LVCacheMap() {
        delete[] buf;
    }
};

#endif // __LV_REF_CACHE_H_INCLUDED__
