use tracing::{debug, error};
use once_cell::sync::Lazy;
use std::collections::BTreeMap;
use url_encoded_data;

pub static BANG_PREFIX: &str = "!";

#[derive(Debug)]
struct Bang<'a> {
    pub url: &'a str,
    pub aliases: &'a [&'a str]
}

impl<'a> Bang<'_> {
    fn new(url: &'a str, aliases: &'a [&'a str]) -> Bang<'a> {
        Bang {url, aliases}
    }
}

static BUILTIN_BANGS: Lazy<BTreeMap<&str, Bang>> = Lazy::new(|| {
    let mut bangs = BTreeMap::new();
    bangs.insert("Google", Bang::new("https://google.com/search?q={}", &["g", "google"] as &[&str]));

    bangs.insert("DuckDuckGo", Bang::new("https://duckduckgo.com/?q={}", &["d", "ddg", "duckduckgo"] as &[&str]));

    bangs.insert("Wikipedia", Bang::new("https://wikipedia.org/w/index.php?search={}", &["w", "wiki", "wikipedia"] as &[&str]));
    bangs
});

#[derive(Debug, Clone)]
struct BangLoc<'b> {
    pub url: &'b str,
    pub start_idx: usize,
    pub len: usize
}

impl<'b> BangLoc<'_> {
    fn new(url: &'b str, start_idx: usize, len: usize) -> BangLoc<'b> {
        BangLoc {url, start_idx, len}
    }
}

pub fn redirect_bang(query: &String) -> Option<String> {
    if !query.contains(BANG_PREFIX) {
        return None;
    }
    let bangs = query.match_indices(BANG_PREFIX).filter(|(bang_start_idx, _)| {
        if *bang_start_idx == 0 || query.chars().nth(*bang_start_idx - 1).unwrap().is_whitespace() {
            true
        } else {
            false
        }
    }).map(|(bang_start_idx, _)| {
        let rest = query.get(bang_start_idx + 1..query.len()).unwrap();
        BUILTIN_BANGS.iter().map(|(_, bang)| {
            let alias = bang.aliases.iter()
                .filter(|alias| rest.starts_with(**alias))
                .filter(
                    |alias| rest.chars()
                    .nth(alias.len())
                    .unwrap_or(' ')
                    .is_whitespace())
                .max_by(|a, b| a.len().cmp(&b.len()))?;
            Some(BangLoc::new(bang.url, bang_start_idx, alias.len()))
        }).filter(|bang| bang.is_some()).map(|bang| bang.unwrap()).next()
    }).filter(|bang| bang.is_some())
    .map(|bang| bang.unwrap())
    .collect::<Vec<_>>();


    let bang = bangs.first()?;
    let end_idx = {
        let mut end_idx = bang.start_idx + 1 + bang.len;
        if end_idx < query.len() {
            end_idx += 1;
        }
        end_idx
    };

    let start_idx = if end_idx == query.len() && bang.start_idx > 0 {
        bang.start_idx - 1
    } else {
        bang.start_idx
    };


    let query_split = query.split_once(query.get(start_idx..end_idx).unwrap()).unwrap();

    let query_trimmed = format!("{}{}", query_split.0, query_split.1);

    // A hack to get URL escaping without using a proper URL layout, hopefully has no other issues apart from prepending '=' to the string
    let query_encoded = url_encoded_data::stringify(&[("", query_trimmed.as_str())]);
    let query_encoded = query_encoded.get(1..query_encoded.len()).unwrap().to_owned();


    let bang_url_split = bang.url.split_once("{}").unwrap();

    let bang_url = format!(
        "{}{}{}",
        bang_url_split.0,
        query_encoded,
        bang_url_split.1
    );

    Some(bang_url)
}
