#![feature(path_add_extension)]

mod collect_files;
pub use collect_files::*;
mod generate_html;
pub use generate_html::*;

use markdown::*;
use vagabond::*;

use std::collections::HashSet;


const NORMAL: &str = "\x1b[0m";
const BOLD:   &str = "\x1b[1m";
const WHITE:  &str = "\x1b[37m";
const RED:    &str = "\x1b[31m";
const YELLOW: &str = "\x1b[33m";
const BLUE:   &str = "\x1b[34m";

static mut VERBOSE: bool = false;
#[macro_export] macro_rules! verbose {
    ($($tokens:tt)*) => { if unsafe { VERBOSE } {
        eprint!("{BOLD}{BLUE}[INFO]{NORMAL}: "); eprint!($($tokens)*);
        eprintln!("{NORMAL}");
    } };
}
#[macro_export] macro_rules! warn {
    ($($tokens:tt)*) => {{
        eprint!("{BOLD}{YELLOW}[WARNING]{NORMAL}{WHITE}: "); eprint!($($tokens)*);
        eprintln!("{NORMAL}");
    }};
}
#[macro_export] macro_rules! error {
    ($($tokens:tt)*) => {{
        eprint!("{BOLD}{RED}[ERROR]{WHITE}: "); eprint!($($tokens)*);
        eprintln!("{NORMAL}"); std::process::exit(1);
    }};
}

fn main() {
    let args = Arguments::from_env_or_exit();
    if args.version {
        let version = env!("CARGO_PKG_VERSION");
        eprintln!("toaster, version {version}");
        std::process::exit(0);
    }
    if args.verbose {
        unsafe { VERBOSE = true; }
    }
    if args.source.is_none() || args.destination.is_none() {
        error!("Provide a source directory and a destination directory.")
    }
    let source_directory = args.source.unwrap().canonicalize().unwrap();
    let destination_directory = args.destination.unwrap();


    let website = Website::from_path(&source_directory);

    // Check for duplicate output paths for pages.
    let mut destinations: HashSet<&str> = HashSet::new();
    let mut duplicates: HashSet<&str> = HashSet::new();
    for page in &website.pages {
        if !destinations.insert(&page.full_url) {
            duplicates.insert(&page.full_url);
        };
    }
    if !duplicates.is_empty() {
        for destination in duplicates {
            warn!("Multiple pages have the output path {destination:?}");
            for page in &website.pages {
                if page.full_url == destination {
                    eprintln!(":: {:?}", page.source_path);
                }
            }
        }
    }

    let mut destination = destination_directory.clone();
    destination.push(make_url_safe(&website.name));

    if args.delete && Entry::from_path(&destination).is_ok() {
        verbose!("Deleting existing destination directory {destination:?}");
        remove(&destination).unwrap_or_else(|_|
            error!("Failed to delete existing destination directory {destination:?}"));
    }


    for page in &website.pages {
        let mut destination = destination.clone();
        destination.push(&page.full_url);
        // Convert document to different formats.
        if args.html {
            let html = generate_html(&page.document, page, &website);
            write_file(&html, &destination, "html");
        }
        // Copy original markdown file.
        destination.add_extension("md");
        verbose!("Copying original markdown file to {destination:?}");
        copy(&page.source_path, &destination).unwrap_or_else(|_|
            error!("Failed to copy original markdown file {:?} to {:?}",
                page.source_path, destination));
    }

    for static_file in &website.static_files {
        let mut destination = destination.clone();
        destination.push(&static_file.full_url);
        verbose!("Copying static file to {destination:?}");
        make_parent_directory(&destination).unwrap();
        copy(&static_file.source_path, &destination).unwrap_or_else(|_|
            error!("Failed to copy static file {:?} to {:?}",
                static_file.source_path, destination));
    }

    for static_dir in &website.static_dirs {
        let mut destination = destination.clone();
        destination.push(&static_dir.full_url);
        verbose!("Copying static directory to {destination:?}");
        make_parent_directory(&destination).unwrap();
        copy(&static_dir.source_path, &destination).unwrap_or_else(|_|
            error!("Failed to copy static directory {:?} to {:?}",
                static_dir.source_path, destination));
    }
}



pub fn write_file(text: &str, destination: &PathBuf, ext: &str) {
    let mut destination = destination.clone();
    destination.add_extension(ext);
    verbose!("Generating {destination:?}");
    make_parent_directory(&destination).unwrap_or_else(|_|
        error!("Failed to create parent directories for {destination:?}"));
    write_to_file(&destination, text).unwrap_or_else(|_|
        error!("Failed to write generated {ext} file to {destination:?}"));
}

pub fn make_url_safe(text: &str) -> String {
    text.to_ascii_lowercase().chars().filter_map(|c|
        if c.is_alphanumeric() || "-_~.+/#".contains(c) { Some(c) }
        else if c == ' ' { Some('-') }
        else { None } )
    .collect()
}



xflags::xflags! {
    /// Generate a website from a structured directory of markdown files.
    cmd arguments {
        /// Source directory with markdown files
        optional source: PathBuf
        /// Path to output directory
        optional destination: PathBuf
        /// Delete the destination directory first if it exists
        optional --delete
        /// Generate HTML output
        optional --html
        /// Generate Gemtext output
        optional --gmi
        /// Print information as each file is parsed
        optional -v, --verbose
        /// Print the program version and exit
        optional --version
    }
}