use include_dir::{include_dir, Dir};
use nom::{
    branch::alt,
    bytes::{
        complete::{tag, take_while1},
        streaming::take_while,
    },
    character::{
        complete::{alpha1, char, multispace0, multispace1, space0, space1},
        streaming::none_of,
    },
    combinator::{map, opt, recognize},
    multi::{fold_many0, many0, separated_list0},
    sequence::{delimited, pair, preceded, tuple},
    IResult,
};
use once_cell::sync::Lazy;
use regex::Regex;
use std::path::Path;
use std::{collections::HashMap, io};
use std::{collections::HashSet, env::var};

use crate::utils::find_file;

static COMMENT_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r#"(\".*?\"|\'.*?\')|(/\*.*?\*/|//[^\r\n]*\n)"#).unwrap());
static VERSION_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r#"\#\s*version[^\r\n]*\n"#).unwrap());
static INCLUDE_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r#"\#\s*include\s*\"(?P<filename>[a-zA-Z0-9\-\.\/_]+)\""#).unwrap());

static VARIABLE_TYPE_PAT: Lazy<Regex> =
    Lazy::new(|| Regex::new(r#"(\w+)\s+([\w\[\] ]+);"#).unwrap());

static VARIABLE_NAME_PAT: Lazy<Regex> =
    Lazy::new(|| Regex::new(r#"(\w+)\s*(\[(\d+)\])?(\s*[^,]+)?"#).unwrap());

static TYPE_MAP: Lazy<HashMap<&'static str, u32>> = Lazy::new(|| {
    let mut gtypes: HashMap<&str, u32> = HashMap::new();
    gtypes.insert("float", glow::FLOAT);
    gtypes.insert("vec2", glow::FLOAT_VEC2);
    gtypes.insert("vec3", glow::FLOAT_VEC3);
    gtypes.insert("vec4", glow::FLOAT_VEC4);
    gtypes.insert("int", glow::INT);
    gtypes.insert("ivec2", glow::INT_VEC2);
    gtypes.insert("ivec3", glow::INT_VEC3);
    gtypes.insert("ivec4", glow::INT_VEC4);
    gtypes.insert("bool", glow::BOOL);
    gtypes.insert("bvec2", glow::BOOL_VEC2);
    gtypes.insert("bvec3", glow::BOOL_VEC3);
    gtypes.insert("bvec4", glow::BOOL_VEC4);
    gtypes.insert("mat2", glow::FLOAT_MAT2);
    gtypes.insert("mat3", glow::FLOAT_MAT3);
    gtypes.insert("mat4", glow::FLOAT_MAT4);
    gtypes.insert("sampler1D", glow::SAMPLER_1D);
    gtypes.insert("sampler2D", glow::SAMPLER_2D);
    gtypes.insert("sampler3D", glow::SAMPLER_3D);
    gtypes.insert("samplerCube", glow::SAMPLER_CUBE);
    gtypes
});

#[derive(Debug, Clone)]
pub struct Variable {
    pub name: String,
    pub vtype: String,
    pub type_code: u32,
}

fn get_declarations(code: &str, qualifier: Option<&[&str]>) -> Option<Vec<Variable>> {
    if code.is_empty() {
        return None;
    }

    let qualifier_pattern = if let Some(quals) = qualifier {
        quals.join("|")
    } else {
        String::new()
    };

    let type_pattern = if !qualifier_pattern.is_empty() {
        format!(r"{}\s+(\w+)\s+([\w,\[\]\n =\.]+);", qualifier_pattern)
    } else {
        String::from(r"(\w+)\s+([\w\[\] ]+);")
    };

    let re_type = Regex::new(&type_pattern).unwrap();
    let mut variables = Vec::new();

    for caps in re_type.captures_iter(code) {
        let vtype = caps.get(1).unwrap().as_str().to_string();
        let names = caps.get(2).unwrap().as_str();

        for cap in VARIABLE_NAME_PAT.captures_iter(names) {
            let name = cap.get(1).unwrap().as_str().to_string();
            if let Some(size_match) = cap.get(3) {
                let size: usize = size_match.as_str().parse().unwrap();
                if size == 0 {
                    return None;
                }
                for i in 0..size {
                    let iname = format!("{}[{}]", name, i);
                    variables.push(Variable {
                        name: iname,
                        vtype: vtype.clone(),
                        type_code: *TYPE_MAP.get(vtype.as_str()).unwrap(),
                    });
                }
            } else {
                variables.push(Variable {
                    name,
                    vtype: vtype.clone(),
                    type_code: *TYPE_MAP.get(vtype.as_str()).unwrap(),
                });
            }
        }
    }

    Some(variables)
}

pub(super) fn get_uniform(code: &str) -> Vec<Variable> {
    let uniforms = get_declarations(code, Some(&["uniform"]));
    uniforms.unwrap_or_default()
}

pub(super) fn get_const(code: &str) -> Vec<Variable> {
    let consts = get_declarations(code, Some(&["const"]));
    consts.unwrap_or_default()
}

pub(super) fn get_in(code: &str) -> Vec<(Option<usize>, Variable)> {
    let ins = get_declarations(code, Some(&[r#"(layout\s?\(location\s?=(\d+)\)\s+)?in"#]));

    if ins.is_none() {
        return vec![];
    }

    let mut variables: Vec<usize> = Vec::new();

    let type_pattern = r#"layout\s?\(location\s?=(\d+)\)"#;
    let re_type = Regex::new(&type_pattern).unwrap();

    for cap in re_type.captures_iter(code) {
        if let Some(matched) = cap.get(1) {
            variables.push(matched.as_str().parse().unwrap());
        }
    }

    let ins = ins.unwrap();

    if ins.len() != variables.len() && !variables.is_empty() {
        return vec![];
    } else {
        ins.into_iter()
            .enumerate()
            .map(|(i, v)| (variables.get(i).copied(), v))
            .collect()
    }
}

pub(super) fn get_out(code: &str) -> Vec<Variable> {
    let outs = get_declarations(code, Some(&[r#"(layout\(.+\)\w+)?out"#]));
    outs.unwrap_or_default()
}

pub(super) fn get_hooks(code: &str) -> Vec<(String, Option<String>)> {
    if code.is_empty() {
        return vec![];
    }

    let re_hooks = Regex::new(r#"\<(?P<hook>\w+)(\.(?P<subhook>.+))?(?:\([^<>]+\))?\>"#).unwrap();
    let mut hooks = HashSet::new();

    for cap in re_hooks.captures_iter(code) {
        let hook = cap.name("hook").unwrap().as_str().to_string();
        let subhook = cap.name("subhook").map(|m| m.as_str().to_string());
        hooks.insert((hook, subhook));
    }

    hooks.into_iter().collect()
}

// 移除 GLSL 代码中的注释
pub(super) fn remove_comments(code: &str) -> String {
    COMMENT_RE
        .replace_all(code, |caps: &regex::Captures| {
            if caps.get(2).is_some() {
                "".to_string()
            } else {
                caps.get(1).unwrap().as_str().to_string()
            }
        })
        .to_string()
}

// 移除 GLSL 代码中的版本指令
pub(super) fn remove_version(code: &str) -> String {
    VERSION_RE.replace_all(code, "\n").to_string()
}

// 递归合并包含的文件内容
pub fn merge_includes(mut code: String) -> io::Result<String> {
    let mut includes: Vec<String> = vec![];

    for _ in 0..10 {
        if INCLUDE_RE.is_match(&code) {
            let c = INCLUDE_RE.replace_all(&code, |caps: &regex::Captures| {
                let filename = caps.name("filename").unwrap().as_str().to_string();

                if !includes.contains(&filename) {
                    let file_data =
                        find_file(&filename).expect(&format!("File {} not found", filename));

                    let cleaned_contents = remove_comments(&file_data);
                    let new_code = format!(
                        "\n// --- start of \"{}\" ---\n{}\n// --- end of \"{}\" ---\n",
                        filename, cleaned_contents, filename
                    );
                    includes.push(filename);
                    new_code
                } else {
                    "".to_string()
                }
            });

            code = c.to_string();
        } else {
            break;
        }
    }
    Ok(code)
}

mod test {

    use super::{get_declarations, get_hooks, get_in, merge_includes};
    use std::{fs::read_to_string, io::Write};

    #[test]
    fn test_include() {
        let file =
            std::fs::read_to_string("/Users/tsuki/projects/untitled/shaders/transform/polar.glsl")
                .unwrap();

        let mut merged = std::fs::File::create(
            "/Users/tsuki/projects/untitled/shaders/transform/polar_merged.glsl",
        )
        .unwrap();

        merged
            .write_all(merge_includes(file).unwrap().as_bytes())
            .unwrap();
    }

    #[test]
    fn test_declare() {
        let code = "
        uniform float u_time;
        attribute vec3 position;
        uniform vec4 colors[3];
        layout(location = 0) in vec3 position;
        ";

        let variables = get_in(code);
        println!("{:?}", variables);
    }

    #[test]
    fn test_hook() {
        let code = r"
            <update>
            <render.xyz(1, 2)>
            <input.key(escape)>
        ";

        let hooks = get_hooks(code);
        println!("{:?}", hooks);
    }
}
