/*
 * This file is part of the DOM implementation for KDE.
 *
 * Copyright (C) 2006 Allan Sandfeld Jensen (kde@carewolf.com)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 *
 */

#ifndef _MultiMap_h_
#define _MultiMap_h_


#include <assert.h>
#include <stdlib.h>

#include <QtCore/QHash>
#include <QtCore/QList>
#include <QtCore/QSet>

template<class T> class MultiMapPtrList;

template<class K, class T>
class KMultiMap {
public:
    typedef QSet<T*> Set;
    typedef QHash<K*, Set*> Map;
    typedef MultiMapPtrList<T*> List;

    KMultiMap() {}
    ~KMultiMap() {
        qDeleteAll(map.values());
        map.clear();
    }

    void insert(K* key, T* element)
    {
        Set *set = map.value(key);
        if (!set){
             set = new Set();
             map.insert(key, set);
        }
        if (!set->contains(element))
            set->insert(element);
    }
    void remove(K* key, T* element) 
    {
        Set *set = map.value(key);
        if (set) {
            set->remove(element);
            if (set->isEmpty()) {
                map.remove(key);
                delete set;
            }
        }
    }
    void remove(K* key) {
        Set *set = map.value(key);
        if (set) {
            map.remove(key);
            delete set;
        }
    }
    Set* find(K* key) {
        return map.value(key);
    }
private:
    Map map;
};

static inline unsigned int stupidHash(void* ptr)
{
    unsigned long val = (unsigned long)ptr;
    // remove alignment and multiply by a prime unlikely to be a factor of size
    val = (val >> 4) * 1237;
    return val;
}

#define START_PTRLIST_SIZE 4
#define MAX_PTRLIST_SIZE 27

class PtrListEntry {
public:
    PtrListEntry(unsigned int log_size) : count(0), log_size(log_size), search(log_size), next(0) {
//         entry = new T* [size];
        assert(log_size < MAX_PTRLIST_SIZE);
        entry = (void**)calloc ((1<<log_size), sizeof(void*));
    }
    ~PtrListEntry() {
//         delete[] entry;
        free(entry);
    }
    bool insert(void* e) {
        unsigned int t_size = size();
        if (count == t_size) return false;
        unsigned int hash = stupidHash(e);
        void** firstFree = 0;
        // Only let elements be placed 'search' spots from their hash
        for(unsigned int j=0; j<search; j++) {
            unsigned int i = (hash + j) & (t_size-1); // modulo size
            // We need check to all hashes in 'search' to garuantee uniqueness
            if (entry[i] == 0) {
                if (!firstFree)
                    firstFree = entry + i;
            } else
            if (entry[i] == e)
                return true;
        }
        if (firstFree) {
            *firstFree = e;
            count++;
            return true;
        }
        // We had more than 'search' collisions
        if (count < (t_size/3)*2) {
            // only 2/3 full => increase search
            unsigned int s = search *2;
            if (s >= t_size) s = t_size;
            search = s;
            return insert(e);
        }
        return false;
    }
    // Insert another smaller set into this one
    // Is only garuantied to succede when this PtrList is new
    void insert(PtrListEntry* listEntry) {
        assert(size() >= listEntry->count * 2);
        unsigned int old_size = 1U << listEntry->log_size;
        for(unsigned int i = 0; i < old_size; i++) {
            bool s = true;
            void *e = listEntry->entry[i];
            if (e) s = insert(e);
            assert(s);
        }
    }
    bool remove(void* e) {
        if (count == 0) return false;
        unsigned int size = (1U<<log_size);
        unsigned int hash = stupidHash(e);
        // Elements are at most placed 'search' spots from their hash
        for(unsigned int j=0; j<search; j++) {
            unsigned int i = (hash + j) & (size-1); // modulo size
            if (entry[i] == e) {
                entry[i] = 0;
                count--;
                return true;
            }
        }
        return false;
    }
    bool contains(void* e) {
        if (count == 0) return false;
        unsigned int t_size = size();
        unsigned int hash = stupidHash(e);
        // Elements are at most placed 'search' spots from their hash
        for(unsigned int j=0; j<search; j++) {
            unsigned int i = (hash + j) & (t_size-1); // modulo size
            if (entry[i] == e) return true;
        }
        return false;
    }
    void* at(unsigned int i) const {
        assert (i < (1U<<log_size));
        return entry[i];
    }
    bool isEmpty() const {
        return count == 0;
    }
    bool isFull() const {
        return count == size();
    }
    unsigned int size() const {
        return (1U << log_size);
    }

    unsigned int count;
    const unsigned short log_size;
    unsigned short search;
    PtrListEntry *next;
    void** entry;
};

// An unsorted and unique PtrList that is implement as a linked list of hash-sets
// Optimized for fast insert and fast lookup
template<class T>
class MultiMapPtrList {
public:
    MultiMapPtrList(unsigned int init_size= 16) : m_first(0), m_current(0), m_pos(0) {
        assert(init_size > 0);
        unsigned int s = init_size - 1;
        unsigned int log_size = 0;
        while (s > 0) {
            log_size++;
            s = s >> 1;
        }
        m_first = new PtrListEntry(log_size);
    }
    MultiMapPtrList(const MultiMapPtrList& ptrList) : m_first(0), m_current(0), m_pos(0) {
        unsigned int t_count = ptrList.count();
        unsigned int log_size = 0;
        while (t_count > 0) {
            log_size++;
            t_count = t_count >> 1;
        }
        // At least as large as the largest ptrListEntry in the original
        if (t_count < ptrList.m_first->log_size) log_size = ptrList.m_first->log_size;
        m_first = new PtrListEntry(log_size);

        PtrListEntry *t_current = ptrList.m_first;
        while (t_current) {
            unsigned int t_size = t_current->size();
            for(unsigned int i=0; i < t_size; i++) {
                void* e = t_current->at(i);
                if (e != 0) {
                    bool t = m_first->insert(e);
                    if (!t) {
                        // Make a new, but keep the size
                        PtrListEntry *t_new = new PtrListEntry(log_size);
                        t_new->insert(e);
                        t_new->next = m_first;
                        m_first = t_new;
                    }
                }
            }
            t_current = t_current->next;
        }
    }
    ~MultiMapPtrList() {
        PtrListEntry *t_next, *t_current = m_first;
        while (t_current) {
            t_next = t_current->next;
            delete t_current;
            t_current = t_next;
        }
    }
    void append(T* e) {
        PtrListEntry *t_last = 0, *t_current = m_first;
        int count = 0;
        while (t_current) {
            if (t_current->insert(e)) return;
            t_last = t_current;
            t_current = t_current->next;
            count++;
        }
        // Create new hash-set
        unsigned int newsize = m_first->log_size+1;
        if (newsize > MAX_PTRLIST_SIZE) newsize = MAX_PTRLIST_SIZE;
        t_current = new PtrListEntry(newsize);
        bool t = t_current->insert(e);
        assert(t);
        // Prepend it to the list, for insert effeciency
        t_current->next = m_first;
        m_first = t_current;
        // ### rehash some of the smaller sets
        /*
        if (count > 4) {
            // rehash the last in the new
            t_current->insert(t_last);
        }*/
    }
    void remove(T* e) {
        PtrListEntry *t_next, *t_last = 0, *t_current = m_first;
        // Remove has to check every PtrEntry.
        while (t_current) {
            t_next = t_current->next;
            if (t_current->remove(e) && t_current->isEmpty()) {
                if (t_last) {
                    t_last->next = t_current->next;
                }
                else {
                    assert (m_first == t_current);
                    m_first = t_current->next;
                }
                delete t_current;
            } else {
                t_last = t_current;
            }
            t_current = t_next;
        }
    }
    bool contains(T* e) {
        PtrListEntry *t_current = m_first;
        while (t_current) {
            if (t_current->contains(e)) return true;
            t_current = t_current->next;
        }
        return false;
    }
    bool isEmpty() {
        if (!m_first) return true;
        PtrListEntry *t_current = m_first;
        while (t_current) {
            if (!t_current->isEmpty()) return false;
            t_current = t_current->next;
        }
        return true;
    }
    unsigned int count() const {
        unsigned int t_count = 0;
        PtrListEntry *t_current = m_first;
        while (t_current) {
            t_count += t_current->count;
            t_current = t_current->next;
        }
        return t_count;
    }
// Iterator functions:
    T* first() {
        m_current = m_first;
        m_pos = 0;
        // skip holes
        if (m_current && !m_current->at(m_pos))
            return next();
        else
            return current();
    }
    T* current() {
        if (!m_current)
            return (T*)0;
        else
            return (T*)m_current->at(m_pos);
    }
    T* next() {
        if (!m_current) return (T*)0;
        m_pos++;
        if (m_pos >= m_current->size()) {
            m_current = m_current->next;
            m_pos = 0;
        }
        // skip holes
        if (m_current && !m_current->at(m_pos))
            return next();
        else
            return current();
    }
private:
    PtrListEntry *m_first;
// iteration:
    PtrListEntry *m_current;
    unsigned int m_pos;
};

#undef START_PTRLIST_SIZE
#undef MAX_PTRLIST_SIZE
#endif
