mod board;
mod http;
mod thread;

use std::{path::PathBuf, process::exit};

use clap::{Args, Parser};
use colored::Colorize;
use lazy_regex::*;

static RES_REX: Lazy<Regex> = lazy_regex!(r"^\d{3,6}x\d{3,6}$");
static RATIO_REX: Lazy<Regex> = lazy_regex!(r"^\d{1,2}:\d{1,2}$");
static URL_REX: Lazy<Regex> = lazy_regex!(
    r"^((http|https)://)?boards.(4chan|4channel).org/[a-zA-Z]{1,4}/(catalog|thread/\d+)$"
);

pub static UA: &str = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.3";

#[derive(Debug, Clone, Copy)]
enum Mode {
    Thread,
    Board,
}

#[derive(Debug, Clone)]
struct Config {
    mode: Mode,
    target: String,
    min_res: Option<(u64, u64)>,
    max_res: Option<(u64, u64)>,
    aspect_ratios: Option<Vec<(u32, u32)>>,
    out_dir: PathBuf,
    verbose: bool,
}

impl From<Cli> for Config {
    fn from(value: Cli) -> Self {
        let mode: Mode;
        let target: String;

        if let Some(thread_url) = value.target.thread {
            mode = Mode::Thread;
            target = thread_url;
        } else if let Some(board_url) = value.target.board {
            mode = Mode::Board;
            target = board_url;
        } else {
            eprintln!("{}", "No target URL".red().bold());
            exit(1);
        }

        if !URL_REX.is_match(&target) {
            eprintln!("{}", "Target URL doesn't pass the RegEx check".red().bold());
            exit(1);
        }

        let out_dir = PathBuf::from(value.output);

        Config {
            mode,
            target,
            min_res: Self::parse_res(value.min_res),
            max_res: Self::parse_res(value.max_res),
            aspect_ratios: Self::parse_aspect_ratios(value.aspect_ratios),
            out_dir,
            verbose: value.verbose,
        }
    }
}

impl Config {
    fn parse_res(res_str: Option<String>) -> Option<(u64, u64)> {
        let res = match res_str {
            Some(res) => res,
            None => return None,
        };

        if !RES_REX.is_match(&res) {
            eprintln!(
                "{}",
                "Given resolution doesn't pass the RegEx check".red().bold()
            );
            exit(1);
        }

        let split: Vec<&str> = res.split('x').collect();
        let parsed = (
            split[0].parse::<u64>().unwrap(),
            split[1].parse::<u64>().unwrap(),
        );

        Some(parsed)
    }

    fn parse_aspect_ratios(ratios_str: Option<String>) -> Option<Vec<(u32, u32)>> {
        let ratios = match ratios_str {
            Some(ratios) => ratios,
            None => return None,
        };

        let ratios_vec: Vec<&str> = ratios.split(',').collect();
        let mut parsed_vec = Vec::new();

        for r in ratios_vec {
            if !RATIO_REX.is_match(r) {
                eprintln!(
                    "{}",
                    "One or more of the given aspect ratios don't pass the RegEx check"
                        .red()
                        .bold()
                );
                exit(1);
            }

            let split: Vec<&str> = r.split(':').collect();
            let parsed = (
                split[0].parse::<u32>().unwrap(),
                split[1].parse::<u32>().unwrap(),
            );

            parsed_vec.push(parsed);
        }

        Some(parsed_vec)
    }
}

#[derive(Debug, Parser)]
struct Cli {
    /// Set the output directory
    #[arg(short = 'o', value_name = "PATH", required = true)]
    output: String,

    /// Set the target URL
    #[command(flatten)]
    target: Target,

    /// Set the minimum image resolution (e.g. "1920x1080")
    #[arg(short = 'n', value_name = "MIN_RES")]
    min_res: Option<String>,

    /// Set the minimum image resolution (e.g. "3840x2160")
    #[arg(short = 'm', value_name = "MAX_RES")]
    max_res: Option<String>,

    /// Set a comma-separated list of accepted image aspect ratios (e.g. "4:3,16:9")
    #[arg(short = 'a', value_name = "RATIOS")]
    aspect_ratios: Option<String>,

    /// Toggle verbose output
    #[arg(short = 'v', action, default_value_t = false)]
    verbose: bool,
}

#[derive(Debug, Args)]
#[group(required = true, multiple = false)]
struct Target {
    /// Set the thread URL
    #[arg(short = 't', value_name = "URL")]
    thread: Option<String>,

    /// Set the board URL
    #[arg(short = 'b', value_name = "URL")]
    board: Option<String>,
}

#[tokio::main]
async fn main() {
    let args = Cli::parse();
    let cfg = Config::from(args);
    let client = reqwest::Client::new();

    if cfg.verbose {
        println!("\nDLRS CONFIG:\n{cfg:#?}\n");
    }

    let res = match cfg.mode {
        Mode::Thread => thread::dl(cfg, client, None).await,
        Mode::Board => board::dl(cfg, client).await,
    };

    let (dl, fl) = match res {
        Ok((dl, fl)) => (dl, fl),
        Err(e) => {
            eprintln!(
                "{}",
                format!("Error during thread download: {e}").red().bold()
            );
            exit(1);
        }
    };

    println!(
        "{}",
        format!("\nA total of {dl} files downloaded and {fl} filtered")
            .truecolor(252, 156, 12)
            .bold()
    );
}
