Documentation
use ansi2::ans::to_ans;
use ansi2::image::image_to_ans;
use ansi2::{css::Mode, theme::Theme};
use ansi2::{html::to_html, svg::to_svg, text::to_text};
use base64::Engine;
use base64::prelude::BASE64_STANDARD;
use clap::{Parser, ValueEnum, command};
use std::path::Path;
use std::{fs::read, io::Read};

#[derive(ValueEnum, Debug, Clone, Copy)]
enum Format {
    Svg,
    Html,
    Text,
    Ans,
}

#[derive(Parser, Debug, Clone)]
#[command(version, about, long_about = None)]
struct Args {
    #[arg(short, long)]
    format: Option<Format>,

    #[arg(short, long)]
    width: Option<usize>,

    #[arg(short, long)]
    theme: Option<Theme>,

    #[clap(short, long)]
    mode: Option<Mode>,

    #[arg(long)]
    font: Option<String>,

    #[arg(long)]
    light_bg: Option<String>,
    #[arg(long)]
    dark_bg: Option<String>,

    #[arg(long)]
    font_size: Option<usize>,

    #[arg(long)]
    length_adjust: Option<String>,

    #[arg(short, long, default_value_t = false)]
    sourcemap: bool,

    #[clap()]
    file: Option<String>,
}

fn process_input(buf: Vec<u8>) -> String {
    if let Some(ty) = infer::get(&buf) {
        if ty.matcher_type() == infer::MatcherType::Image {
            if let Some(s) = image_to_ans(&buf) {
                return s;
            }
        }
    }

    return String::from_utf8_lossy(&buf).to_string();
}
fn main() {
    let args: Args = Args::parse();
    let Args {
        width,
        format,
        theme,
        mode,
        font,
        light_bg,
        dark_bg,
        font_size,
        length_adjust,
        sourcemap,
        file,
    } = args;
    let format = format.unwrap_or(Format::Svg);
    let theme = theme.unwrap_or(Theme::Vscode);

    let buf = if let Some(file) = file {
        std::fs::read(file).expect("can't read string from file")
    } else {
        let mut v = Vec::new();
        std::io::stdin()
            .read_to_end(&mut v)
            .expect("can't read string from stdin");
        v
    };

    let s = process_input(buf);
    let base64 = font.map(|font_url| {
        if font_url.starts_with("http") {
            return font_url;
        }

        if !Path::new(&font_url).exists() {
            return font_url;
        }

        let bin = read(font_url).expect("read font file error");
        let base64 = BASE64_STANDARD.encode(bin);
        return format!("data:font;base64,{base64}");
    });

    let output = match format {
        Format::Svg => {
            let svg = to_svg(
                s,
                theme,
                width,
                base64,
                mode,
                light_bg,
                dark_bg,
                font_size,
                length_adjust,
                sourcemap,
            );
            #[cfg(feature = "minify")]
            let svg = minify_svg(&svg).expect("compress error");
            svg
        }
        Format::Html => to_html(
            &s, theme, width, base64, mode, light_bg, dark_bg, font_size, sourcemap,
        ),
        Format::Text => to_text(&s, width),
        Format::Ans => to_ans(&s, width),
    };
    print!("{}", output);
}

#[cfg(feature = "minify")]
fn minify_svg(svg: &str) -> Result<String, String> {
    use oxvg_ast::{
        implementations::{roxmltree::parse, shared::Element},
        serialize::{Indent, Node as _, Options},
        visitor::Info,
    };
    use oxvg_optimiser::Jobs;
    let arena = typed_arena::Arena::new();
    let dom = parse(svg, &arena).map_err(|e| e.to_string())?;

    Jobs::default()
        .run(&dom, &Info::<Element>::new(&arena))
        .map_err(|err| err.to_string())?;

    dom.serialize_with_options(Options {
        indent: Indent::None,
        ..Default::default()
    })
    .map_err(|err| err.to_string())
}