use clap::*;
use phylotree::tree::{EdgeLength, NodeId, Tree};

// Create clap subcommand arguments
pub fn make_subcommand() -> Command {
    Command::new("tex")
        .about("Visualize the Newick tree via LaTeX")
        .after_help(
            r#"
* Styles are stored in the comments of each node

* Drawing a cladogram by default
* Set `--bl` to draw a phylogenetic tree

* Underscore `_` is a control character in LaTeX
    * All `_`s in names, labels and comments will be replaced as spaces " "

* To convert the .tex files generated by this command to pdf,
  you need LaTeX and some additional utilities and packages
    * `XeLaTeX` and `latexmk` for compiling unicode .tex
    * `xeCJK` for East Asian characters
    * `Forest` is a `TikZ`-based package for drawing trees

* If you don't have LaTeX installed on your system,
  I recommend using tectonic, which is delivered in a single executable file

```
nwr tex test.nwk -o test.tex

# Install tectonic
tectonic test.tex

# With Texlive or something else
latexmk -xelatex test.tex
latexmk -c test.tex
```
"#,
        )
        .arg(
            Arg::new("infile")
                .required(true)
                .num_args(1)
                .index(1)
                .help("Input filename. [stdin] for standard input"),
        )
        .arg(
            Arg::new("bare")
                .long("bare")
                .action(ArgAction::SetTrue)
                .help("Only forest code, no full template"),
        )
        .arg(
            Arg::new("bl")
                .long("bl")
                .action(ArgAction::SetTrue)
                .help("With branch lengths"),
        )
        .arg(
            Arg::new("forest")
                .long("forest")
                .action(ArgAction::SetTrue)
                .help("The infile is a forest file"),
        )
        .arg(
            Arg::new("style")
                .long("style")
                .short('s')
                .action(ArgAction::SetTrue)
                .help("Custom English and Chinese fonts"),
        )
        .arg(
            Arg::new("outfile")
                .short('o')
                .long("outfile")
                .num_args(1)
                .default_value("stdout")
                .help("Output filename. [stdout] for screen"),
        )
}

// command implementation
pub fn execute(args: &ArgMatches) -> anyhow::Result<()> {
    let mut writer = intspan::writer(args.get_one::<String>("outfile").unwrap());
    let is_bare = args.get_flag("bare");
    let is_bl = args.get_flag("bl");
    let is_style = args.get_flag("style");

    let infile = args.get_one::<String>("infile").unwrap();

    let out_string = if args.get_flag("forest") {
        let mut reader = intspan::reader(infile);
        let mut s = String::new();
        reader.read_to_string(&mut s).expect("Read error");

        s
    } else {
        let tree = nwr::read_newick(infile);

        let height = if is_bl { tree.height().unwrap() } else { 0.0 };
        // eprintln!("height = {:#?}", height);

        let mut s = format_forest(&tree, height);

        // a bar of unit length
        if is_bl {
            let bar_len = format!("{:.3}", height / 100.0 * 10.0);
            // a grey 1pt bar
            s += "\\draw[-, grey, line width=1pt]";
            // bar position
            s += " ($(current bounding box.south east)+(-10mm,-2mm)$)";
            // 10 mm
            s += " --++ (-10mm,0mm)";
            // text
            s += &format!(" node[midway, below]{{\\scriptsize{{{}}}}};\n", &bar_len);
        }

        s
    };

    if is_bare {
        writer.write_all((out_string + "\n").as_ref())?;
    } else {
        static FILE_TEMPLATE: &str = include_str!("../../../doc/template.tex");
        let mut template = FILE_TEMPLATE.to_string();

        {
            // Section forest
            let begin = template.find("%FOREST_BEGIN").unwrap();
            let end = template.find("%FOREST_END").unwrap();
            template.replace_range(begin..end, &out_string);
        }

        if !is_style {
            let default_font = r"
\setmainfont{NotoSans}[
    Extension      = .ttf,
    UprightFont    = *-Regular,
    BoldFont       = *-Bold,
    ItalicFont     = *-Italic,
    BoldItalicFont = *-BoldItalic
]
";

            // Section style
            let begin = template.find("%STYLE_BEGIN").unwrap();
            let end = template.find("%STYLE_END").unwrap();
            template.replace_range(begin..end, default_font);
        }

        writer.write_all(template.as_ref())?;
    }

    Ok(())
}

fn format_forest(tree: &Tree, height: EdgeLength) -> String {
    let root = tree.get_root().unwrap();
    format_sub_forest(tree, &root, height)
}

fn format_sub_forest(tree: &Tree, id: &NodeId, height: EdgeLength) -> String {
    let node = tree.get(id).unwrap();
    let indent = "  ";

    let children = &node.children;
    let depth = node.get_depth();

    if children.is_empty() {
        let indention = indent.repeat(depth);
        format!("{}[{}]\n", indention, format_node(tree, id, height))
    } else {
        let branch_set = children
            .iter()
            .map(|child| format_sub_forest(tree, child, height))
            .collect::<Vec<_>>();

        let indention = indent.repeat(depth);
        format!(
            "{}[{}\n{}{}]\n",
            indention,
            format_node(tree, id, height),
            branch_set.join(""),
            indention,
        )
    }
}

// almost all the operations in here
fn format_node(tree: &Tree, id: &NodeId, height: EdgeLength) -> String {
    let node = tree.get(id).unwrap();
    let depth = node.get_depth();

    let mut repr = String::new();

    let mut name = node.name.clone().map(|x| x.replace('_', " "));
    let mut color: Option<String> = None;
    let mut label: Option<String> = None;

    // internal node's name will be treated as labels and place a dot there
    if !node.is_tip() && name.is_some() {
        label = Some(name.clone().unwrap());
        name = None;
        // dot with default color
        repr += ", dot";
    }

    if let Some(cmt) = node.comment.clone() {
        let cmt = cmt.replace('_', " ");
        let parts: Vec<&str> = cmt.split(':').collect();

        let mut comment = String::new(); // real comment
        for pt in parts {
            if pt.starts_with("color=") {
                color = Some(pt.replace("color=", ""));
            } else if pt.starts_with("label=") {
                label = Some(pt.replace("label=", ""));
            } else if pt.starts_with("dot=") {
                repr += &format!(", dot={{{}}}", pt.replace("dot=", ""));
            } else if pt.starts_with("bar=") {
                repr += &format!(", bar={{{}}}", pt.replace("bar=", ""));
            } else if pt.starts_with("rec=") {
                repr += &format!(", rec={{{}}}", pt.replace("rec=", ""));
            } else if pt.starts_with("tri=") {
                repr += &format!(", tri={{{}}}", pt.replace("tri=", ""));
            } else if pt.starts_with("comment=") {
                if !comment.is_empty() {
                    comment += " ";
                }
                comment += pt.replace("comment=", "").as_str();
            } else if pt.starts_with("T=") {
                if !comment.is_empty() {
                    comment += " ";
                }
                comment += pt.replace("T=", "").as_str();
            } else if pt.starts_with("S=") {
                if !comment.is_empty() {
                    comment += " ";
                }
                comment += pt.replace("S=", "").as_str();
            } else if pt.starts_with("rank=") {
                if !comment.is_empty() {
                    comment += " ";
                }
                comment += pt.replace("rank=", "").as_str();
            } else if pt.starts_with("member=") {
                if !comment.is_empty() {
                    comment += " ";
                }
                comment += pt.replace("member=", "").as_str();
            } else {
                if !comment.is_empty() {
                    comment += " ";
                }
                comment += pt;
            }
        }

        if !comment.is_empty() && node.is_tip() {
            repr += &format!(", comment={{{}}}", &comment);
        }
    }

    if color.is_some() {
        if name.is_some() {
            repr = format!(
                ", \\color{{{}}}{{{}}}",
                color.clone().unwrap(),
                name.clone().unwrap()
            ) + &repr;
        }
        if label.is_some() && !label.clone().unwrap().is_empty() {
            repr += &format!(
                ", label=\\color{{{}}}{{{}}}",
                color.clone().unwrap(),
                label.clone().unwrap()
            );
        }
    } else {
        if name.is_some() {
            repr = format!("{{{}}},", name.clone().unwrap()) + &repr;
        }
        if label.is_some() && !label.clone().unwrap().is_empty() {
            repr += &format!(", label={{{}}}", label.clone().unwrap());
        }
    }

    if name.is_none() {
        if node.is_tip() {
            repr = "{~},".to_owned() + &repr; // non-breaking space in latex
        } else {
            repr = ",".to_owned() + &repr;
        }
    }

    if height == 0.0 {
        let tier = if node.is_tip() {
            0
        } else {
            branch_depth(tree, id) - depth
        };
        repr += &format!(", tier={}", tier);
    } else {
        let edge = node.parent_edge.unwrap_or(0.0);
        let bl = calc_length(edge, height);
        repr += &format!(", l={}mm, l sep=0", bl);

        if node.is_tip() {
            // Add an invisible node to the rightmost to occupy spaces
            repr += ", [{~},tier=0,edge={draw=none}]";
        }
    }

    repr
}

// max depth of this node's children
fn branch_depth(tree: &Tree, id: &NodeId) -> usize {
    tree.get_descendants(id)
        .unwrap()
        .iter()
        .map(|e| tree.get(e).unwrap().get_depth())
        .max()
        .unwrap()
}

// relative length
fn calc_length(edge: EdgeLength, height: EdgeLength) -> i32 {
    (edge * 100.0 / height).round() as i32
}
