package ch.zhaw.ads;

import java.util.AbstractList;

public class MyList<T extends Object> extends AbstractList<T> {

    private Node<T> head;
    private Node<T> tail;
    private int size;

    public MyList() {
        clear();
    }

    @Override
    public boolean add(Object obj) {
        try {
            if (isEmpty()) {
                head = tail = new Node<T>((T) obj);
                size++;
                return true;
            }

            Node<T> newNode = new Node<T>((T) obj, tail, null);

            // the old tail now has a nextNode
            tail.nextNode = newNode;
            // and move tail to newNode
            tail = newNode;
            size++;

            return true;
        } catch (ClassCastException e) {
            return false;
        }
    }

    @Override
    public boolean remove(Object obj) {
        if (isEmpty()) {
            return false;
        }

        try {
            Node<T> nodeToRemove = new Node<T>((T) obj);

            for (Node<T> node = head; node != null; node = node.nextNode) {
                if (node.equals(nodeToRemove)) {
                    unlink(node);
                    return true;
                }
            }
        } catch (ClassCastException e) {
            return false;
        }
        // reaching this means that no element was found
        return false;
    }

    private void unlink(Node<T> node) {
        Node<T> previousNode = node.previousNode;
        Node<T> nextNode = node.nextNode;

        if (previousNode == null) {
            // this means the node which will be unlinked was the first element. the new first element is the nextNode of the unlinked node
            head = nextNode;
        } else {
            previousNode.nextNode = nextNode;
        }

        // same thing, other way around
        if (nextNode == null) {
            tail = previousNode;
        } else {
            nextNode.previousNode = previousNode;
        }
        size--;
    }

    @Override
    public T get(int index) {
        if (index < 0 || index >= size) {
            throw new IndexOutOfBoundsException();
        }

        // check if the index is closer to head or tail
        // shifting the size to the right by one bit basically divides it by 2 (works because its binary)
        if (index < (size >> 1)) {
            Node<T> node = head;
            for (int i = 0; i < index; i++) {
                node = node.nextNode;
            }
            return node.value;
        } else {
            Node<T> node = tail;
            for (int i = size - 1; i > index; i--) {
                node = node.previousNode;
            }
            return node.value;
        }
    }

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    @Override
    public int size() {
        return size;
    }

    @Override
    public void clear() {
        size = 0;
        head = null;
        tail = null;
    }
}
