use std::path::{Path, PathBuf};

use anyhow::{Context, Result};
use clap::Parser;
use cli::{Args, Command};
use log::trace;
use mapping::map_tree::MappingTree;
use walkdir::{DirEntry, WalkDir};

use crate::mapping::MapKey;

mod cli;
mod mapping;

fn main() -> anyhow::Result<()> {
    let args = Args::parse();

    stderrlog::new()
        .module(module_path!())
        .quiet(args.quiet)
        .show_module_names(false)
        .color(stderrlog::ColorChoice::Auto)
        .verbosity(args.verbosity as usize)
        .timestamp(stderrlog::Timestamp::Off)
        .init()?;

    let mut mappings = MappingTree::new();

    let relevant_directories = match &args.command {
        Command::Visualize { options } => &options.relevant_directories,
        Command::Generate { options } => &options.relevant_directories,
    };

    for dir in relevant_directories {
        trace!("Processing '{}'..", dir.display());
        let path = strip_path(&dir, &args.home_name)?;

        mappings
            .include(path_to_str(path)?)
            .with_context(|| format!("Failed to include path: '{}'", path.display()))?;
    }

    let home = path_to_str(&args.home_name)?.to_owned();

    let mut current_depth = 1;
    while current_depth != args.depth {
        for (key, value) in mappings.iter(false) {
            trace!(
                "Adding to child ('{}' -> '{}')",
                MapKey::display(&key),
                value
            );

            let mut local_mappings = MappingTree::new();
            for dir in WalkDir::new(extend(&home, &value)?)
                .min_depth(1)
                .max_depth(1)
                .into_iter()
                .filter_entry(|e| is_dir(e) && !is_hidden(e))
            {
                let directory = dir
                    .with_context(|| format!("Failed to read dir ('{}')", home.clone() + &value))?;
                let path_to_strip = &PathBuf::from(extend(&home, &value)?);
                let path = strip_path(&directory.path(), &path_to_strip)?;
                trace!(
                    "Including: '{}' (after stripping '{}' from '{}' -> '{}' + '/' + '{}')",
                    path.display(),
                    directory.path().display(),
                    path_to_strip.display(),
                    home,
                    value
                );

                let gen_key = MapKey::new_ones_from_path(path_to_str(path)?, 1);
                local_mappings
                    .insert(
                        &gen_key,
                        path_to_str(strip_path(&directory.path(), &PathBuf::from(&home))?)?,
                    )
                    .with_context(|| format!("Failed to include path: '{}'", path.display()))?;
            }

            trace!("{}", local_mappings);

            trace!(
                "'{}' -> '{:#?}'",
                MapKey::display(&key),
                local_mappings.root_node()
            );
            mappings.interleave(&key, local_mappings.root_node().to_owned())?;
        }
        current_depth += 1;
    }

    match args.command {
        Command::Visualize { .. } => println!("{}", mappings),
        Command::Generate { .. } => println!("{}", mappings.to_lf_mappings(args.home_name)),
    }

    Ok(())
}

fn extend(base: &str, value: &str) -> Result<String> {
    let base_path = PathBuf::from(base);
    let value_path = PathBuf::from(value);

    Ok(path_to_str(&base_path.join(&value_path))?.to_owned())
}

fn is_hidden(entry: &DirEntry) -> bool {
    entry
        .file_name()
        .to_str()
        .map(|s| s.starts_with("."))
        .unwrap_or(false)
}

fn is_dir(entry: &DirEntry) -> bool {
    entry.file_type().is_dir()
}

fn strip_path<'a>(path: &'a Path, to_strip: &Path) -> Result<&'a Path> {
    path.strip_prefix(&to_strip).with_context(|| {
        format!(
            "'{}' is not under the specified home path ('{}')!",
            path.display(),
            to_strip.display()
        )
    })
}

fn path_to_str(path: &Path) -> Result<&str> {
    path.to_str().with_context(|| {
        format!(
            "\
Can't derive a keymapping from path: '{}' \
because it can't be turned to a string
",
            path.display()
        )
    })
}

// fn gen_lf_mappings(home_name: PathBuf, char_num: usize, rel_dirs: Vec<PathBuf>) {
//     let mut mappings_vec = vec![];
//     let mut index_counter = 0;
//     rel_dirs.iter().for_each(|rel_dir| {
//         mappings_vec.push(vec![Mapping::new(
//             &gen_hot_key(rel_dir, rel_dir, char_num),
//             rel_dir,
//             rel_dir,
//             None,
//         )]);
//         get_dir(rel_dir.to_owned()).iter().for_each(|path| {
//             mappings_vec[index_counter].push(Mapping::new(
//                 &gen_hot_key(
//                     path,
//                     path.parent().expect("All paths here should have parents"),
//                     char_num,
//                 ),
//                 path,
//                 &path
//                     .parent()
//                     .expect("All paths here should have parents")
//                     .to_owned(),
//                 None,
//             ));
//         });
//         index_counter += 1;
//     });
//     print_mappings(&mappings_vec, home_name);
//     mappings_vec
//         .into_iter()
//         .for_each(|rel_dir_mapping: Vec<Mapping>| {
//             let mut hash_map = sort_mapping_by_hot_key(rel_dir_mapping.clone());
//             //dbg!(hash_map);
//             hash_map.insert("gsi".to_owned(), vec![rel_dir_mapping[0].clone()]);
//         });
// }
//
// fn sort_mapping_by_hot_key(mut mappings: Vec<Mapping>) -> HashMap<String, Vec<Mapping>> {
//     mappings.sort_by_key(|mapping| mapping.hot_key.clone());
//
//     let mut filtered_mappings: HashMap<String, Vec<Mapping>> = HashMap::new();
//     mappings.iter().for_each(|mapping| {
//         filtered_mappings.insert(mapping.hot_key.clone(), vec![]);
//     });
//     //dbg!(&mappings);
//
//     let mut index_counter = 1;
//     mappings.iter().for_each(|mapping| {
//         if mappings.len() > index_counter {
//             let next_mapping = &mappings[index_counter];
//             let vec = filtered_mappings
//                 .get_mut(&mapping.hot_key)
//                 .expect("This existst as it has been initialized");
//
//             if &next_mapping.hot_key == &mapping.hot_key {
//                 vec.push(mapping.clone());
//                 vec.push(next_mapping.clone());
//             } else {
//                 vec.push(mapping.clone());
//             }
//
//             let new_vec = vec.to_owned();
//             filtered_mappings.insert(mapping.hot_key.to_owned(), new_vec);
//         }
//
//         index_counter += 1;
//     });
//     filtered_mappings
// }
//
// fn print_mappings(mappings: &Vec<Vec<Mapping>>, home_name: PathBuf) {
//     for mapping in mappings {
//         mapping.iter().for_each(|map| {
//             println!(
//                 "{} = \"cd {}\";",
//                 map.hot_key,
//                 map.path
//                     .display()
//                     .to_string()
//                     .replace(home_name.to_str().expect("This should be UTF-8"), "~")
//             );
//         });
//
//         println!("# -------------");
//     }
// }