use std::{collections::HashSet, process::{Command, Stdio}};

#[allow(unused)]
use log::info;
use regex::{escape, Regex};
use serde::{Deserialize, Serialize};
use crate::{diagnostics::find_non_whitespace_indices, server::LspServer, utils::from_uri_to_escape_path_string};
use ropey::Rope;
use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity, NumberOrString, Position, Range, Url};

use super::AbstractLinterConfiguration;

#[derive(Debug, Serialize, Deserialize)]
pub struct VivadoConfiguration {
    pub language_id: String,
    pub linter: VivadoLinter,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct VivadoLinter {
    pub name: String,
    pub exe_name: String,
    /// 目前是否启动
    pub enabled: bool,
    pub path: String,
    pub args: Vec<String>,
}

impl VivadoLinter {
    fn new(invoker: &str, args: Vec<String>) -> Self {
        Self {
            name: "vivado".to_string(),
            exe_name: invoker.to_string(),
            enabled: false,
            path: invoker.to_string(),
            args,
        }
    }
}

impl AbstractLinterConfiguration for VivadoConfiguration {
    fn new(language_id: &str, invoker: &str, args: Vec<String>) -> Self {
        VivadoConfiguration {
            language_id: language_id.to_string(),
            linter: VivadoLinter::new(invoker, args)
        }
    }

    fn provide_diagnostics(
        &self,
        uri: &Url,
        #[allow(unused)]
        rope: &Rope,
        #[allow(unused)]
        server: &LspServer
    ) -> Option<Vec<Diagnostic>> {
        let mut diagnostics = Vec::<Diagnostic>::new();
        
        let invoke_name = self.get_invoke_name();
        let pathbuf = uri.to_file_path().unwrap();
        let path_string = pathbuf.to_str().unwrap();

        let cache_info = server.cache.cache_info.read().unwrap();
        
        let cwd = match &cache_info.linter_cache {
            Some(pc) => pc,
            None => {
                info!("缓存系统尚未完成初始化，本次诊断取消");
                return None;
            }
        };

        // vivado 比较特殊，需要先分析出当前文件用了哪些其他文件的宏，然后把那部分宏所在的文件加入编译参数中
        let dependence_files = get_all_dependence_files(uri, server);        
        
        let child = Command::new(&invoke_name)
            .current_dir(cwd)
            .stdin(Stdio::piped())
            .stderr(Stdio::piped())
            .stdout(Stdio::piped())
            .args(&self.linter.args)
            .args(dependence_files)
            .arg(path_string)
            .spawn()
            .ok()?;

        let output = child.wait_with_output().ok()?;
        let output_string = String::from_utf8(output.stdout).ok()?;

        info!("vivado linter: {:?}, output:\n{}", path_string, output_string);

        static REGEX_STORE: std::sync::OnceLock<Regex> = std::sync::OnceLock::new();
        let regex = REGEX_STORE.get_or_init(|| { Regex::new(r"ERROR: \[VRFC (?P<error_code>\S+)] (?P<description>.+) \[(?P<file>.+):(?P<line>\d+)\]").unwrap() });  
        
        for error_line in output_string.lines() {
            let caps = match regex.captures(error_line) {
                Some(caps) => caps,
                None => continue
            };

            let error_code = caps.name("error_code").unwrap().as_str();
            let error_description = caps.name("description").unwrap().as_str();
            let error_no = caps.name("line").unwrap().as_str();
            let error_no = match error_no.parse::<usize>() {
                // Xilinx Vivado xvlog 在报告错误和警告时，行数是从 1 开始计数的。
                Ok(no) => no - 1,
                Err(_) => 0
            };

            if let Some((start_char, end_char)) = find_vivado_suitable_range(rope, error_no, error_description) {
                let range = Range {
                    start: Position { line: error_no as u32, character: start_char as u32 },
                    end: Position { line: error_no as u32, character: end_char as u32 }
                };
                
                let diagnostic = Diagnostic {
                    range,
                    code: Some(NumberOrString::String(error_code.to_string())),
                    severity: Some(DiagnosticSeverity::ERROR),
                    source: Some("Digital IDE: vivado".to_string()),
                    message: error_description.to_string(),
                    related_information: None,
                    tags: None,
                    code_description: None,
                    data: None
                };
                diagnostics.push(diagnostic);
            }   
        }


        Some(diagnostics)
    }

    fn get_linter_path(&self) -> &str {
        &self.linter.path
    }

    fn get_exe_name(&self) -> String {
        if std::env::consts::OS == "windows" {
            format!("{}.bat", self.linter.exe_name)
        } else {
            self.linter.exe_name.to_string()
        }
    }
}

/// 计算出当前文件所有用到的别的文件（比如使用了其他文件的宏）
/// 必须把这些文件也编入诊断中，才能基于 vivado 得到合理的结果
fn get_all_dependence_files(
    uri: &Url,
    server: &LspServer
) -> Vec<String> {
    let mut files = HashSet::<String>::new();
    let path_string = from_uri_to_escape_path_string(uri).unwrap();
    let mut used_macro_names = HashSet::<String>::new();

    let hdl_param = server.srcs.hdl_param.clone();
    let path_to_hdl_file = hdl_param.path_to_hdl_file.read().unwrap();
    if let Some(hdl_file) = path_to_hdl_file.get(&path_string) {
        for macro_symbol in &hdl_file.parse_result.symbol_table.macro_usages {
            used_macro_names.insert(macro_symbol.name.to_string());
        }
    }

    for (file_path, hdl_file) in path_to_hdl_file.iter() {
        if file_path == path_string.as_str() {
            // 只看其他文件
            continue;
        }
        
        for define in hdl_file.fast.fast_macro.defines.iter() {
            let macro_name = define.name.to_string();
            if used_macro_names.contains(&macro_name) {
                used_macro_names.remove(&macro_name);
                files.insert(file_path.to_string());
            }
        }

        // 如果 unused_macro_names 都找到了对应的 path，直接 break 即可
        if used_macro_names.is_empty() {
            break;
        }
    }
    
    // 释放锁
    drop(path_to_hdl_file);

    files.into_iter().collect()
} 

/// 根据 vivado 返回的诊断信息，返回适合的错误在那一行的起始位置
/// 默认是返回该行的第一个非空字符到最后一个非空字符中间的位置，即 find_non_whitespace_indices
fn find_vivado_suitable_range(
    rope: &Rope,
    error_no: usize,
    error_description: &str
) -> Option<(usize, usize)> {
    // 一般 vivado 会把出错的关键词用单引号包起来
    // 只需要提取这个单词，并在行中匹配它，如果任何一步失败，都采用 find_non_whitespace_indices 即可
    static REGEX_STORE: std::sync::OnceLock<Regex> = std::sync::OnceLock::new();
    let regex = REGEX_STORE.get_or_init(|| { Regex::new(r"'([^']+)'").unwrap() });  
    let error_keyword = match regex.captures(error_description) {
        Some(caps) => {
            caps.get(1).unwrap().as_str()
        }
        None => {
            return find_non_whitespace_indices(rope, error_no)
        }
    };

    let error_keyword = escape(error_keyword);
    let pattern = format!(r"\b(?i){}\b", error_keyword);
    let regex = Regex::new(&pattern).unwrap();

    if let Some(line_text) = rope.line(error_no).as_str() {
        // 处理特殊情况： error_keyword 为 ;
        if error_keyword == ";" {
            if let Some(index) = line_text.find(";") {
                return Some((index, index));
            }
        }
        if let Some(mat) = regex.find(line_text) {
            // info!("mat {} {}", mat.start(), mat.end());
            return Some((mat.start(), mat.end()));
        }
    }

    find_non_whitespace_indices(rope, error_no)
}


/// 判断是否为类似于 xxx ignored 的错误
/// ERROR: [VRFC 10-8530] module 'main' is ignored due to previous errors [/home/dide/project/Digital-Test/Digital-macro/user/src/main.v:1]
#[allow(unused)]
fn is_ignore_type(diag: &Diagnostic) -> bool {
    // 获取 vrfc 编码
    let vrfc_code = if let Some(NumberOrString::String(code)) = &diag.code {
        code
    } else {
        return false;
    };

    match vrfc_code.as_str() {
        "10-8530" => {
            true
        }

        _ => {
            false
        }
    }
}
