import java.io.*;
import java.net.*;
import java.util.concurrent.*;

/**
 * NetworkingDemo.java
 * Demonstrates Java networking concepts for MSc students.
 * Covers: URL parsing, TCP echo server/client with Socket/ServerSocket,
 *         multi-threaded server handling multiple clients, and
 *         HTTP GET using HttpURLConnection.
 *
 * Compile: javac NetworkingDemo.java
 * Run:     java NetworkingDemo
 *
 * Note: The server and client run in the same JVM using separate threads.
 *       Firewall/antivirus may need to allow localhost connections.
 */
public class NetworkingDemo {

    // ================================================================
    // 1. URL Parsing Demo
    // ================================================================
    static void urlParsingDemo() {
        System.out.println("=== URL Parsing ===");
        try {
            URL url = new URL("https://www.example.com:8080/api/students?page=1&size=10#section");
            System.out.println("Full URL:   " + url);
            System.out.println("Protocol:   " + url.getProtocol());
            System.out.println("Host:       " + url.getHost());
            System.out.println("Port:       " + url.getPort());
            System.out.println("Path:       " + url.getPath());
            System.out.println("Query:      " + url.getQuery());
            System.out.println("Fragment:   " + url.getRef());
            System.out.println("Authority:  " + url.getAuthority());
        } catch (MalformedURLException e) {
            System.err.println("Invalid URL: " + e.getMessage());
        }
        System.out.println();
    }

    // ================================================================
    // 2. Simple TCP Echo Server (single-client)
    // ================================================================
    static class SimpleEchoServer implements Runnable {
        private final int port;
        private final CountDownLatch readyLatch;

        SimpleEchoServer(int port, CountDownLatch readyLatch) {
            this.port = port;
            this.readyLatch = readyLatch;
        }

        @Override
        public void run() {
            try (ServerSocket serverSocket = new ServerSocket(port)) {
                serverSocket.setSoTimeout(5000); // 5 second accept timeout
                System.out.println("  [Server] Listening on port " + port);
                readyLatch.countDown(); // signal that server is ready

                // Accept one client
                try (Socket client = serverSocket.accept();
                     BufferedReader in = new BufferedReader(
                         new InputStreamReader(client.getInputStream()));
                     PrintWriter out = new PrintWriter(
                         client.getOutputStream(), true)) {

                    System.out.println("  [Server] Client connected from "
                        + client.getInetAddress());
                    String line;
                    while ((line = in.readLine()) != null) {
                        if (line.equalsIgnoreCase("quit")) {
                            out.println("Goodbye!");
                            break;
                        }
                        String echo = "ECHO: " + line;
                        out.println(echo);
                        System.out.println("  [Server] Echoed: " + echo);
                    }
                }
                System.out.println("  [Server] Client disconnected.");
            } catch (IOException e) {
                System.err.println("  [Server] Error: " + e.getMessage());
            }
        }
    }

    // ================================================================
    // 3. Simple TCP Client
    // ================================================================
    static class SimpleClient implements Runnable {
        private final int port;
        private final CountDownLatch serverReady;

        SimpleClient(int port, CountDownLatch serverReady) {
            this.port = port;
            this.serverReady = serverReady;
        }

        @Override
        public void run() {
            try {
                serverReady.await(); // wait for server to start
                Thread.sleep(100);   // small extra delay

                try (Socket socket = new Socket("localhost", port);
                     PrintWriter out = new PrintWriter(
                         socket.getOutputStream(), true);
                     BufferedReader in = new BufferedReader(
                         new InputStreamReader(socket.getInputStream()))) {

                    // Send a few messages
                    String[] messages = {"Hello Server", "Java Networking", "quit"};
                    for (String msg : messages) {
                        System.out.println("  [Client] Sending: " + msg);
                        out.println(msg);
                        String response = in.readLine();
                        System.out.println("  [Client] Received: " + response);
                    }
                }
            } catch (Exception e) {
                System.err.println("  [Client] Error: " + e.getMessage());
            }
        }
    }

    // ================================================================
    // 4. Multi-threaded Server (handles multiple clients concurrently)
    // ================================================================
    static class MultiThreadedServer implements Runnable {
        private final int port;
        private final CountDownLatch readyLatch;
        private final int expectedClients;

        MultiThreadedServer(int port, CountDownLatch readyLatch, int expectedClients) {
            this.port = port;
            this.readyLatch = readyLatch;
            this.expectedClients = expectedClients;
        }

        @Override
        public void run() {
            ExecutorService pool = Executors.newFixedThreadPool(4);
            try (ServerSocket serverSocket = new ServerSocket(port)) {
                serverSocket.setSoTimeout(5000);
                System.out.println("  [MTServer] Listening on port " + port);
                readyLatch.countDown();

                for (int i = 0; i < expectedClients; i++) {
                    Socket client = serverSocket.accept();
                    // Each client handled in its own thread from the pool
                    pool.submit(new ClientHandler(client));
                }
            } catch (IOException e) {
                System.err.println("  [MTServer] Error: " + e.getMessage());
            } finally {
                pool.shutdown();
                try { pool.awaitTermination(5, TimeUnit.SECONDS); }
                catch (InterruptedException ignored) {}
            }
        }
    }

    static class ClientHandler implements Runnable {
        private final Socket socket;

        ClientHandler(Socket socket) {
            this.socket = socket;
        }

        @Override
        public void run() {
            String clientName = socket.getInetAddress() + ":" + socket.getPort();
            try (BufferedReader in = new BufferedReader(
                     new InputStreamReader(socket.getInputStream()));
                 PrintWriter out = new PrintWriter(
                     socket.getOutputStream(), true)) {

                String line;
                while ((line = in.readLine()) != null) {
                    if (line.equalsIgnoreCase("quit")) {
                        out.println("Bye from handler!");
                        break;
                    }
                    out.println("[" + Thread.currentThread().getName() + "] " + line);
                }
            } catch (IOException e) {
                System.err.println("  [Handler] Error with " + clientName);
            }
        }
    }

    // ================================================================
    // 5. HTTP GET using HttpURLConnection
    // ================================================================
    static void httpGetDemo() {
        System.out.println("=== HTTP GET Demo ===");
        String targetUrl = "http://httpbin.org/get";
        System.out.println("  Fetching: " + targetUrl);
        try {
            URL url = new URL(targetUrl);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("GET");
            conn.setConnectTimeout(5000);
            conn.setReadTimeout(5000);
            conn.setRequestProperty("User-Agent", "JavaNetworkingDemo/1.0");

            int status = conn.getResponseCode();
            System.out.println("  Status code: " + status);
            System.out.println("  Content-Type: " + conn.getContentType());

            // Read response body (first 10 lines)
            if (status == 200) {
                try (BufferedReader reader = new BufferedReader(
                        new InputStreamReader(conn.getInputStream()))) {
                    System.out.println("  Response body (first 10 lines):");
                    String line;
                    int lineCount = 0;
                    while ((line = reader.readLine()) != null && lineCount < 10) {
                        System.out.println("    " + line);
                        lineCount++;
                    }
                    if (lineCount == 10) {
                        System.out.println("    ... (truncated)");
                    }
                }
            }
            conn.disconnect();
        } catch (IOException e) {
            System.out.println("  HTTP request failed: " + e.getMessage());
            System.out.println("  (This is expected if there is no internet connection)");
        }
        System.out.println();
    }

    // ================================================================
    // Main
    // ================================================================
    public static void main(String[] args) throws Exception {

        // --- 1. URL Parsing ---
        urlParsingDemo();

        // --- 2. Simple Echo Server + Client ---
        System.out.println("=== Simple TCP Echo Server/Client ===");
        int echoPort = 9876;
        CountDownLatch echoReady = new CountDownLatch(1);
        Thread serverThread = new Thread(new SimpleEchoServer(echoPort, echoReady));
        Thread clientThread = new Thread(new SimpleClient(echoPort, echoReady));
        serverThread.start();
        clientThread.start();
        clientThread.join();
        serverThread.join();
        System.out.println();

        // --- 3. Multi-threaded Server with Multiple Clients ---
        System.out.println("=== Multi-Threaded Server (3 clients) ===");
        int mtPort = 9877;
        int numClients = 3;
        CountDownLatch mtReady = new CountDownLatch(1);
        Thread mtServer = new Thread(
            new MultiThreadedServer(mtPort, mtReady, numClients));
        mtServer.start();
        mtReady.await(); // wait for server
        Thread.sleep(100);

        // Spawn multiple clients
        Thread[] clients = new Thread[numClients];
        for (int i = 0; i < numClients; i++) {
            final int clientId = i + 1;
            clients[i] = new Thread(() -> {
                try (Socket s = new Socket("localhost", mtPort);
                     PrintWriter out = new PrintWriter(s.getOutputStream(), true);
                     BufferedReader in = new BufferedReader(
                         new InputStreamReader(s.getInputStream()))) {

                    out.println("Hello from client " + clientId);
                    String resp = in.readLine();
                    System.out.println("  [Client" + clientId + "] Got: " + resp);
                    out.println("quit");
                    in.readLine(); // read goodbye
                } catch (IOException e) {
                    System.err.println("  [Client" + clientId + "] Error: "
                        + e.getMessage());
                }
            });
            clients[i].start();
        }
        for (Thread c : clients) c.join();
        mtServer.join();
        System.out.println();

        // --- 4. HTTP GET ---
        httpGetDemo();

        System.out.println("--- NetworkingDemo complete ---");
    }
}
