package org.academiadecodigo.bootcamp.containers;

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

public class LinkedListTest {
    public static void main(String[] args) {

        LinkedListTest test = new LinkedListTest();
        test.assertCondition("Add data to the list", test.testAdd());
        test.assertCondition("Index data from the list", test.testIndex());
        test.assertCondition("Get data from the list", test.testGet());
        test.assertCondition("Remove data from the list", test.testRemove());
        test.assertCondition("Iterator on list", test.testIterator());

    }

    /**
     * Tests insertion of data objects into the list
     *
     * @return true if all tests passed
     */
    public boolean testAdd() {

        // Preparation
        LinkedList<Integer> integerList = new LinkedList<>();

        // Empty list should contain zero elements
        if (integerList.size() != 0) {
            return false;
        }

        // List with one element should have size of one
        integerList.add(1);
        if (integerList.size() != 1) {
            return false;
        }

        // List with two elements should have size of two
        integerList.add(2);
        if (integerList.size() != 2) {
            return false;
        }

        // List with three elements should have size of three
        integerList.add(3);
        if (integerList.size() != 3) {
            return false;
        }

        // List with four elements should have size of four
        integerList.add(2, 4);
        if (integerList.size() != 4) {
            return false;
        }

        return true;

    }

    /**
     * Tests obtaining the index of data objects from the list
     *
     * @return true if all tests passed
     */
    public boolean testIndex() {

        // Preparation
        LinkedList<Integer> list = new LinkedList<>();
        Integer data1 = 1;
        Integer data2 = 2;
        Integer data3 = 3;
        Integer data4 = 4;

        // Tests with empty list
        if (list.indexOf(data1) != -1) {
            return false;
        }

        // Tests with one element
        list.add(data1);
        if (list.indexOf(data1) != 0) {
            return false;
        }

        if (list.indexOf(data2) != -1) {
            return false;
        }

        // Tests with two elements
        list.add(data2);
        if (list.indexOf(data1) != 0) {
            return false;
        }

        if (list.indexOf(data2) != 1) {
            return false;
        }

        if (list.indexOf(data3) != -1) {
            return false;
        }

        // Tests with three elements
        list.add(data3);
        if (list.indexOf(data1) != 0) {
            return false;
        }

        if (list.indexOf(data2) != 1) {
            return false;
        }

        if (list.indexOf(data3) != 2) {
            return false;
        }

        if (list.indexOf(data4) != -1) {
            return false;
        }

        // Tests with four elements
        list.add(2, data4);
        if (list.indexOf(data1) != 0) {
            return false;
        }

        if (list.indexOf(data2) != 1) {
            return false;
        }

        if (list.indexOf(data4) != 2) {
           return false;
        }

        if (list.indexOf(data3) != 3) {
            return false;
        }

        return true;
    }

    /**
     * Tests getting data objects from the list
     *
     * @return true if all tests passed
     */
    public boolean testGet() {

        // Preparation
        LinkedList<Integer> list = new LinkedList<>();
        Integer data1 = 1;
        Integer data2 = 2;
        Integer data3 = 3;
        Integer data4 = 4;

        // Tests with empty list
        if (list.get(0) != null) {
            return false;
        }

        if (list.get(1) != null) {
            return false;
        }

        if (list.get(9) != null) {
            return false;
        }

        if (list.get(-1) != null) {
            return false;
        }

        // Tests with one element
        list.add(data1);
        if (!data1.equals(list.get(0))) {
            return false;
        }

        if (list.get(1) != null) {
            return false;
        }

        if (list.get(9) != null) {
            return false;
        }

        if (!data1.equals(list.get(-1))) {
            return false;
        }

        // Tests with two elements
        list.add(data2);
        if (!data1.equals(list.get(0))) {
            return false;
        }

        if (!data2.equals(list.get(1))) {
            return false;
        }

        if (list.get(2) != null) {
            return false;
        }

        if (list.get(9) != null) {
            return false;
        }

        if (!data2.equals(list.get(-1))) {
            return false;
        }

        if (!data1.equals(list.get(-2))) {
            return false;
        }

        // Tests with three elements
        list.add(data3);
        if (!data1.equals(list.get(0))) {
            return false;
        }

        if (!data2.equals(list.get(1))) {
            return false;
        }

        if (!data3.equals(list.get(2))) {
            return false;
        }

        if (list.get(3) != null) {
            return false;
        }

        if (list.get(9) != null) {
            return false;
        }

        // Tests with four elements and indexed add
        list.add(2, data4);
        if (!data1.equals(list.get(0))) {
            return false;
        }

        if (!data2.equals(list.get(1))) {
            return false;
        }

        if (!data4.equals(list.get(2))) {
            return false;
        }

        if (!data3.equals(list.get(3))) {
            return false;
        }

        return true;

    }

    /**
     * Tests removing data objects from the list
     *
     * @return true if all tests passed
     */
    public boolean testRemove() {

        // Preparation
        LinkedList<Integer> list = new LinkedList<>();
        Integer data1 = 1;
        Integer data2 = 2;
        Integer data3 = 3;
        Integer data4 = 4;

        // Test removal with empty list
        if (list.remove(data1)) {
            return false;
        }

        // Test removal from list with one element
        list.add(data1);

        if (list.remove(data2)) {
            return false;
        }

        if (!list.remove(data1)) {
            return false;
        }

        if (list.size() != 0) {
            return false;
        }

        // Test removal from list with two elements
        list.add(data1);
        list.add(data2);

        if (list.remove(data3)) {
            return false;
        }

        if (!list.remove(data1)) {
            return false;
        }

        if (list.size() != 1) {
            return false;
        }

        list.add(data1);
        if (!list.remove(data1)) {
            return false;
        }

        if (!list.remove(data2)) {
            return false;
        }

        if (list.size() != 0) {
            return false;
        }

        // Test removal from list with three elements
        list.add(data1);
        list.add(data2);
        list.add(data3);

        if (list.remove(data4)) {
            return false;
        }

        if (!list.remove(data2)) {
            return false;
        }

        if (list.size() != 2) {
            return false;
        }

        if (list.indexOf(data1) != 0) {
            return false;
        }

        if (list.indexOf(data3) != 1) {
            return false;
        }

        if (!list.remove(data3)) {
            return false;
        }

        if (list.size() != 1) {
            return false;
        }

        // Test indexed removal from list
        list.add(data2);

        if (list.remove(2)) {
            return false;
        }

        if (!list.remove(0)) {
            return false;
        }

        if (list.size() != 1) {
            return false;
        }

        list.add(0, data1);
        if (!list.remove(1)) {
            return false;
        }

        if (!list.remove(0)) {
            return false;
        }

        if (list.size() != 0) {
            return false;
        }

        return true;
    }

    public boolean testIterator() {
        // Preparation
        LinkedList<Integer> list = new LinkedList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);

        Iterator<Integer> it = list.iterator();
        if (!it.hasNext()) {
            return false;
        }

        if (!it.next().equals(1)) {
            return false;
        }

        if (!it.next().equals(2)) {
            return false;
        }

        if (!it.next().equals(3)) {
            return false;
        }

        if (!it.next().equals(4)) {
            return false;
        }

        try {
            it.next();
            return false;
        }
        catch (NoSuchElementException e) {
        }

        Iterator<Integer> it1 = list.iterator();
        try {
            // Removing before the first next() should fail
            it1.remove();
            return false;
        }
        catch (IllegalStateException e) {}

        it1.next(); // 1
        it1.next(); // 2
        try {
            it1.remove(); // 2 is gone
        }
        catch (IllegalStateException e) {
            return false;
        }

        try {
            // Consecutive remove should fail
            it1.remove();
            return false;
        }
        catch (IllegalStateException e) {}

        it1.next(); // 3
        it1.next(); // 4

        Iterator<Integer> it2 = list.iterator();
        for (int i = 0; i < list.size(); i++) {
            if (!it2.next().equals(list.get(i)))
                return false;
        }

        Iterator<Integer> it3 = list.iterator();
        if (!it3.next().equals(1)) {
            return false;
        }

        if (!it3.next().equals(3)) {
            return false;
        }

        if (!it3.next().equals(4)) {
            return false;
        }

        for (Integer i : list) {
            if (i == null || i == list.get(list.indexOf(i)).intValue()) {
                return false;
            }
        }

        return true;
    }

    private void assertCondition(String test, boolean result) {

        System.out.println(test + ": " + (result ? "OK" : "FAIL"));

    }

}
