package list.mylinkedlist;

import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.NoSuchElementException;

public class MyLinkedList<E> implements List<E> {
		
	/*
	 * Classe Nodo
	 */
	private static class Node<E> {
		
		// Dati del nodo
		private E data;
		
		// Riferimenti elemento precedente e successivo
		private Node<E> prev;
		private Node<E> next;
		
		// Costruttore
		public Node(E data, Node<E> prev, Node<E> next) {
			this.data = data;
			this.prev = prev;
			this.next = next;
		}
		
	}
	
	private Node<E> head = null;
	private Node<E> tail = null;
	private int size = 0;
	
	// Costruttore
	public MyLinkedList() {}
	
	// Metodi
	
	public boolean isEmpty() {
		return size == 0;
	}
	
	public int size() {
		return size;
	}
	
	public E getFirst() {
		if (head == null) throw new NullPointerException();
		return head.data;
	}
	
	public void addFirst(E item) {
		if (item == null) throw new NullPointerException();
		Node<E> newNode = new Node<E>(item, null, null);
		if (size == 0) {
			head = tail = newNode;
		} else {
			head.prev = newNode;
			newNode.next = head;
			head = newNode;
		}
		size++;
	}
	
	public E removeFirst() {
		if (head == null) throw new NoSuchElementException();
		E tmp = head.data;
		if (size == 1) {
			head = tail = null;
		} else {
			Node<E> firstPlace = head;
			Node<E> secondPlace = head.next;
			
			head = secondPlace;
			secondPlace.prev = null;
			firstPlace.next = null;
		}
		size--;
		return tmp;
	}
	
	public E getLast() {
		if (tail == null) throw new NullPointerException();
		return tail.data;
	}
	
	public E removeLast() {
		if (tail == null) throw new NoSuchElementException();
		E tmp = tail.data;
		if (size == 1) {
			head = tail = null;
		} else {
			Node<E> lastNode = tail;
			Node<E> penultimateNode = tail.prev;
			
			tail = penultimateNode;
			penultimateNode.next = null;
			lastNode.prev = null;
		}
		size--;
		return tmp;
	}
	
	public void addLast(E item) {
		if (item == null) throw new NullPointerException();
		Node<E> newNode = new Node<E>(item, null, null);
		if (size == 0) {
			head = tail = newNode;
		} else {
			Node<E> oldLastNode = tail;
			oldLastNode.next = newNode;
			newNode.prev = oldLastNode;
			tail = newNode;
		}
		size++;
	}
	
	public void clear() {
		head = tail = null;
		size = 0;
	}
	
	public boolean add(E item) {
		if (item == null) throw new NoSuchElementException();
		addLast(item);
		return true;
	}
	
	public void add(int index, E item) {
		if (item == null) throw new NullPointerException();
		if (index < 0 || index > size) throw new IndexOutOfBoundsException();
		// Se l'inserimento è richiesto nella prima posizione
		if (index == 0) {
			addFirst(item);
			return;
		}
		// Se l'inserimento è richiesto nell'ultima posizione
		if (index == size) {
			addLast(item);
			return;
		}
		// Se l'inserimento è richiesto nel generico posto i
		Node<E> newNode = new Node<E>(item, null, null);
		
		Node<E> prevNode = head;
		for (int i = 0; i < index - 1; i++) {
			prevNode = prevNode.next;
		}
		
		Node<E> nextNode = prevNode.next;
		
		prevNode.next = newNode;
		newNode.prev = prevNode;
		newNode.next = nextNode;
		nextNode.prev = newNode;
		
		size++;
	}
	
	public E get(int index) {
		if (index < 0 || index >= size) throw new IndexOutOfBoundsException();
		
		Node<E> currentNode = head;
		for (int i = 0; i < index; i++) {
			currentNode = currentNode.next;
		}
		return currentNode.data;
	}
	
	public E set(int index, E item) {
		if (index < 0 || index >= size) throw new IndexOutOfBoundsException();
		
		Node<E> currentNode = head;
		for (int i = 0; i < index; i++) {
			currentNode = currentNode.next;
		}
		E oldData = currentNode.data;
		currentNode.data = item;
		return oldData;
	}
	
	public String toString() {
		StringBuffer result = new StringBuffer();
		for (Object x : this) result.append(x + " ");
		return result.toString();
	}
	
	public boolean remove(Object object) {
		if (object == null || head == null) return false;
		Node<E> currentNode = head;
		while (currentNode != null) {
			if (currentNode.data.equals(object)) {
				
				// Caso in cui esso sia un primo nodo
				if (currentNode == head) {
					removeFirst();
					return true;
				}
				
				// Caso in cui esso sia un ultimo nodo
				if (currentNode == tail) {
					removeLast();
					return true;
				}
				
				// Caso in cui sia in mezzo
				Node<E> prevNode = currentNode.prev;
				Node<E> nextNode = currentNode.next;
				
				prevNode.next = nextNode;
				nextNode.prev = prevNode;
				currentNode.prev = null;
				currentNode.next = null;
				
				size--;
				
				return true;
			}
			currentNode = currentNode.next;
		}
		return false;
	}
	
	public boolean contains(Object object) {
		if (object == null || head == null) return false;
		Node<E> currentNode = head;
		while (currentNode != null) {
			if (currentNode.data.equals(object)) return true;
			currentNode = currentNode.next;
		}
		return false;
	}
	
	public Object[] toArray() {
		if (head == null) return null;
		Object[] array = new Object[size];
		Node<E> currentNode = head;
		int i = 0;
		while (currentNode != null) {
			array[i++] = currentNode;
			currentNode = currentNode.next;
		}
		return array;
	}
	
	public boolean equals(Object object) {
		return object == this;
	}
	
	// Parte iteratori
	
	public Iterator<E> iterator() {
		
	}
	
	public ListIterator<E> listIterator() {
		
	}
	
	private class MyLinkedListIterator implements ListIterator<E> {
		
		// Variabili adoperate
		private Node<E> nextNode;
		private Node<E> prevNode;
		private Node<E> lastReturned = null;
		private int posNext;
		
		public MyLinkedListIterator() {
			nextNode = head;
			prevNode = null;
			posNext = 0;
		}
		
		public MyLinkedListIterator(int index) {
			if ((index < 0) || (index > size)) throw new IndexOutOfBoundsException();
			// Caso in cui sia il primo
			if (index == 0) {
				nextNode = head;
				prevNode = null;
				posNext = 0;
				return;
			}
			// Caso in cui sia l'ultimo
			if (index == size) {
				nextNode = null;
				prevNode = tail;
				posNext = size;
				return;
			}
			nextNode = head;
			prevNode = null;
			posNext = 0;
			while (posNext < index) {
				nextNode = nextNode.next;
				prevNode = nextNode.prev;
				posNext++;
			}
		}
		
		@Override
		public boolean hasNext() {
			return nextNode != null;
		}
		
		@Override
		public boolean hasPrevious() {
			return prevNode != null;
		}
		
		@Override
		public E next() {
			if (!hasNext()) throw new NoSuchElementException();
			E data = nextNode.data;
			lastReturned = nextNode;
			prevNode = nextNode;
			nextNode = nextNode.next;
			posNext++;
			return data;
		}
		
		@Override
		public E previous() {
			if (!hasPrevious()) throw new NoSuchElementException();
			E data = prevNode.data;
			lastReturned = prevNode;
			nextNode = prevNode;
			prevNode = prevNode.prev;
			posNext--;
			return data;
		}
		
		@Override
		public int nextIndex() {
			return posNext;
		}
		
		@Override
		public int previousIndex() {
			return posNext - 1;
		}
		
		/*
		 * Il metodo remove si occupa della rimozione del nodo appena restituito
		 */
		@Override
		public void remove() {
			// Se non è stato restituito alcun nodo
			if (lastReturned == null) throw new NoSuchElementException();
			// Se l'ultimo elemento ad essere stato restituito è la testa
			if (lastReturned == head) {
				removeFirst();
				nextNode = head;			// Head dopo la rimozione del primo nodo è il successivo a quello appena rimosso
				prevNode = null;
				posNext = 0;
				lastReturned = null;
				return;
			}
			// Se l'ultimo elemento ad essere stato restituito è la coda
			if (lastReturned == tail) {
				removeLast();
				nextNode = null;
				prevNode = tail;
				posNext = size;
				lastReturned = null;
				return;
			}
			// Se è un elemento generico
			Node<E> removingNode = lastReturned;
			Node<E> nNode = removingNode.next;
			Node<E> pNode = removingNode.prev;
			
			pNode.next = nNode;
			nNode.prev = pNode;
			removingNode.prev = null;
			removingNode.next = null;
			
			if (lastReturned == prevNode) posNext--;
			
			nextNode = nNode;
			prevNode = pNode;
			size--;
			lastReturned = null;
		}
		
		/*
		 * Quando viene richiamata la add, l'elemento viene aggiunto a sinistra del cursore.
		 * Esempio di seguito:
		 * A | B
		 * chiamata add(X)
		 * A X | B
		 * Come detto prima la X viene aggiunta a sinistra
		 */
		@Override
		public void add(E data) {
			if (data == null) throw new NullPointerException();
			Node<E> newNode = new Node<E>(data, null, null);
			// Se la lista è vuota
			if (size == 0) {
				tail = head = new Node<E>(data, null, null);
				size++;
				prevNode = 
			}
		}
		
	}
	
}
