use serde::{Deserialize, Serialize};
use regex::Regex;
use ropey::Rope;
use std::process::{Command, Stdio};
use tower_lsp::lsp_types::*;

use crate::{server::LspServer, utils::command::is_command_valid};

use super::{AbstractLinterConfiguration, LinterStatus};



#[derive(Debug, Serialize, Deserialize)]
pub struct VeribleConfiguration {
    pub language_id: String,
    pub linter: VeribleLinter,
    pub format: VeribleFormat,
}

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

#[derive(Debug, Serialize, Deserialize)]
pub struct VeribleFormat {
    pub enabled: bool,
    pub path: String,
    pub args: Vec<String>,
}

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


impl Default for VeribleFormat {
    fn default() -> Self {
        Self {
            enabled: false,
            path: "verible-verilog-format".to_string(),
            args: Vec::new(),
        }
    }
}




/// syntax checking using verible-verilog-syntax
fn verible_syntax(
    rope: &Rope,
    verible_syntax_path: &str,
    verible_syntax_args: &[String],
) -> Option<Vec<Diagnostic>> {
    let mut child = Command::new(verible_syntax_path)
        .stdin(Stdio::piped())
        .stderr(Stdio::piped())
        .stdout(Stdio::piped())
        .args(verible_syntax_args)
        .arg("-")
        .spawn()
        .ok()?;

    static RE: std::sync::OnceLock<Regex> = std::sync::OnceLock::new();
    let re = RE.get_or_init(|| {
        Regex::new(
            r"^.+:(?P<line>\d*):(?P<startcol>\d*)(?:-(?P<endcol>\d*))?:\s(?P<message>.*)\s.*$",
        )
        .unwrap()
    });
    // write file to stdin, read output from stdout
    rope.write_to(child.stdin.as_mut()?).ok()?;
    
    let output = child.wait_with_output().ok()?;
    if !output.status.success() {
        let mut diags: Vec<Diagnostic> = Vec::new();
        let raw_output = String::from_utf8(output.stdout).ok()?;
        for error in raw_output.lines() {
            let caps = re.captures(error)?;
            let line: u32 = caps.name("line")?.as_str().parse().ok()?;
            let startcol: u32 = caps.name("startcol")?.as_str().parse().ok()?;
            let endcol: Option<u32> = match caps.name("endcol").map(|e| e.as_str().parse()) {
                Some(Ok(e)) => Some(e),
                None => None,
                Some(Err(_)) => return None,
            };
            let start_pos = Position::new(line - 1, startcol - 1);
            let end_pos = Position::new(line - 1, endcol.unwrap_or(startcol) - 1);
            diags.push(Diagnostic::new(
                Range::new(start_pos, end_pos),
                Some(DiagnosticSeverity::ERROR),
                None,
                Some("verible".to_string()),
                caps.name("message")?.as_str().to_string(),
                None,
                None,
            ));
        }
        Some(diags)
    } else {
        None
    }
}


impl AbstractLinterConfiguration for VeribleConfiguration {
    fn new(language_id: &str, invoker: &str, args: Vec<String>) -> Self {
        VeribleConfiguration {
            language_id: language_id.to_string(),
            linter: VeribleLinter::new(invoker, args),
            format: VeribleFormat::default()
        }
    }

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

        Some(diagnostics)
    }

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

    fn get_windows_extension(&self) -> &str {
        "exe"
    }

    fn linter_status(&self) -> LinterStatus {
        match self.language_id.as_str() {
            "verilog" => {
                let invoke_name = self.get_invoke_name("verible-verilog-syntax");
                let available = is_command_valid(&invoke_name);
                LinterStatus {
                    tool_name: "verible-verilog-syntax".to_string(),
                    available,
                    invoke_name
                }
            }

            "systemverilog" => {
                let invoke_name = self.get_invoke_name("verible-verilog-syntax");
                let available = is_command_valid(&invoke_name);
                LinterStatus {
                    tool_name: "verible-verilog-syntax".to_string(),
                    available,
                    invoke_name
                }
            }

            // verible 不支持 vhdl
            _ => {
                LinterStatus::default()
            }
        }
    }
}