import java.io.*;
import java.nio.file.*;
import java.util.*;
import java.util.concurrent.*;

/**
 * IT6003 - Advanced Java Programming
 * Practice Exercises: Threads and I/O
 *
 * Instructions:
 *   - Implement each method/class where you see the // TODO comment.
 *   - Do NOT change the method signatures.
 *   - Run main() to test your implementations.
 *   - Some exercises create files in a "temp" folder inside the working directory.
 */
public class ExercisesThreadsIO {

    // ──────────────────────────────────────────────
    // Exercise 1: Thread Subclass – Print 1 to 10
    // ──────────────────────────────────────────────
    /**
     * Create a class NumberThread that extends Thread.
     * In its run() method, print numbers 1 through 10
     * (each on a new line, prefixed with the thread name).
     *
     * Example output line: NumberThread: 1
     */
    static class NumberThread extends Thread {
        // TODO: Override the run() method to print numbers 1-10.
        //       Use getName() to prefix each line.

        public NumberThread() {
            super("NumberThread");
        }

        @Override
        public void run() {
            // TODO: Print numbers 1 to 10
        }
    }

    // ──────────────────────────────────────────────
    // Exercise 2: Runnable – Print Even Numbers 2-20
    // ──────────────────────────────────────────────
    /**
     * Create a Runnable that prints even numbers from 2 to 20.
     *
     * @return a Runnable whose run() prints even numbers 2..20
     */
    public static Runnable createEvenPrinter() {
        // TODO: Return a Runnable (lambda or anonymous class) that
        //       prints even numbers from 2 to 20, each on a new line.
        return null;
    }

    // ──────────────────────────────────────────────
    // Exercise 3: Thread.join() – Sequential Execution
    // ──────────────────────────────────────────────
    /**
     * Create two threads:
     *   - Thread A prints "A1" through "A5"
     *   - Thread B prints "B1" through "B5"
     * Use join() so that Thread A completes entirely before Thread B starts.
     */
    public static void runSequentially() throws InterruptedException {
        // TODO: Create Thread A, start it, call join().
        //       Then create Thread B, start it, call join().
    }

    // ──────────────────────────────────────────────
    // Exercise 4: Synchronized Counter
    // ──────────────────────────────────────────────
    /**
     * Implement a thread-safe counter with increment(), decrement(),
     * and getCount() methods. All methods must be synchronized.
     */
    static class SynchronizedCounter {
        private int count = 0;

        // TODO: Implement synchronized void increment()
        public synchronized void increment() {
            // TODO
        }

        // TODO: Implement synchronized void decrement()
        public synchronized void decrement() {
            // TODO
        }

        // TODO: Implement synchronized int getCount()
        public synchronized int getCount() {
            // TODO
            return 0;
        }
    }

    // ──────────────────────────────────────────────
    // Exercise 5: Write to File (BufferedWriter)
    // ──────────────────────────────────────────────
    /**
     * Write the given lines to a file using BufferedWriter with
     * try-with-resources. Each string should be on its own line.
     *
     * @param filePath the path of the file to create/overwrite
     * @param lines    the lines to write
     */
    public static void writeToFile(String filePath, List<String> lines) throws IOException {
        // TODO: Use try-with-resources with a BufferedWriter (via FileWriter)
        //       to write each line followed by newLine().
    }

    // ──────────────────────────────────────────────
    // Exercise 6: Read File Line by Line (BufferedReader)
    // ──────────────────────────────────────────────
    /**
     * Read a text file line by line using BufferedReader and return
     * all lines as a List<String>.
     *
     * @param filePath the path of the file to read
     * @return list of lines from the file
     */
    public static List<String> readFromFile(String filePath) throws IOException {
        // TODO: Use try-with-resources with a BufferedReader (via FileReader)
        //       to read all lines into a list and return it.
        return null;
    }

    // ──────────────────────────────────────────────
    // Exercise 7: Count Lines, Words, Characters
    // ──────────────────────────────────────────────
    /**
     * Count the number of lines, words, and characters in a file
     * using Files.lines().
     *
     * @param filePath the path of the file
     * @return an int array: [lineCount, wordCount, charCount]
     */
    public static int[] countFileStats(String filePath) throws IOException {
        // TODO: Use Files.lines(Path) to stream lines.
        //       Count lines, split each line into words to count words,
        //       and sum line lengths (including newline) for characters.
        //       Return {lineCount, wordCount, charCount}.
        return null;
    }

    // ──────────────────────────────────────────────
    // Exercise 8: Copy File Using NIO
    // ──────────────────────────────────────────────
    /**
     * Copy the contents of the source file to the destination file
     * using java.nio.file.Files.copy(). Overwrite if destination exists.
     *
     * @param source path of the source file
     * @param dest   path of the destination file
     */
    public static void copyFile(String source, String dest) throws IOException {
        // TODO: Use Files.copy(Path, Path, StandardCopyOption.REPLACE_EXISTING).
    }

    // ──────────────────────────────────────────────
    // Exercise 9: Serialize and Deserialize a Student
    // ──────────────────────────────────────────────
    /**
     * A simple Student class that implements Serializable.
     */
    static class Student implements Serializable {
        private static final long serialVersionUID = 1L;
        String name;
        String regNo;
        double gpa;

        Student(String name, String regNo, double gpa) {
            this.name = name;
            this.regNo = regNo;
            this.gpa = gpa;
        }

        @Override
        public String toString() {
            return name + " (" + regNo + ", GPA: " + gpa + ")";
        }
    }

    /**
     * Serialize the given Student object to a file.
     *
     * @param student  the student to serialize
     * @param filePath the file to write to
     */
    public static void serializeStudent(Student student, String filePath) throws IOException {
        // TODO: Use ObjectOutputStream wrapping FileOutputStream in
        //       try-with-resources. Call writeObject(student).
    }

    /**
     * Deserialize a Student object from a file.
     *
     * @param filePath the file to read from
     * @return the deserialized Student
     */
    public static Student deserializeStudent(String filePath) throws IOException, ClassNotFoundException {
        // TODO: Use ObjectInputStream wrapping FileInputStream in
        //       try-with-resources. Call readObject() and cast to Student.
        return null;
    }

    // ──────────────────────────────────────────────
    // Exercise 10: ExecutorService with Callable
    // ──────────────────────────────────────────────
    /**
     * Use an ExecutorService with a fixed thread pool of 3 threads to
     * execute 5 Callable<String> tasks. Each task should:
     *   - Sleep for a random time between 100-500ms
     *   - Return "Task <number> completed by <threadName>"
     *
     * Collect and return all results.
     *
     * @return list of result strings from all 5 tasks
     */
    public static List<String> runCallableTasks() throws InterruptedException, ExecutionException {
        // TODO: Create ExecutorService with Executors.newFixedThreadPool(3).
        //       Submit 5 Callable tasks that sleep and return a message.
        //       Collect Future results, call get() on each, and return.
        //       Shut down the executor.
        return null;
    }

    // ──────────────────────────────────────────────
    // Main – test your implementations here
    // ──────────────────────────────────────────────
    public static void main(String[] args) throws Exception {

        // Create a temp directory for file exercises
        String tempDir = "temp_exercises";
        Files.createDirectories(Paths.get(tempDir));

        System.out.println("=== Exercise 1: Thread Subclass ===");
        NumberThread nt = new NumberThread();
        nt.start();
        nt.join();
        // Expected: NumberThread: 1 ... NumberThread: 10

        System.out.println("\n=== Exercise 2: Runnable Even Printer ===");
        Runnable evenPrinter = createEvenPrinter();
        if (evenPrinter != null) {
            Thread t = new Thread(evenPrinter, "EvenThread");
            t.start();
            t.join();
        }
        // Expected: 2, 4, 6, ..., 20

        System.out.println("\n=== Exercise 3: Sequential Threads ===");
        runSequentially();
        // Expected: A1..A5 then B1..B5

        System.out.println("\n=== Exercise 4: Synchronized Counter ===");
        SynchronizedCounter counter = new SynchronizedCounter();
        Thread incThread = new Thread(() -> {
            for (int i = 0; i < 1000; i++) counter.increment();
        });
        Thread decThread = new Thread(() -> {
            for (int i = 0; i < 500; i++) counter.decrement();
        });
        incThread.start();
        decThread.start();
        incThread.join();
        decThread.join();
        System.out.println("Counter value: " + counter.getCount());
        // Expected: 500

        System.out.println("\n=== Exercise 5: Write to File ===");
        String writeFile = tempDir + "/output.txt";
        List<String> lines = Arrays.asList(
            "Bismillah",
            "Advanced Java Programming",
            "State University of Zanzibar",
            "IT6003 Exercise"
        );
        writeToFile(writeFile, lines);
        System.out.println("File written to: " + writeFile);

        System.out.println("\n=== Exercise 6: Read from File ===");
        List<String> readLines = readFromFile(writeFile);
        if (readLines != null) {
            System.out.println("Lines read:");
            readLines.forEach(line -> System.out.println("  " + line));
        }
        // Expected: same 4 lines as written above

        System.out.println("\n=== Exercise 7: Count File Stats ===");
        int[] stats = countFileStats(writeFile);
        if (stats != null) {
            System.out.println("Lines: " + stats[0] + ", Words: " + stats[1] + ", Characters: " + stats[2]);
        }
        // Expected: Lines: 4, Words: 10, Characters: ~70

        System.out.println("\n=== Exercise 8: Copy File ===");
        String copyDest = tempDir + "/output_copy.txt";
        copyFile(writeFile, copyDest);
        System.out.println("File copied to: " + copyDest);
        List<String> copiedLines = readFromFile(copyDest);
        if (copiedLines != null) {
            System.out.println("Copied content matches: " + copiedLines.equals(readLines));
        }
        // Expected: true

        System.out.println("\n=== Exercise 9: Serialize / Deserialize ===");
        String studentFile = tempDir + "/student.ser";
        Student student = new Student("Ali Hassan", "SUZA-2024-001", 3.75);
        serializeStudent(student, studentFile);
        Student loaded = deserializeStudent(studentFile);
        System.out.println("Original:     " + student);
        System.out.println("Deserialized: " + loaded);
        // Expected: same output for both

        System.out.println("\n=== Exercise 10: ExecutorService Callable ===");
        List<String> results = runCallableTasks();
        if (results != null) {
            results.forEach(System.out::println);
        }
        // Expected: 5 lines like "Task 1 completed by pool-1-thread-X"

        // Cleanup temp directory
        System.out.println("\n--- Cleaning up temp files ---");
        Files.walk(Paths.get(tempDir))
             .sorted(Comparator.reverseOrder())
             .map(Path::toFile)
             .forEach(File::delete);
        System.out.println("Done.");
    }
}
