package ua.nure.jfn.task2;

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

public class ListImpl<T> implements List<T> {

	private Node<T> head;
	private int size;

	private static class Node<T> {
		Object data;
		Node<T> next;

		Node(T data) {
			this.data = data;
		}

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

		boolean isLast() {
			return next == null;
		}

		public void add(T element) {
			if (next == null) {
				next = new Node<>(element);
			} else {
				next.add(element);
			}
		}

		public void removeLast() {
			if (next.isLast())
				next = null;
			else
				next.removeLast();
		}

		@SuppressWarnings("unchecked")
		public T getLast() {
			if (next.isLast())
				return (T) next.data;

			return (T) next.getLast();
		}

		@SuppressWarnings("unchecked")
		public T search(T element) {
			if (next == null)
				return null;

			if (next.data != null && next.data.equals(element))
				return (T) next.data;

			return (T) next.search(element);
		}

		public boolean remove(T element) {
			if (next == null)
				return false;

			if (next.data == element || next.data.equals(element)) {
				next = next.next;
				return true;
			}

			return next.remove(element);
		}

		public boolean removeNode(Node<T> element) {
			if (next == element) {
				next = next.next;
				return true;
			}

			if (next == null) {
				return next.removeNode(element);
			}

			return false;
		}
	}

	@Override
	public void add(T element) {
		if (head == null)
			head = new Node<>(element);
		else
			head.add(element);

		size++;
	}

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

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

	@Override
	public void addFirst(T element) {
		head = new Node<>(element, head);
		size++;
	}

	@Override
	public void removeFirst() {
		if (head == null)
			throw new NoSuchElementException();

		head = head.next;
		size--;
	}

	@Override
	public void removeLast() {
		if (head == null)
			throw new NoSuchElementException();

		if (head.isLast())
			head = null;
		else
			head.removeLast();

		size--;
	}

	@SuppressWarnings("unchecked")
	@Override
	public T getFirst() {
		if (head == null)
			throw new NoSuchElementException();

		return (T) head.data;
	}

	@SuppressWarnings("unchecked")
	@Override
	public T getLast() {
		if (head == null)
			throw new NoSuchElementException();

		if (head.isLast())
			return (T) head.data;

		return (T) head.getLast();
	}

	@SuppressWarnings("unchecked")
	@Override
	public T search(T element) {
		if (head == null)
			return null;

		if (head.data != null && head.data.equals(element))
			return (T) head.data;

		if (head.next != null)
			return (T) head.search(element);

		return null;
	}

	@Override
	public boolean remove(T element) {
		if (head == null)
			return false;

		if (head.data == element || head.data.equals(element)) {
			head = head.next;
			return true;
		}

		if (head.next != null)
			return head.remove(element);

		return false;
	}

	@Override
	public String toString() {
		String result = "[";
		Node<T> current = head;

		while (current != null) {
			result += current.data != null ? current.data.toString() : "null";

			if (current.next != null)
				result += ", ";

			current = current.next;
		}

		result += "]";

		return result;
	}

	@Override
	public Iterator<T> iterator() {
		return new IteratorImpl();
	}

	private class IteratorImpl implements Iterator<T> {

		Node<T> curr = head;
		Node<T> prev = null;
		boolean nextCalled = false;

		@Override
		public boolean hasNext() {
			return curr != null;
		}

		@SuppressWarnings("unchecked")
		@Override
		public T next() {
			if (!hasNext())
				throw new NoSuchElementException();

			prev = curr;
			curr = curr.next;
			nextCalled = true;
			return (T) prev.data;
		}

		@Override
		public void remove() {
			if (!nextCalled)
				throw new IllegalStateException(
						"next() has not been called, or remove() has already been called after the last next()");

			if (head == prev)
				head = head.next;
			else
				head.removeNode(prev);

			prev = null;
			nextCalled = false;

			size--;
		}

	}

	@Override
	public Stream<T> stream() {
		return new StreamImpl<>(this);
	}

	public static void main(String[] args) {
		List<Integer> intList = new ListImpl<>();

		System.out.println("Testing add method (from Container):");
		intList.add(30);
		intList.add(40);
		intList.add(50);
		System.out.println("List after adding elements: " + intList);

		System.out.println("\nTesting addFirst method:");
		intList.addFirst(20);
		intList.addFirst(10);
		System.out.println("List after adding elements at the beginning: " + intList);

		System.out.println("\nTesting getFirst and getLast methods:");
		System.out.println("First element: " + intList.getFirst());
		System.out.println("Last element: " + intList.getLast());

		System.out.println("\nTesting search method:");
		System.out.println("Search for 30: " + intList.search(30));
		System.out.println("Search for 60: " + intList.search(60));

		System.out.println("\nTesting remove method (by element):");
		System.out.println("Before removing 30: " + intList);
		boolean removed = intList.remove(30);
		System.out.println("Element 30 removed: " + removed);
		System.out.println("After removing 30: " + intList);

		removed = intList.remove(100);
		System.out.println("Element 100 removed: " + removed);

		System.out.println("\nTesting removeFirst method:");
		System.out.println("Before removeFirst: " + intList);
		intList.removeFirst();
		System.out.println("After removeFirst: " + intList);

		System.out.println("\nTesting removeLast method:");
		System.out.println("Before removeLast: " + intList);
		intList.removeLast();
		System.out.println("After removeLast: " + intList);
	}
}
