use std::fs;
use std::path::PathBuf;
use std::sync::RwLock;
use std::{collections::HashMap, fs::File};
use std::io::{BufReader, Read};

use bincode::Error;
use regex::Regex;
use ropey::Rope;
use serde::{Serialize, Deserialize};
use sv_parser::{unwrap_node, RefNode};
use xml::reader::{EventReader, XmlEvent};

use super::hdlparam::{FastHdlparam, InstParameter, Macro, Range};
use super::sv_parser::{get_identifier, get_instance_params, get_instance_ports, get_position, get_pp_range};

pub struct PrimitiveText {
    pub name_to_text: RwLock<HashMap<String, String>>
}

impl PrimitiveText {
    pub fn new() -> Self {
        Self {
            name_to_text: RwLock::new(HashMap::<String, String>::new())
        }
    }

    pub fn update_text(&self, name: &str, text: &str) {
        let mut text_map = self.name_to_text.write().unwrap();
        text_map.insert(name.to_owned(), text.to_owned());
    }

    pub fn get_comment(&self, name: &str, text: &str) -> String {
        if let Some(comment) = PrimitiveText::extract_pattern_comments(text).get(name) {
            "// ".to_string() + comment
        } else {
            "".to_string()
        }
    }

    fn extract_pattern_comments(text: &str) -> HashMap<String, String> {
        let re = Regex::new(r"\s*\.(?P<x>[^\(]+)\((?P<y>[^\)]+)\)(?:,)?\s*//\s*(?P<z>.+)").unwrap();
        text.lines()
            .filter_map(|line| {
                re.captures(line).map(|caps| {
                    let name = caps.name("x").unwrap().as_str().to_string();
                    let comment = caps.name("z").unwrap().as_str().to_string();
                    (name, comment)
                })
            })
            .collect()
    }
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Template {
    pub text: String,
	pub fast: FastHdlparam
}

#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct PrimitiveXml {
	pub name_to_template: HashMap<String, Template>
}

pub fn load_primitive_bin(file: &str) -> Option<PrimitiveXml> {
    if let Ok(mut file) = File::open(file) {
        let mut buffer = Vec::new();
        match file.read_to_end(&mut buffer) {
            Ok(_) => {
                let deserialized_data: PrimitiveXml = bincode::deserialize(&buffer).unwrap();
                Some(deserialized_data)
            }
            Err(_) => {
                None
            }
        }
    } else {
        None
    }
}

#[allow(unused)]
pub fn init_parse_primitive_files(dir: &str) -> Result<PrimitiveXml, Error> {
    let mut primitive_xml = PrimitiveXml::default();
    for entry in fs::read_dir(dir)? {
        let entry = entry?;
        let path = entry.path();
        if let Some(ext) = path.extension() {
            if ext == "xml" {
                println!("parse xml file {}", path.to_str().unwrap());
                if let Ok(primitive) = xml_parser(path.to_str().unwrap()) {
                    primitive_xml.name_to_template.extend(
                        primitive.name_to_template
                    );
                };
            }
        }
    }
    Ok(primitive_xml)
}

fn xml_parser(path: &str) -> Result<PrimitiveXml, Error> {
    let file = File::open(path)?;
    let file = BufReader::new(file);

    let mut parser = EventReader::new(file);

	let mut primitive_xml = PrimitiveXml::default();

    loop {
        match parser.next() {
            Ok(XmlEvent::StartElement { name, .. }) => {
                if name.local_name == "Template" {
                    if let Some((inst_name , template)) = xml_parse_template(&mut parser) {
                        primitive_xml.name_to_template.insert(inst_name, template);
                    }
                }
            }
            Ok(XmlEvent::EndElement { name }) => {
                if name.local_name == "RootFolder" { break; }
            }
            _ => ()
        }
    }

    Ok(primitive_xml)
}

fn xml_parse_template(parser: &mut EventReader<BufReader<File>>) -> Option<(String, Template)> {
    loop {
        match parser.next() {
            Ok(XmlEvent::Characters(text)) => {
                if text.contains("Cut code below this line") {
                    if let Some((name, text, fast)) = xml_parse_text(&text) {
                        return Some((name, Template { text, fast }));
                    } else {
                        return None
                    }
                }
            }
            _ => return None
        }
    }
}

fn xml_parse_text(text: &str) -> Option<(String, String, FastHdlparam)> {
    let module_string = "module primitive_module();\n".to_string() + text + "\nendmodule";
    if  let Ok((syntax_tree, _)) = sv_parser::parse_sv_str(
        &module_string,
        PathBuf::new(),
        &HashMap::new(),
        &Vec::<PathBuf>::new(),
        true,
        true
    ) {
        let doc = Rope::from_str(syntax_tree.text.text());

        let mut res_inst_name = String::new();
        let mut hdlparam = FastHdlparam {
            fast_macro: Macro {
                defines: Vec::new(),
                errors: Vec::new(),
                includes: Vec::new(),
                invalid: Vec::new()
            },
            content: Vec::new(),
            file_type: "primitives".to_string()
        };
        let res_text = syntax_tree.text.text().to_string();

        for node in &syntax_tree {
            match node {
                RefNode::ModuleDeclaration(x) => {
                    let start_keyword = unwrap_node!(x, Keyword).unwrap();
                    let start_keyword = get_identifier(start_keyword).unwrap();
                    let start_pos = get_position(&doc, start_keyword, 0);
                    let module_range = get_pp_range(&doc, RefNode::ModuleDeclaration(x));
                    let module_range = Range { start: start_pos, end: module_range.end };
    
                    let id = unwrap_node!(x, ModuleIdentifier).unwrap();
                    let id = get_identifier(id).unwrap();
                    let name = syntax_tree.get_str(&id).unwrap();
                    hdlparam.new_module(name, module_range);
                }
                RefNode::ModuleInstantiation(x) => {
                    if let Some(id) = unwrap_node!(x, ModuleIdentifier) {
                        let id = get_identifier(id).unwrap();
                        let inst_type = syntax_tree.get_str(&id).unwrap();
                        let start_pos = get_position(&doc, id, 0);
                        let range = get_pp_range(&doc, RefNode::ModuleInstantiation(x));
                        let inst_range = Range { start: start_pos, end: range.end };
    
                        if let Some(id) = unwrap_node!(x, HierarchicalInstance) {
                            let hier_node = id.clone();
                            let id = get_identifier(id).unwrap();
                            let name = syntax_tree.get_str(&id).unwrap();
    
                            let param_range = match unwrap_node!(x, ParameterValueAssignment) {
                                Some(inst_node) => {
                                    get_pp_range(&doc, inst_node).to_option()
                                }
                                _ => None
                            };
    
                            let inst_param_assignments = if let Some(param_node) = unwrap_node!(x, ParameterValueAssignment) {
                                get_instance_params(&syntax_tree, &doc, param_node)
                            } else {
                                Vec::<InstParameter>::new()
                            };
    
                            let inst_port_assignments = get_instance_ports(&syntax_tree, &doc, hier_node.clone());
                            let port_range = get_pp_range(&doc, hier_node).to_option();
    
                            res_inst_name = inst_type.to_string();
                            hdlparam.add_instance(
                                name, inst_type, inst_range,
                                param_range, inst_param_assignments,
                                port_range, inst_port_assignments
                            );
                        }
                    }
                }
                RefNode::GateInstantiation(x) => {
                    let id = unwrap_node!(x, GateInstantiation).unwrap();
                    let id = get_identifier(id).unwrap();
                    let inst_type = syntax_tree.get_str(&id).unwrap();
                    let start_pos = get_position(&doc, id, 0);
                    let range = get_pp_range(&doc, RefNode::GateInstantiation(x));
                    let inst_range = Range { start: start_pos, end: range.end };
    
                    match unwrap_node!(x, NInputGateInstance, NOutputGateInstance) {
                        Some(id) => {
                            let gate_node = id.clone();
                            let id = get_identifier(id).unwrap();
                            let name = syntax_tree.get_str(&id).unwrap();
    
                            let param_range = None;
                            let inst_port_assignments = get_instance_ports(&syntax_tree, &doc, gate_node.clone());
                            let port_range = get_pp_range(&doc, gate_node).to_option();
    
                            hdlparam.add_instance(
                                name, inst_type, inst_range,
                                param_range, Vec::<InstParameter>::new(),
                                port_range, inst_port_assignments
                            );
                        }
                        _ => ()
                    }
                }
                _ =>  ()
            }
        }

        for module in hdlparam.content.iter_mut() {
            module.name = res_inst_name.clone();
        }
        Some((res_inst_name, res_text, hdlparam))
    } else {
        None
    }
}

#[cfg(test)]
mod tests {
    use std::{fs::{self, File}, io::Write, path::Path};

    use super::{init_parse_primitive_files, xml_parser};

    const TESTFILE: &str = "primitive_files/verilog2.xml";
    const TESTDIR: &str = "primitive_files";

    #[test]
    
    fn gen_primitive_bin() {
        if let Ok(primitive_info) = init_parse_primitive_files("primitive_files") {
            let serialized_data = bincode::serialize(&primitive_info).unwrap();
            let path = "target/primitive.bin";
            let mut file = File::create(path).unwrap();
            let _ = file.write_all(&serialized_data);
        } else {
            println!("parse primitive error");
        }
    }

    #[test]
    fn read_primitive_bin() {
        let target_path = "target/primitive.bin";
        let load = crate::core::primitive_parser::load_primitive_bin(&target_path);
        assert!(load.is_some());
    }

    #[test]
    fn test_xml() {
        let res = xml_parser(TESTFILE);
        match res {
            Ok(r) => {
                println!("res len {:#?}", r.name_to_template.len())
            }
            Err(e) => {
                println!("error {:#?}", e)
            }
        }
    }

	#[test]
    fn test_dir() {
        // 判断路径是否存在且为文件夹
        let path = Path::new(TESTDIR);
        if path.exists() && path.is_dir() {
            // 递归遍历文件夹
            if let Err(e) = traverse_directory(path) {
                eprintln!("Error: {}", e);
            }
        } else {
            eprintln!("Path does not exist or is not a directory");
        }
    }

    fn traverse_directory(dir: &Path) -> Result<(), Box<dyn std::error::Error>> {
        if dir.is_dir() {
            for entry in fs::read_dir(dir)? {
                let entry = entry?;
                let path = entry.path();
    
                if path.is_dir() {
                    // 递归遍历子文件夹
                    traverse_directory(&path)?;
                } else if path.is_file() {
                    // 检查文件扩展名
                    if let Some(ext) = path.extension() {
                        if ext == "xml" {
                            println!("Test file: {:?}", path);
                            let file_path = path.to_str().unwrap();
                            let _ = xml_parser(file_path);
                        }
                    }
                }
            }
        }
        Ok(())
    }
}