import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Random;

/**
 * Double Linked List Tester Class.
 * @author Daniel Pade
 */
public class Driver {
  private Class listClass;
  private Class nodeClass;

  public Driver() {
    listClass = DoubleLinkedList.class;
    nodeClass = null;
  }

  /**
   * Entry point.
   */
  public static void main(String[] args) {
    Driver driver = new Driver();
    if (!driver.testInternalClass()) {
      System.err.println("Problems with internal class");
      System.err.println("FATAL ERROR FOUND");
      System.exit(1);
    }

    if (!driver.testFields()) {
      System.err.println("Problems with field names");
      System.err.println("FATAL ERROR FOUND");
      System.exit(1);
    }

    // Generate a large random list
    System.err.println("Generating lists");
    Random rand = new Random();
    ArrayList<Integer> masterList = new ArrayList<Integer>();
    DoubleLinkedList<Integer> testList = new DoubleLinkedList<Integer>();
    for (int i = 0; i < 10000; i++) {
      Integer randInt = rand.nextInt();
      masterList.add(randInt);
      testList.append(randInt);
    }

    System.err.println();
    compareLastField(testList, driver);

    System.err.println();
    compareMasterList(masterList, testList);

    int prevSize = testList.size();
    System.err.println();
    testDeletion(rand, masterList, testList);
    if (prevSize <= testList.size()) {
      System.err.println("List didn't shrink!");
      System.exit(1);
    }

    System.err.println();
    compareLastField(testList, driver);

    System.err.println();
    compareMasterList(masterList, testList);

    System.err.println();
    System.err.println("Testing 'inList' method");
    testInListMethod();
  }

  /**
   * Test the method to query the list for a specific item.
   */
  public static void testInListMethod() {
    DoubleLinkedList<Integer> testList = new DoubleLinkedList<Integer>();
    Random rand = new Random();
    ArrayList<Integer> masterList = new ArrayList<Integer>();
    for (int i = 0; i < 10; i++) {
      Integer randInt = rand.nextInt(10);
      masterList.add(randInt);
      testList.append(randInt);
    }

    int randomIndex = rand.nextInt(masterList.size());
    if (!testList.inList(masterList.get(randomIndex))) {
      System.err.println("Failure");
      return;
    }

    int randomLarge = rand.nextInt(10) + 10;
    int randomSmall = rand.nextInt(10) - 10;
    if (testList.inList(randomLarge) || testList.inList(randomSmall)) {
      System.err.println("Failure");
      return;
    }
    System.err.println("Success");
  }


  /**
   * Compare the element in the 'last' member variable of testList
   * to the element at the index (size - 1).
   */
  public static void compareLastField(DoubleLinkedList<Integer> testList, Driver driver) {
    System.err.println("Checking prev elements:");
    Integer lastElement = getValueOfLast(driver, testList);
    Integer lastIndexedElement = testList.get(testList.size() - 2);
    if (lastElement.equals(lastIndexedElement)) {
      System.err.println("Items match!");
    } else {
      System.err.println("Problems found");
    }
  }

  /**
   * Compare elements in the master list to the test list.
   */
  public static void compareMasterList(ArrayList master, DoubleLinkedList<Integer> test) {
    System.err.println("Comparing ArrayList to your List:");
    boolean match = true;
    for (int i = 0; i < test.size(); i++) {
      if (!test.get(i).equals(master.get(i))) {
        match = false;
        break;
      }
    }
    if (match) {
      System.err.println("Items match!");
    } else {
      System.err.println("Problems found");
    }
  }

  /**
   * Test deleting elements (randomized).
   */
  public static void testDeletion(Random rand, ArrayList master, DoubleLinkedList<Integer> test) {
    System.err.println("Deleting random elements");
    int numElements = rand.nextInt(1000) + 100;
    for (int i = 0; i < numElements; i++) {
      int index = rand.nextInt(test.size());
      test.delete(index);
      master.remove(index);
    }
  }

  /**
   * Get the value of the 'last' field within the specified list.
   */
  public static Integer getValueOfLast(Driver driver, DoubleLinkedList<Integer> testList) {
    Field lastField = null;
    Field prevField = null;
    try {
      lastField = driver.listClass.getDeclaredField("last");
      prevField = lastField.getType().getDeclaredField("prev");
    } catch (NoSuchFieldException e) {
      System.err.println("Need a 'last' member variable");
      System.exit(1);
    }
    lastField.setAccessible(true);

    Field lastElemField = null;
    try {
      lastElemField = driver.nodeClass.getDeclaredField("elem");
    } catch (NoSuchFieldException e) {
      System.err.println("Need a 'elem' member variable within Node");
      System.exit(1);
    }
    lastElemField.setAccessible(true);
    prevField.setAccessible(true);

    Integer lastInt = null;
    try {
      lastInt = (Integer) lastElemField.get(prevField.get(lastField.get(testList)));
    } catch (IllegalAccessException e) {
      System.err.println("Driver Bug: Unable to continue");
      System.exit(1);
    }
    return lastInt;
  }

  /**
   * Test the names and members of the internal node class.
   */
  public boolean testInternalClass() {
    boolean ranClean = true;
    Class<?>[] internalClasses = listClass.getDeclaredClasses();
    if (internalClasses.length != 1) {
      System.err.println("Should have exactly 1 internal class (Node<E>)");
      ranClean = false;
    }
    for (Class<?> internalClass : internalClasses) {
      nodeClass = internalClass;
      if (!internalClass.getName().contains("DoubleLinkedList$Node")) {
        System.err.println("Internal class should be named 'Node'");
        ranClean = false;
      }
      ranClean = testInternalClassMembers(internalClass);
    }
    return ranClean;
  }

  /**
   * Test the member variables associated with the internal class.
   */
  public boolean testInternalClassMembers(Class<?> internalClass) {
    boolean ranClean = true;
    Field[] fields = internalClass.getDeclaredFields();
    for (Field field : fields) {
      String name = field.getName();
      Class type = field.getType();
      if (name.contains("this")) {
        continue;
      } else if (!(name.equals("elem") || name.equals("next") || name.equals("prev"))) {
        System.err.println("Invalid member in internal class");
        ranClean = false;
        break;
      }
    }
    return ranClean;
  }

  /**
   * Test the member variables associated with the main class.
   */
  public boolean testFields() {
    boolean ranClean = true;
    Field[] fields = listClass.getDeclaredFields();
    for (Field field : fields) {
      String fieldName = field.getName();
      if (! (fieldName.equals("head") || fieldName.equals("size") || fieldName.equals("last"))) {
        ranClean = false;
        System.err.println("Need a 'head', 'size', and 'tail' member variable");
        break;
      }
    }
    return ranClean;
  }
}
