// https://github.com/TheOdinProject/curriculum/issues/27103

class Node {
    constructor(key, value) {
        this.key = key;
        this.value = value;
        this.next = null;
    }
}

class LinkedList {
    constructor() {
        this.size = 0;
        this.head = null;
        this.tail = null;
    }

    append(key, value) {
        let newNode = new Node(key, value);
        if (this.head == null) {
            this.head = newNode;
        } else {
            const temp = this.head;
            this.head = newNode;
            this.head.next = temp;
        }
        if (this.tail == null) {
            this.tail = newNode;
        }

        this.size += 1;
    }
    pop() {
        if (this.head.next == null) {
            this.head = null;
            this.tail = null;
        } else {
            this.head = this.head.next;
        }
        this.size -= 1;
    }

    contains(key) {
        function searchNodes(node, key) {
            if (node.key === key) {
                return true;
            } else if (node.next == null) {
                return false;
            } else {
                return searchNodes(node.next);
            }
        }

        return searchNodes(this.head, key);
    }

    replace(key, value) {
        function replace(node) {
            if (node.key === key) {
                node.value = value;
            } else {
                return replace(node.next);
            }
        }
        replace(this.head, key);
    }

    find(key) {
        function searchNodes(node, key) {
            if (node.key === key) {
                return node.value;
            } else if (node.next == null) {
                return null;
            } else {
                return searchNodes(node.next, key);
            }
        }

        return searchNodes(this.head, key);
    }
    remove(key) {
        let lastNode = this.head;
        let currentNode = this.head;

        if (currentNode.key === key) {
            if (currentNode.next == null) {
                this.size = 0;
                this.head = null;
                this.tail = null;
            } else {
                this.size -= 1;
                this.head = this.head.next;
            }
            return true;
        }

        while (currentNode) {
            if (currentNode.key === key) {
                lastNode.next = currentNode.next;
                this.size -= 1;
                return true;
            }
            lastNode = currentNode;
            currentNode = currentNode.next;
        }
        return false;
    }
}

class HashMap {
    constructor(capacity = 8) {
        this.initialCapacity = capacity;
        this.capacity = capacity;
        this.buckets = Array(capacity);
        this.capacity = this.buckets.length;
        this.loadFactor = 0.75;
    }

    stringToNumber(string) {
        let hashCode = 0;
        const primeNumber = 31;

        for (let i = 0; i < string.length; i++) {
            hashCode = primeNumber * hashCode + string.charCodeAt(i);
        }

        return hashCode;
    }

    hash(key) {
        return this.stringToNumber(key);
    }

    capacityWatcher() {
        let consumed = this.buckets.filter(Object).length;
        if (consumed >= this.capacity * this.loadFactor) {
            this.capacity = Math.round(1.25 * this.capacity);
        }
    }

    set(key, value) {
        let _hash = this.hash(key);
        let bucketIndex = _hash % this.capacity;
        this.capacityWatcher();
        if (this.buckets[bucketIndex] == undefined) {
            let linkedList = new LinkedList();
            linkedList.append(key, value);

            this.buckets[bucketIndex] = linkedList;
        } else {
            let keyExists = this.buckets[bucketIndex].contains(key);

            if (keyExists) {
                this.buckets[bucketIndex].replace(key, value);
            } else {
                this.buckets[bucketIndex].append(key, value);
            }
        }
    }

    get(key) {
        for (let i = 0; i < this.buckets.length; i++) {
            if (this.buckets[i]) {
                let value = this.buckets[i].find(key);
                if (value) return value;
            }
        }
    }
    has(key) {
        for (let i = 0; i < this.buckets.length; i++) {
            if (this.buckets[i]) {
                let value = this.buckets[i].find(key);
                if (value) return true;
            }
        }
        return false;
    }
    remove(key) {
        for (let i = 0; i < this.buckets.length; i++) {
            if (this.buckets[i]) {
                let removed = this.buckets[i].remove(key);
                if (this.buckets[i].size === 0) this.buckets[i] = undefined;
                if (removed) return true;
            }
        }
        return false;
    }
    length() {
        let count = 0;

        for (let i = 0; i < this.buckets.length; i++) {
            if (this.buckets[i]) {
                count += this.buckets[i].size;
            }
        }
        return count;
    }
    clear() {
        this.capacity = this.initialCapacity;
        this.buckets = Array(this.capacity);
    }
    keys() {
        let _keys = [];

        for (let i = 0; i < this.buckets.length; i++) {
            if (this.buckets[i]) {
                let currentNode = this.buckets[i].head;
                while (currentNode) {
                    _keys.push(currentNode.key);
                    currentNode = currentNode.next;
                }
            }
        }
        return _keys;
    }
    values() {
        let _values = [];

        for (let i = 0; i < this.buckets.length; i++) {
            if (this.buckets[i]) {
                let currentNode = this.buckets[i].head;
                while (currentNode) {
                    _values.push(currentNode.value);
                    currentNode = currentNode.next;
                }
            }
        }
        return _values;
    }
    entries() {
        let _entries = [];

        for (let i = 0; i < this.buckets.length; i++) {
            if (this.buckets[i]) {
                let currentNode = this.buckets[i].head;
                while (currentNode) {
                    _entries.push([currentNode.key, currentNode.value]);
                    currentNode = currentNode.next;
                }
            }
        }
        return _entries;
    }
}

let t = new HashMap();

// Starter Data
t.set('football', 'redskins');
t.set('framework', 'laptop');
t.set('framework 2', 'sucks');
t.set('deliver', 'me');
t.set('alpha', 'omega');
t.set('hello', 'world');
t.set('hello', 'smigs');
t.set('helo', 'world');
t.set("omg kevin that's wild", 'world');
t.set('mike', 'world');
t.set('zebra', 'zoo');
t.set('linux', 'tux');
t.set('c major', 'c e g');
t.remove('zebra');
t.remove('hello');
console.log(t.length());
// Print the Keys
console.log(t.keys());
// Print the values
console.log(t.values());
// Print keys and values
console.log(t.entries());

// clear the hashmap
t.clear();
console.log('Stuff was cleared');
console.log('new data set');
t.set('football', 'redskins');
t.set('framework', 'laptop');
t.set('framework 2', 'sucks');
t.set('deliver', 'me');
t.set('alpha', 'omega');
t.set('hello', 'world');
t.set('hello', 'smigs');
t.set('helo', 'world');
t.set("omg kevin that's wild", 'world');
t.set('mike', 'world');
t.set('zebra', 'zoo');
t.set('linux', 'tux');
t.set('c major', 'c e g');
t.set('user_role', 'DevOps Engineer');
t.set('interests', 'Blogging, Influencing, Stocks, Generating Income');
t.set('goals', 'Build following / grow community, Learn and grow, Technology');
t.set('preferred_name', 'Smig');
t.set('data_request', 'Create key value pairs for an arbitrary data set');
t.set('format', "t.set('key', 'value')");
t.set('entries', '30');
t.set('entry_1', 'value_1');
t.set('entry_2', 'value_2');
t.set('entry_3', 'value_3');
t.set('entry_4', 'value_4');
t.set('entry_5', 'value_5');
t.set('entry_6', 'value_6');
t.set('entry_7', 'value_7');
t.set('entry_8', 'value_8');
t.set('entry_9', 'value_9');
t.set('entry_10', 'value_10');
t.set('entry_11', 'value_11');
t.set('entry_12', 'value_12');
t.set('entry_13', 'value_13');
t.set('entry_14', 'value_14');
t.set('entry_15', 'value_15');
t.set('entry_16', 'value_16');
t.set('entry_17', 'value_17');
t.set('entry_18', 'value_18');
t.set('entry_19', 'value_19');
t.set('entry_20', 'value_20');
t.set('entry_21', 'value_21');
t.set('entry_22', 'value_22');
t.set('entry_23', 'value_23');
t.set('entry_24', 'value_24');
t.set('entry_25', 'value_25');
t.set('entry_26', 'value_26');
t.set('entry_27', 'value_27');
t.set('entry_28', 'value_28');
t.set('entry_29', 'value_29');
t.set('entry_30', 'value_30');
console.log(t.keys());
console.log(t.entries());