#include "pocketpy/dict.h"

namespace pkpy{

    Dict::Dict(): _capacity(__Capacity),
            _mask(__Capacity-1),
            _size(0), _critical_size(__Capacity*__LoadFactor+0.5f), _head_idx(-1), _tail_idx(-1){
        __alloc_items();
    }

    void Dict::__alloc_items(){
        _items = (Item*)malloc(_capacity * sizeof(Item));
        for(int i=0; i<_capacity; i++){
            _items[i].first = nullptr;
            _items[i].second = nullptr;
            _items[i].prev = -1;
            _items[i].next = -1;
        }
    }

    Dict::Dict(Dict&& other){
        _capacity = other._capacity;
        _mask = other._mask;
        _size = other._size;
        _critical_size = other._critical_size;
        _head_idx = other._head_idx;
        _tail_idx = other._tail_idx;
        _items = other._items;
        other._items = nullptr;
    }

    Dict::Dict(const Dict& other){
        _capacity = other._capacity;
        _mask = other._mask;
        _size = other._size;
        _critical_size = other._critical_size;
        _head_idx = other._head_idx;
        _tail_idx = other._tail_idx;
        // copy items
        _items = (Item*)malloc(_capacity * sizeof(Item));
        memcpy(_items, other._items, _capacity * sizeof(Item));
    }

    void Dict::set(VM* vm, PyVar key, PyVar val){
        // do possible rehash
        if(_size+1 > _critical_size) _rehash(vm);
        bool ok; int i;
        _probe_1(vm, key, ok, i);
        if(!ok) {
            _size++;
            _items[i].first = key;

            // append to tail
            if(_size == 0+1){
                _head_idx = i;
                _tail_idx = i;
            }else{
                _items[i].prev = _tail_idx;
                _items[_tail_idx].next = i;
                _tail_idx = i;
            }
        }
        _items[i].second = val;
    }

    void Dict::_rehash(VM* vm){
        Item* old_items = _items;
        int old_head_idx = _head_idx;

        _capacity *= 4;
        _mask = _capacity - 1;
        _size = 0;
        _critical_size = _capacity*__LoadFactor+0.5f;
        _head_idx = -1;
        _tail_idx = -1;
        
        __alloc_items();

        // copy old items to new dict
        int i = old_head_idx;
        while(i != -1){
            set(vm, old_items[i].first, old_items[i].second);
            i = old_items[i].next;
        }

        free(old_items);
    }


    PyVar Dict::try_get(VM* vm, PyVar key) const{
        bool ok; int i;
        _probe_0(vm, key, ok, i);
        if(!ok) return nullptr;
        return _items[i].second;
    }

    bool Dict::contains(VM* vm, PyVar key) const{
        bool ok; int i;
        _probe_0(vm, key, ok, i);
        return ok;
    }

    bool Dict::del(VM* vm, PyVar key){
        bool ok; int i;
        _probe_0(vm, key, ok, i);
        if(!ok) return false;
        _items[i].first = nullptr;
        // _items[i].second = PY_DELETED_SLOT;  // do not change .second if it is not NULL, it means the slot is occupied by a deleted item
        _size--;

        if(_size == 0){
            _head_idx = -1;
            _tail_idx = -1;
        }else{
            if(_head_idx == i){
                _head_idx = _items[i].next;
                _items[_head_idx].prev = -1;
            }else if(_tail_idx == i){
                _tail_idx = _items[i].prev;
                _items[_tail_idx].next = -1;
            }else{
                _items[_items[i].prev].next = _items[i].next;
                _items[_items[i].next].prev = _items[i].prev;
            }
        }
        _items[i].prev = -1;
        _items[i].next = -1;
        return true;
    }

    void Dict::update(VM* vm, const Dict& other){
        other.apply([&](PyVar k, PyVar v){ set(vm, k, v); });
    }

    Tuple Dict::keys() const{
        Tuple t(_size);
        int i = _head_idx;
        int j = 0;
        while(i != -1){
            t[j++] = _items[i].first;
            i = _items[i].next;
        }
        PK_ASSERT(j == _size);
        return t;
    }

    Tuple Dict::values() const{
        Tuple t(_size);
        int i = _head_idx;
        int j = 0;
        while(i != -1){
            t[j++] = _items[i].second;
            i = _items[i].next;
        }
        PK_ASSERT(j == _size);
        return t;
    }

    void Dict::clear(){
        _size = 0;
        _head_idx = -1;
        _tail_idx = -1;
        for(int i=0; i<_capacity; i++){
            _items[i].first = nullptr;
            _items[i].second = nullptr;
            _items[i].prev = -1;
            _items[i].next = -1;
        }
    }

    Dict::~Dict(){
        if(_items) free(_items);
    }
}   // namespace pkpy