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

use log::info;
use regex::Regex;
use serde::{Deserialize, Serialize};
use ropey::Rope;
use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity, Position, Range, Url};

use crate::{diagnostics::find_non_whitespace_indices, server::LspServer};

use super::AbstractLinterConfiguration;

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

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

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

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

    fn provide_diagnostics(
        &self,
        uri: &Url,
        rope: &Rope,
        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 child = Command::new(&invoke_name)
            .stdin(Stdio::piped())
            .stderr(Stdio::piped())
            .stdout(Stdio::piped())
            .args(&self.linter.args)
            .arg(path_string)
            .spawn()
            .ok()?;

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

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

        // 初始化一个正则捕获器
        static REGEX_STORE: std::sync::OnceLock<Regex> = std::sync::OnceLock::new();
        // {filename}:{errorno}: {error tag}(:{error description})
        let regex = REGEX_STORE.get_or_init(|| { Regex::new(r"^(.*?)\s*:\s*(\d+)\s*:\s*(\w+)\s*(?::\s*(.*))?$").unwrap() });

        for error_line in output_string.lines() {

            let captures = match regex.captures(error_line) {
                Some(cap) => cap,
                None => continue 
            };

            let error_no = captures.get(2).unwrap().as_str().trim();
            let error_tag = captures.get(3).unwrap().as_str().trim();
            let error_description = captures.get(4).map_or("", |m| m.as_str()).trim();

            let error_no = match error_no.parse::<usize>() {
                // Icarus Verilog 在报告错误和警告时，行数是从 1 开始计数的。
                Ok(no) => no - 1,
                Err(_) => 0
            };

            if let Some((start_char, end_char)) = find_non_whitespace_indices(rope, error_no) {
                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 }
                };

                // 特殊处理错误信息，比如
                // /home/dide/project/Digital-Test/DIDEtemp/user/sim/FFT/FFT_IFFT_tb.v:81: error: Unknown module type: FFT_IFFT
                // 找不到模块 XXX，此时 error_description 就是 Unknown module type: 找不到的模块名，去 fast 里寻找
                if error_description.starts_with("Unknown module type") {
                    let mut groups = error_description.split(":");
                    if let Some(unknown_module_name) = groups.nth(1) {
                        let unknown_module_name = unknown_module_name.trim();
                        info!("包含 {} ? {}", unknown_module_name, server.srcs.contains_module(unknown_module_name));
                        
                        if server.srcs.contains_module(unknown_module_name) {
                            continue;
                        }
                    }
                }

                let diagnostic = Diagnostic {
                    range,
                    code: None,
                    severity: Some(DiagnosticSeverity::ERROR),
                    source: Some("Digital IDE: iverilog".to_string()),
                    message: format!("{} {}", error_tag, error_description),
                    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!("{}.exe", self.linter.exe_name)
        } else {
            self.linter.exe_name.to_string()
        }
    }
}
