import java.util.*;

/**
 * GenericsDemo.java
 * Demonstrates Java Generics for MSc students.
 * Covers: generic classes, generic methods, bounded type parameters,
 *         wildcard types (? extends, ? super), and a generic Stack implementation.
 *
 * Compile: javac GenericsDemo.java
 * Run:     java GenericsDemo
 */
public class GenericsDemo {

    // ================================================================
    // 1. Generic class: Pair<K, V>
    //    Holds two values of potentially different types.
    // ================================================================
    static class Pair<K, V> {
        private K key;
        private V value;

        public Pair(K key, V value) {
            this.key = key;
            this.value = value;
        }

        public K getKey()   { return key; }
        public V getValue() { return value; }

        @Override
        public String toString() {
            return "(" + key + ", " + value + ")";
        }
    }

    // ================================================================
    // 2. Generic method: works with any reference type array
    // ================================================================
    public static <T> void printArray(T[] array) {
        System.out.print("[");
        for (int i = 0; i < array.length; i++) {
            System.out.print(array[i]);
            if (i < array.length - 1) System.out.print(", ");
        }
        System.out.println("]");
    }

    // ================================================================
    // 3. Bounded type parameter: T must be Comparable
    //    Returns the maximum element in an array.
    // ================================================================
    public static <T extends Comparable<T>> T findMax(T[] array) {
        if (array == null || array.length == 0) {
            throw new IllegalArgumentException("Array must not be empty");
        }
        T max = array[0];
        for (int i = 1; i < array.length; i++) {
            if (array[i].compareTo(max) > 0) {
                max = array[i];
            }
        }
        return max;
    }

    // ================================================================
    // 4. Wildcard: ? extends Number  (upper-bounded)
    //    Accepts a list of any Number subtype and computes the sum.
    // ================================================================
    public static double sumOfList(List<? extends Number> list) {
        double sum = 0.0;
        for (Number n : list) {
            sum += n.doubleValue();
        }
        return sum;
    }

    // Wildcard: ? super Integer  (lower-bounded)
    // Can add integers into any list that holds Integer or a supertype.
    public static void addNumbers(List<? super Integer> list, int count) {
        for (int i = 1; i <= count; i++) {
            list.add(i);
        }
    }

    // ================================================================
    // 5. Generic Stack<T> implementation backed by an array
    // ================================================================
    static class GenericStack<T> {
        private Object[] data;
        private int top;
        private int capacity;

        public GenericStack(int capacity) {
            this.capacity = capacity;
            this.data = new Object[capacity];
            this.top = -1;
        }

        public void push(T item) {
            if (top == capacity - 1) {
                throw new RuntimeException("Stack overflow");
            }
            data[++top] = item;
        }

        @SuppressWarnings("unchecked")
        public T pop() {
            if (isEmpty()) {
                throw new RuntimeException("Stack underflow");
            }
            T item = (T) data[top];
            data[top--] = null; // help GC
            return item;
        }

        @SuppressWarnings("unchecked")
        public T peek() {
            if (isEmpty()) {
                throw new RuntimeException("Stack is empty");
            }
            return (T) data[top];
        }

        public boolean isEmpty() { return top == -1; }
        public int size()        { return top + 1; }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder("Stack[");
            for (int i = 0; i <= top; i++) {
                sb.append(data[i]);
                if (i < top) sb.append(", ");
            }
            sb.append("] <-- top");
            return sb.toString();
        }
    }

    // ================================================================
    // Main method - run all demos
    // ================================================================
    public static void main(String[] args) {

        // --- Pair<K, V> ---
        System.out.println("=== Generic Class: Pair<K, V> ===");
        Pair<String, Integer> grade = new Pair<>("Ali", 85);
        Pair<Integer, Double> measurement = new Pair<>(1, 98.6);
        System.out.println("Grade pair: " + grade);
        System.out.println("Measurement pair: " + measurement);
        System.out.println("Key type: " + grade.getKey().getClass().getSimpleName());
        System.out.println();

        // --- Generic method ---
        System.out.println("=== Generic Method: printArray ===");
        Integer[] intArr = {1, 2, 3, 4, 5};
        String[] strArr = {"Java", "Python", "Rust"};
        Double[] dblArr = {1.1, 2.2, 3.3};
        System.out.print("Integer array: "); printArray(intArr);
        System.out.print("String array:  "); printArray(strArr);
        System.out.print("Double array:  "); printArray(dblArr);
        System.out.println();

        // --- Bounded type parameter ---
        System.out.println("=== Bounded Type: <T extends Comparable<T>> ===");
        System.out.println("Max of intArr: " + findMax(intArr));
        System.out.println("Max of strArr: " + findMax(strArr)); // lexicographic
        System.out.println("Max of dblArr: " + findMax(dblArr));
        System.out.println();

        // --- Wildcards ---
        System.out.println("=== Wildcards ===");
        List<Integer> integers = Arrays.asList(10, 20, 30);
        List<Double> doubles = Arrays.asList(1.5, 2.5, 3.5);
        // Both work because Integer and Double extend Number
        System.out.println("Sum of integers: " + sumOfList(integers));
        System.out.println("Sum of doubles:  " + sumOfList(doubles));

        // Lower-bounded wildcard: ? super Integer
        List<Number> numberList = new ArrayList<>();
        addNumbers(numberList, 5);
        System.out.println("After addNumbers (? super Integer): " + numberList);
        System.out.println();

        // --- Generic Stack ---
        System.out.println("=== Generic Stack<T> ===");
        GenericStack<String> stringStack = new GenericStack<>(5);
        stringStack.push("Alpha");
        stringStack.push("Beta");
        stringStack.push("Gamma");
        System.out.println(stringStack);
        System.out.println("Peek: " + stringStack.peek());
        System.out.println("Pop:  " + stringStack.pop());
        System.out.println("Pop:  " + stringStack.pop());
        System.out.println("Size: " + stringStack.size());
        System.out.println(stringStack);

        System.out.println();
        GenericStack<Integer> intStack = new GenericStack<>(10);
        for (int i = 1; i <= 5; i++) intStack.push(i * 10);
        System.out.println("Integer stack: " + intStack);
        System.out.println("Pop: " + intStack.pop());
        System.out.println("After pop:     " + intStack);

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