package com.tinsae.crawler;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Multithreaded Web Crawler
 * - Starts from a root URL
 * - Spawns threads to crawl links found in pages
 * - Stores content locally
 * - Respects max threads and max depth limits
 * 
 * @author Tinsae Ghilay
 */
public class Crawler {

    private final String rootUrl;
    private final int maxThreads;
    private final int maxDepth;
    private final String outputDir;

    // Thread-safe collections
    private final Set<String> visitedUrls = Collections.synchronizedSet(new HashSet<>());
    private final ExecutorService executorService;
    private final AtomicInteger activeThreads = new AtomicInteger(0);
    private final AtomicInteger totalThreadsStarted = new AtomicInteger(0);

    public Crawler(String rootUrl, int maxThreads, int maxDepth, String outputDir) {
        this.rootUrl = rootUrl;
        this.maxThreads = maxThreads;
        this.maxDepth = maxDepth;
        this.outputDir = outputDir;
        this.executorService = Executors.newFixedThreadPool(maxThreads);

        // Create output directory if it doesn't exist
        try {
            Files.createDirectories(Paths.get(outputDir));
            System.out.println("Output directory created: " + outputDir);
        } catch (IOException e) {
            System.err.println("Failed to create output directory: " + e.getMessage());
        }
    }

    /**
     * Start crawling from the root URL
     */
    public void start() throws MalformedURLException {
        System.out.println("Starting crawler...");
        System.out.println("Root URL: " + rootUrl);
        System.out.println("Max Threads: " + maxThreads);
        System.out.println("Max Depth: " + maxDepth);
        System.out.println("Output Directory: " + outputDir);
        System.out.println("-------------------------------------------");

        // Submit the first task
        CrawlTask rootTask = new CrawlTask(rootUrl, 0, this);
        submitTask(rootTask);

        // Wait for all active threads to finish before shutting down
        while (activeThreads.get() > 0 || totalThreadsStarted.get() == 0) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            }
        }

        // Now shutdown the executor
        executorService.shutdown();
        try {
            // Wait up to 5 minutes for completion
            if (!executorService.awaitTermination(5, TimeUnit.MINUTES)) {
                System.out.println("\nTimeout: Stopping crawler...");
                executorService.shutdownNow();
            }
        } catch (InterruptedException e) {
            System.err.println("Crawler interrupted: " + e.getMessage());
            executorService.shutdownNow();
            Thread.currentThread().interrupt();
        }

        System.out.println("\n-------------------------------------------");
        System.out.println("Crawling complete!");
        System.out.println("Total URLs visited: " + visitedUrls.size());
        System.out.println("Total threads started: " + totalThreadsStarted.get());
        System.out.println("Max depth reached: " + maxDepth);
    }

    /**
     * Submit a task to the executor if conditions are met
     */
    synchronized void submitTask(CrawlTask task) {
        // Check if URL was already visited
        if (visitedUrls.contains(task.getUrl())) {
            return;
        }

        // Check if max depth exceeded
        if (task.getDepth() > maxDepth) {
            return;
        }

        // Check if max threads reached (but allow some overflow for queued tasks)
        if (totalThreadsStarted.get() >= maxThreads) {
            System.out.println("[Crawler] Max threads reached (" + maxThreads + "). Stopping new crawls.");
            return;
        }

        // Mark as visited and submit
        visitedUrls.add(task.getUrl());
        totalThreadsStarted.incrementAndGet();
        activeThreads.incrementAndGet();

        executorService.submit(() -> {
            try {
                task.run();
            } finally {
                activeThreads.decrementAndGet();
            }
        });

        System.out.println("[Crawler] Submitted task #" + totalThreadsStarted.get() + " for: " + task.getUrl()
                + " (depth: " + task.getDepth() + ")");
    }

    /**
     * Get the set of visited URLs (thread-safe)
     */
    Set<String> getVisitedUrls() {
        return visitedUrls;
    }

    /**
     * Get the output directory
     */
    String getOutputDir() {
        return outputDir;
    }

    /**
     * Get max depth limit
     */
    int getMaxDepth() {
        return maxDepth;
    }
}
