import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
import java.util.NoSuchElementException;


/**
 * Comprehensive test suite for the dynamic array lab. These tests provide full coverage of AList
 * functionality including basic operations, edge cases, error conditions, and performance
 * characteristics.
 *
 * @author CS 240 Instructors
 * @version V2.0, 9/7/2025
 *
 */
class AListTest {


  /**
   * This method is a hack that allows grab the length of the array for the purposes of testing.
   *
   * @param list - An AList object
   * @return The length of the underlying array
   */
  @SuppressWarnings("rawtypes")
  private static int getArraySize(AList list) {
    return ((Object[]) list.listArray).length;
  }

  // -------------------------
  // TEST METHODS FOR APPPEND
  // -------------------------
  @Test
  public void testAppendNoResizeNeeded() {
    AList<String> list = new AList<>(2);
    list.append("A");
    list.append("B");

    assertEquals(0, list.currPos());
    assertEquals(2, list.length());

    assertEquals("A", list.getValue());
    list.moveToPos(1);
    assertEquals("B", list.getValue());

    assertEquals(2, getArraySize(list));

  }

  @Test
  public void testAppenResizeNeeded() throws IllegalArgumentException, IllegalAccessException,
      NoSuchFieldException, SecurityException {
    AList<String> list = new AList<>(2);
    list.append("A");
    list.append("B");
    list.append("C");

    assertEquals(0, list.currPos());
    assertEquals(3, list.length());

    assertEquals("A", list.getValue());
    list.moveToPos(1);
    assertEquals("B", list.getValue());
    list.moveToPos(2);
    assertEquals("C", list.getValue());

    assertEquals(4, getArraySize(list));
  }

  @Test
  public void testAppendTwoResizesNeeded() throws IllegalArgumentException, IllegalAccessException,
      NoSuchFieldException, SecurityException {
    AList<String> list = new AList<>(3);
    for (int i = 0; i < 7; i++) {
      list.append("A");
    }

    assertEquals(0, list.currPos());
    assertEquals(7, list.length());

    for (list.moveToStart(); !list.isAtEnd(); list.next()) {
      assertEquals("A", list.getValue());
    }

    assertEquals(12, getArraySize(list));
  }

  // -------------------------
  // TEST METHODS FOR INSERT
  // -------------------------

  @Test
  public void testInsertNoResizeNeeded() throws IllegalArgumentException, IllegalAccessException,
      NoSuchFieldException, SecurityException {
    AList<String> list = new AList<>(2);
    list.insert("A");
    list.insert("B");

    assertEquals(0, list.currPos());
    assertEquals(2, list.length());

    assertEquals("B", list.getValue());
    list.moveToPos(1);
    assertEquals("A", list.getValue());

    assertEquals(2, getArraySize(list));


  }

  @Test
  public void testInsertOneResizeNeeded() throws IllegalArgumentException, IllegalAccessException,
      NoSuchFieldException, SecurityException {
    AList<String> list = new AList<>(2);
    list.insert("A");
    list.insert("B");
    list.insert("C");

    assertEquals(0, list.currPos());
    assertEquals(3, list.length());

    assertEquals("C", list.getValue());
    list.moveToPos(1);
    assertEquals("B", list.getValue());
    list.moveToPos(2);
    assertEquals("A", list.getValue());

    assertEquals(4, getArraySize(list));
  }

  @Test
  public void testInsertTwoResizesNeeded() throws IllegalArgumentException, IllegalAccessException,
      NoSuchFieldException, SecurityException {
    AList<String> list = new AList<>(3);
    for (int i = 0; i < 7; i++) {
      list.insert("A");
    }

    assertEquals(0, list.currPos());
    assertEquals(7, list.length());

    for (list.moveToStart(); !list.isAtEnd(); list.next()) {
      assertEquals("A", list.getValue());
    }

    assertEquals(12, getArraySize(list));
  }

  // -------------------------
  // TEST METHODS FOR REMOVE
  // -------------------------
  @Test
  public void testRemoveResizeNeeded() throws IllegalArgumentException, IllegalAccessException,
      NoSuchFieldException, SecurityException {
    AList<String> list = new AList<>(3);
    for (int i = 0; i < 13; i++) {
      list.insert("A");
    }
    assertEquals(24, getArraySize(list));

    for (int i = 0; i < 11; i++) { // this should trigger a resize
      list.remove();
    }

    assertEquals(0, list.currPos());
    assertEquals(2, list.length());

    for (list.moveToStart(); !list.isAtEnd(); list.next()) {
      assertEquals("A", list.getValue());
    }

    assertTrue(getArraySize(list) < 24);
  }

  // -------------------------
  // TEST METHODS FOR ASSIGNMENT COUNTING
  // -------------------------

  @Test
  public void testAssignmentCountingInitial() {
    AList<String> list = new AList<>(5);
    assertEquals(0, list.getAssignmentCount());

    list = new AList<>();
    assertEquals(0, list.getAssignmentCount());
  }

  @Test
  public void testAssignmentCountingAppendNoResize() {
    AList<String> list = new AList<>(3);
    assertEquals(0, list.getAssignmentCount());

    list.append("A");
    assertEquals(1, list.getAssignmentCount());

    list.append("B");
    assertEquals(2, list.getAssignmentCount());

    list.append("C");
    assertEquals(3, list.getAssignmentCount());
  }

  @Test
  public void testAssignmentCountingAppendWithResize() {
    AList<String> list = new AList<>(2);
    assertEquals(0, list.getAssignmentCount());

    list.append("A");
    assertEquals(1, list.getAssignmentCount());

    list.append("B");
    assertEquals(2, list.getAssignmentCount());

    // This should trigger a resize
    list.append("C");
    // 1 assignment for the new element + 2 assignments for copying during resize
    assertEquals(5, list.getAssignmentCount());
  }

  @Test
  public void testAssignmentCountingInsertNoResize() {
    AList<String> list = new AList<>(4);
    list.append("A");
    list.append("B");
    list.moveToPos(1);

    int countBefore = list.getAssignmentCount();
    list.insert("C");

    // Insert at position 1 should shift 1 element and add 1 new element
    assertEquals(countBefore + 2, list.getAssignmentCount());
  }

  @Test
  public void testAssignmentCountingInsertWithResize() {
    AList<String> list = new AList<>(2);
    list.append("A");
    list.append("B");
    list.moveToStart();

    int countBefore = list.getAssignmentCount();
    list.insert("C");

    // Should trigger efficient resize: 3 assignments total
    // (1 for copying "A", 1 for inserting "C", 1 for copying "B")
    assertEquals(countBefore + 3, list.getAssignmentCount());
  }

  @Test
  public void testAssignmentCountingRemove() {
    AList<String> list = new AList<>(5);
    list.append("A");
    list.append("B");
    list.append("C");
    list.append("D");
    list.moveToPos(1);

    int countBefore = list.getAssignmentCount();
    list.remove(); // Remove "B"

    // Should shift 2 elements (C and D) down
    assertEquals(countBefore + 3, list.getAssignmentCount());
  }

  @Test
  public void testAssignmentCountingRemoveWithResize() {
    AList<String> list = new AList<>(2);
    // Fill the list and trigger resizes to get a larger array
    for (int i = 0; i < 10; i++) {
      list.append("Item" + i);
    }

    // Now remove enough items to trigger a downsize
    list.moveToStart();
    int countBefore = list.getAssignmentCount();

    // Remove items until we trigger a resize down
    // Array should be size 16 with 10 items, need to get below 4 items to trigger resize
    for (int i = 0; i < 6; i++) {
      list.remove();
      assertEquals(countBefore + (10 - i), list.getAssignmentCount());
      countBefore += (10 - i);
    }

    list.remove(); // should trigger a resize

    // Efficient resize: only 3 assignments to copy the remaining 3 elements to the smaller array
    assertEquals(countBefore + 3, list.getAssignmentCount());

    assertEquals(8, getArraySize(list)); // Verify resize occurred
  }


  @Test
  public void testAssignmentCountingSequentialOperations() {
    AList<Integer> list = new AList<>(2);

    // Start with 0 assignments
    assertEquals(0, list.getAssignmentCount());

    // Add first element
    list.append(1);
    assertEquals(1, list.getAssignmentCount());

    // Add second element (no resize)
    list.append(2);
    assertEquals(2, list.getAssignmentCount());

    // Add third element (triggers efficient resize)
    list.append(3);
    assertEquals(5, list.getAssignmentCount()); // 1 new + 2 copied during resize

    // Insert at beginning (triggers efficient resize)
    list.moveToStart();
    list.insert(0);
    assertEquals(9, list.getAssignmentCount()); // 4 assignments for efficient resize

    // Remove from middle (shifts some elements)
    list.moveToPos(2);
    list.remove();
    assertEquals(11, list.getAssignmentCount()); // 2 shifted
  }

  // -------------------------
  // COMPREHENSIVE TESTS FOR FULL COVERAGE
  // -------------------------

  // Constructor and Initialization Tests
  @Test
  public void testDefaultConstructor() {
    AList<String> list = new AList<>();
    assertEquals(0, list.length());
    assertEquals(0, list.currPos());
    assertTrue(list.isEmpty());
    assertTrue(list.isAtEnd());
    assertEquals(10, getArraySize(list)); // Default size
  }

  @Test
  public void testParameterizedConstructor() {
    AList<Integer> list = new AList<>(5);
    assertEquals(0, list.length());
    assertEquals(0, list.currPos());
    assertTrue(list.isEmpty());
    assertTrue(list.isAtEnd());
    assertEquals(5, getArraySize(list));
  }

  @Test
  public void testClear() {
    AList<String> list = new AList<>(3);
    list.append("A");
    list.append("B");
    list.moveToPos(1);

    assertFalse(list.isEmpty());
    assertEquals(2, list.length());
    assertEquals(1, list.currPos());

    list.clear();
    assertEquals(0, list.length());
    assertEquals(0, list.currPos());
    assertTrue(list.isEmpty());
    assertTrue(list.isAtEnd());
  }

  // Movement and Position Tests
  @Test
  public void testMoveToStart() {
    AList<String> list = new AList<>();
    list.append("A");
    list.append("B");
    list.append("C");
    list.moveToEnd();
    assertEquals(3, list.currPos());

    list.moveToStart();
    assertEquals(0, list.currPos());
    assertEquals("A", list.getValue());
  }

  @Test
  public void testMoveToEnd() {
    AList<String> list = new AList<>();
    list.append("A");
    list.append("B");
    list.append("C");
    assertEquals(0, list.currPos());

    list.moveToEnd();
    assertEquals(3, list.currPos());
    assertTrue(list.isAtEnd());
  }

  @Test
  public void testNext() {
    AList<String> list = new AList<>();
    list.append("A");
    list.append("B");
    list.append("C");

    assertEquals(0, list.currPos());
    assertEquals("A", list.getValue());

    list.next();
    assertEquals(1, list.currPos());
    assertEquals("B", list.getValue());

    list.next();
    assertEquals(2, list.currPos());
    assertEquals("C", list.getValue());

    list.next();
    assertEquals(3, list.currPos());
    assertTrue(list.isAtEnd());

    // Next at end should not change position
    list.next();
    assertEquals(3, list.currPos());
  }

  @Test
  public void testPrev() {
    AList<String> list = new AList<>();
    list.append("A");
    list.append("B");
    list.append("C");
    list.moveToEnd();

    assertEquals(3, list.currPos());

    list.prev();
    assertEquals(2, list.currPos());
    assertEquals("C", list.getValue());

    list.prev();
    assertEquals(1, list.currPos());
    assertEquals("B", list.getValue());

    list.prev();
    assertEquals(0, list.currPos());
    assertEquals("A", list.getValue());

    // Prev at start should not change position
    list.prev();
    assertEquals(0, list.currPos());
  }

  @Test
  public void testMoveToPos() {
    AList<String> list = new AList<>();
    list.append("A");
    list.append("B");
    list.append("C");

    assertTrue(list.moveToPos(0));
    assertEquals(0, list.currPos());
    assertEquals("A", list.getValue());

    assertTrue(list.moveToPos(2));
    assertEquals(2, list.currPos());
    assertEquals("C", list.getValue());

    assertTrue(list.moveToPos(3));
    assertEquals(3, list.currPos());
    assertTrue(list.isAtEnd());

    // Test invalid positions
    assertFalse(list.moveToPos(-1));
    assertEquals(3, list.currPos()); // Position should not change

    assertFalse(list.moveToPos(4));
    assertEquals(3, list.currPos()); // Position should not change
  }

  @Test
  public void testIsAtEnd() {
    AList<String> list = new AList<>();
    assertTrue(list.isAtEnd()); // Empty list

    list.append("A");
    assertFalse(list.isAtEnd()); // At position 0 of 1 element

    list.moveToEnd();
    assertTrue(list.isAtEnd()); // At position 1 of 1 element

    list.append("B");
    assertFalse(list.isAtEnd()); // At position 1 of 2 elements
  }

  // Error Condition Tests
  @Test
  public void testGetValueEmpty() {
    AList<String> list = new AList<>();
    assertThrows(NoSuchElementException.class, () -> list.getValue());
  }

  @Test
  public void testGetValueAtEnd() {
    AList<String> list = new AList<>();
    list.append("A");
    list.moveToEnd();
    assertThrows(NoSuchElementException.class, () -> list.getValue());
  }

  @Test
  public void testRemoveEmpty() {
    AList<String> list = new AList<>();
    assertThrows(NoSuchElementException.class, () -> list.remove());
  }

  @Test
  public void testRemoveAtEnd() {
    AList<String> list = new AList<>();
    list.append("A");
    list.moveToEnd();
    assertThrows(NoSuchElementException.class, () -> list.remove());
  }

  @Test
  public void testRemoveAtInvalidPosition() {
    AList<String> list = new AList<>();
    list.append("A");
    list.moveToPos(-1); // This should fail, so position stays 0
    assertEquals("A", list.remove()); // Should work

    // Now list is empty but position is still 0
    assertThrows(NoSuchElementException.class, () -> list.remove());
  }

  // Complex Operation Tests
  @Test
  public void testInsertAtDifferentPositions() {
    AList<String> list = new AList<>(5);

    // Insert at empty list
    list.insert("B");
    assertEquals("B", list.getValue());
    assertEquals(0, list.currPos());
    assertEquals(1, list.length());

    // Insert at beginning
    list.moveToStart();
    list.insert("A");
    assertEquals("A", list.getValue());
    assertEquals(0, list.currPos());
    assertEquals(2, list.length());

    // Insert in middle
    list.moveToPos(1);
    list.insert("A.5");
    assertEquals("A.5", list.getValue());
    assertEquals(1, list.currPos());
    assertEquals(3, list.length());

    // Insert at end
    list.moveToEnd();
    list.insert("C");
    assertEquals("C", list.getValue());
    assertEquals(3, list.currPos());
    assertEquals(4, list.length());

    // Verify final order: A, A.5, B, C
    list.moveToStart();
    assertEquals("A", list.getValue());
    list.next();
    assertEquals("A.5", list.getValue());
    list.next();
    assertEquals("B", list.getValue());
    list.next();
    assertEquals("C", list.getValue());
  }

  @Test
  public void testRemoveAtDifferentPositions() {
    AList<String> list = new AList<>();
    list.append("A");
    list.append("B");
    list.append("C");
    list.append("D");

    // Remove from middle
    list.moveToPos(1);
    assertEquals("B", list.remove());
    assertEquals(3, list.length());
    assertEquals("C", list.getValue()); // Should now be at position 1

    // Remove from beginning
    list.moveToStart();
    assertEquals("A", list.remove());
    assertEquals(2, list.length());
    assertEquals("C", list.getValue()); // Should still be at position 0

    // Remove from end (last element)
    list.moveToPos(1);
    assertEquals("D", list.remove());
    assertEquals(1, list.length());
    assertTrue(list.isAtEnd()); // Position should be at end now

    // Remove last element
    list.moveToStart();
    assertEquals("C", list.remove());
    assertEquals(0, list.length());
    assertTrue(list.isEmpty());
  }

  @Test
  public void testMixedOperations() {
    AList<Integer> list = new AList<>(3);

    // Build list: 1, 2, 3
    list.append(1);
    list.append(2);
    list.append(3);

    // Insert 0 at beginning: 0, 1, 2, 3
    list.moveToStart();
    list.insert(0);
    assertEquals(4, list.length());

    // Remove from position 2: 0, 1, 3
    list.moveToPos(2);
    assertEquals(Integer.valueOf(2), list.remove());
    assertEquals(3, list.length());
    assertEquals(Integer.valueOf(3), list.getValue());

    // Append 4: 0, 1, 3, 4
    list.append(4);
    assertEquals(4, list.length());

    // Insert 2 at position 2: 0, 1, 2, 3, 4
    list.moveToPos(2);
    list.insert(2);
    assertEquals(5, list.length());

    // Verify final sequence
    list.moveToStart();
    for (int i = 0; i < 5; i++) {
      assertEquals(Integer.valueOf(i), list.getValue());
      if (i < 4)
        list.next();
    }
  }

  @Test
  public void testIterationPattern() {
    AList<String> list = new AList<>();
    String[] items = {"A", "B", "C", "D", "E"};

    // Add items
    for (String item : items) {
      list.append(item);
    }

    // Test forward iteration
    list.moveToStart();
    int index = 0;
    while (!list.isAtEnd()) {
      assertEquals(items[index], list.getValue());
      index++;
      list.next();
    }
    assertEquals(items.length, index);

    // Test backward iteration
    list.prev(); // Move to last element
    index = items.length - 1;
    while (list.currPos() >= 0) {
      assertEquals(items[index], list.getValue());
      index--;
      if (list.currPos() > 0) {
        list.prev();
      } else {
        break;
      }
    }
    assertEquals(-1, index);
  }

  @Test
  public void testLargeDataSet() {
    AList<Integer> list = new AList<>(10);
    final int SIZE = 1000;

    // Test large append operations
    for (int i = 0; i < SIZE; i++) {
      list.append(i);
    }
    assertEquals(SIZE, list.length());

    // Verify all elements
    list.moveToStart();
    for (int i = 0; i < SIZE; i++) {
      assertEquals(Integer.valueOf(i), list.getValue());
      if (i < SIZE - 1)
        list.next();
    }

    // Test large removal operations
    list.moveToStart();
    for (int i = 0; i < SIZE / 2; i++) {
      assertEquals(Integer.valueOf(i), list.remove());
    }
    assertEquals(SIZE / 2, list.length());

    // Verify remaining elements
    list.moveToStart();
    for (int i = SIZE / 2; i < SIZE; i++) {
      assertEquals(Integer.valueOf(i), list.getValue());
      if (i < SIZE - 1)
        list.next();
    }
  }

  @Test
  public void testEdgeCasesSmallArrays() {
    // Test with minimum size array
    AList<String> list = new AList<>(1);
    assertEquals(1, getArraySize(list));

    list.append("A");
    assertEquals("A", list.getValue());
    assertEquals(1, list.length());

    // This should trigger resize
    list.append("B");
    assertEquals(2, getArraySize(list));
    assertEquals(2, list.length());

    // Remove to trigger downsize
    list.moveToStart();
    list.remove();
    list.remove();
    assertEquals(0, list.length());
    assertTrue(list.isEmpty());
  }

  @Test
  public void testAssignmentCountingClear() {
    AList<String> list = new AList<>(3);
    list.append("A");
    list.append("B");
    list.append("C");

    int countBefore = list.getAssignmentCount();
    list.clear();

    // Clear should not affect assignment count
    assertEquals(countBefore, list.getAssignmentCount());
  }

}
