import java.util.*;

/**
 * CollectionsDemo.java
 * Demonstrates the Java Collections Framework for MSc students.
 * Covers: ArrayList, LinkedList, HashSet, TreeSet, HashMap, TreeMap,
 *         PriorityQueue, Iterator, Collections.sort(), Comparator,
 *         and proper equals()/hashCode() overriding.
 *
 * Compile: javac CollectionsDemo.java
 * Run:     java CollectionsDemo
 */
public class CollectionsDemo {

    // --- Inner class with equals()/hashCode() override ---
    static class Student implements Comparable<Student> {
        private String name;
        private int id;
        private double gpa;

        public Student(String name, int id, double gpa) {
            this.name = name;
            this.id = id;
            this.gpa = gpa;
        }

        public String getName() { return name; }
        public int getId() { return id; }
        public double getGpa() { return gpa; }

        // Two students are equal if they share the same id
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Student student = (Student) o;
            return id == student.id;
        }

        // hashCode must be consistent with equals
        @Override
        public int hashCode() {
            return Objects.hash(id);
        }

        // Natural ordering: by id ascending
        @Override
        public int compareTo(Student other) {
            return Integer.compare(this.id, other.id);
        }

        @Override
        public String toString() {
            return String.format("Student{id=%d, name='%s', gpa=%.2f}", id, name, gpa);
        }
    }

    public static void main(String[] args) {

        // ============================================================
        // 1. ArrayList - dynamic array, fast random access
        // ============================================================
        System.out.println("=== ArrayList Demo ===");
        ArrayList<String> fruits = new ArrayList<>();
        fruits.add("Mango");
        fruits.add("Banana");
        fruits.add("Apple");
        fruits.add("Orange");
        fruits.add("Banana"); // duplicates allowed
        System.out.println("Fruits: " + fruits);

        fruits.remove("Banana"); // removes first occurrence
        System.out.println("After removing Banana: " + fruits);
        System.out.println("Element at index 1: " + fruits.get(1));

        // Iterating with an Iterator
        System.out.print("Iterator walk: ");
        Iterator<String> it = fruits.iterator();
        while (it.hasNext()) {
            System.out.print(it.next() + " ");
        }
        System.out.println("\n");

        // ============================================================
        // 2. LinkedList - doubly-linked, fast insert/remove at ends
        // ============================================================
        System.out.println("=== LinkedList Demo ===");
        LinkedList<Integer> numbers = new LinkedList<>();
        numbers.add(10);
        numbers.add(20);
        numbers.addFirst(5);   // insert at head
        numbers.addLast(30);   // insert at tail
        System.out.println("LinkedList: " + numbers);
        System.out.println("First: " + numbers.getFirst() + ", Last: " + numbers.getLast());
        numbers.removeFirst();
        System.out.println("After removeFirst: " + numbers + "\n");

        // ============================================================
        // 3. HashSet - no duplicates, unordered
        // ============================================================
        System.out.println("=== HashSet Demo ===");
        HashSet<Student> hashSet = new HashSet<>();
        Student s1 = new Student("Ali", 101, 3.5);
        Student s2 = new Student("Fatma", 102, 3.8);
        Student s3 = new Student("Ali Copy", 101, 3.9); // same id as s1
        hashSet.add(s1);
        hashSet.add(s2);
        boolean added = hashSet.add(s3); // should NOT be added (same id)
        System.out.println("s3 added? " + added + "  (equals/hashCode prevents duplicate id)");
        System.out.println("HashSet size: " + hashSet.size());
        hashSet.forEach(System.out::println);
        System.out.println();

        // ============================================================
        // 4. TreeSet - sorted, no duplicates (uses compareTo)
        // ============================================================
        System.out.println("=== TreeSet Demo ===");
        TreeSet<Student> treeSet = new TreeSet<>();
        treeSet.add(new Student("Omar", 105, 3.2));
        treeSet.add(new Student("Aisha", 103, 3.9));
        treeSet.add(new Student("Hassan", 104, 3.6));
        System.out.println("TreeSet (sorted by id via compareTo):");
        for (Student s : treeSet) {
            System.out.println("  " + s);
        }
        System.out.println();

        // ============================================================
        // 5. HashMap - key-value pairs, unordered
        // ============================================================
        System.out.println("=== HashMap Demo ===");
        HashMap<String, Integer> wordCount = new HashMap<>();
        wordCount.put("java", 5);
        wordCount.put("python", 3);
        wordCount.put("rust", 2);
        wordCount.put("java", 10); // overwrites previous value
        System.out.println("HashMap: " + wordCount);
        System.out.println("java count: " + wordCount.get("java"));
        System.out.println("Contains key 'rust'? " + wordCount.containsKey("rust"));

        // Iterate over entries
        System.out.println("Entries:");
        for (Map.Entry<String, Integer> entry : wordCount.entrySet()) {
            System.out.println("  " + entry.getKey() + " -> " + entry.getValue());
        }
        System.out.println();

        // ============================================================
        // 6. TreeMap - sorted by key
        // ============================================================
        System.out.println("=== TreeMap Demo ===");
        TreeMap<String, Integer> sortedMap = new TreeMap<>(wordCount);
        System.out.println("TreeMap (sorted keys): " + sortedMap);
        System.out.println("First key: " + sortedMap.firstKey());
        System.out.println("Last key: " + sortedMap.lastKey());
        System.out.println();

        // ============================================================
        // 7. PriorityQueue - min-heap by default
        // ============================================================
        System.out.println("=== PriorityQueue Demo ===");
        PriorityQueue<Integer> pq = new PriorityQueue<>();
        pq.add(40);
        pq.add(10);
        pq.add(30);
        pq.add(20);
        System.out.print("Polling from PriorityQueue (min first): ");
        while (!pq.isEmpty()) {
            System.out.print(pq.poll() + " ");
        }
        System.out.println("\n");

        // ============================================================
        // 8. Collections.sort() with Comparator
        // ============================================================
        System.out.println("=== Collections.sort() with Comparator ===");
        List<Student> roster = new ArrayList<>(Arrays.asList(
            new Student("Zara", 110, 3.1),
            new Student("Ali", 108, 3.7),
            new Student("Fatma", 112, 3.9),
            new Student("Omar", 109, 3.4)
        ));

        // Sort by name alphabetically
        Collections.sort(roster, Comparator.comparing(Student::getName));
        System.out.println("Sorted by name:");
        roster.forEach(s -> System.out.println("  " + s));

        // Sort by GPA descending
        Collections.sort(roster, Comparator.comparingDouble(Student::getGpa).reversed());
        System.out.println("Sorted by GPA (descending):");
        roster.forEach(s -> System.out.println("  " + s));

        System.out.println("\n--- CollectionsDemo complete ---");
    }
}
