package collections;

import org.jetbrains.annotations.NotNull;

import java.util.Iterator;
import java.util.NoSuchElementException;

public final class SinglyLinkedListJava<T> implements ImperialMutableList<T> {

    private static class Node<T> {
        T element;
        Node<T> next;

        Node(T element, Node<T> next) {
            this.element = element;
            this.next = next;
        }

        Node(T element) {
            this.element = element;
            this.next = null;
        }
    }

    private int size = 0;

    private Node<T> head = null;

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

    @Override
    public String toString() {
        final StringBuilder result = new StringBuilder();
        result.append("[");
        Node<T> current = head;
        boolean first = true;
        while (current != null) {
            if (!first) {
                result.append(", ");
            }
            first = false;
            result.append(current.element);
            current = current.next;
        }
        result.append("]");
        return result.toString();
    }

    @NotNull
    @Override
    public T get(int index) {
        checkIndexInBounds(index);
        Node<T> current = head;
        for (int i = 1; i<index; i++) {
            current = current.next;
        }
        return current.element;
    }

    @Override
    public void add(int index, T element) {
        checkIndexInBounds(index, true);
        size++;
        var pair = traverseTo(index);
        Node<T> newNode = new Node<>(element, pair.getSecond());
        if (pair.getFirst() == null) {
            head = newNode;
        } else {
            pair.getSecond().next = newNode;
        }
    }

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

    @Override
    public boolean contains(T element) {
        var current = head;
        while (current != null) {
            if (current.element == element) {
                return true;
            }
            current = current.next;
        }
        return false;
    }

    @Override
    public T removeAt(int index) {
        checkIndexInBounds(index);
        var pair = traverseTo(index);
        var result = pair.getSecond().element;
        unlink(pair.getFirst(), pair.getSecond());
        return result;
    }

    @Override
    public boolean remove(T element) {
        Node<T> previous = null;
        Node<T> current = head;
        while (current != null) {
            if (current.element == element) {
                unlink(previous, current);
                return true;
            }
            previous = current;
            current = current.next;
        }
        return false;
    }

    @Override
    public T set(int index, T element) {
        checkIndexInBounds(index);
        Node<T> current = head;
        for (int i = 0;i<index;i++) {
            current = current.next;
        }
        var result = current.element;
        current.element = element;
        return result;
    }

    @Override
    public void addAll(int index, @NotNull ImperialMutableList<T> other) {
        checkIndexInBounds(index, true);
        val iterator = other.iterator();
        val start = Node(iterator.next());
        var end = start;
        while (iterator.hasNext()) {
            end.next = Node(iterator.next());
            end = end.next!!;
        }
        if (index == 0) {
            end.next = head;
            head = start;
        } else {
            val (previous, current) = traverseTo(index);
            assert(previous != null);
            previous!!.next = start;
            end.next = current;
        }
        size += other.size;
    }

    @NotNull
    @Override
    public Iterator<T> iterator() {
        return new Iterator<T>() {
            // TODO: you need to populate this anonymous class, including implementing its methods.

            @Override
            public boolean hasNext() {
                throw new RuntimeException("TODO: you need to implement this method.");
            }

            @Override
            public T next() {
                throw new RuntimeException("TODO: you need to implement this method.");
            }
        };
    }

    private void checkIndexInBounds(int index, boolean inclusive) {
        if (index < 0 || index >= (inclusive ? size + 1 : size)) {
            throw new IndexOutOfBoundsException();
        }
    }

    private void checkIndexInBounds(int index) {
        checkIndexInBounds(index, false);
    }

    private ImperialPair<Node<T>, Node<T>> traverseTo(int index) {
        Node<T> previous = null;
        Node<T> current = head;
        for (int i = 0; i < index; i++) {
            previous = current;
            current = current.next;
        }
        return new ImperialPair<>(previous, current);
    }

    private void unlink(Node<T> previous, Node<T> current) {
        if (previous == null) {
            head = current.next;
        } else {
            previous.next = current.next;
        }
        size--;
    }
}
