use crate::{server::{LspConfiguration, LspServer}, utils::get_language_id_by_uri};
use log::info;
use ropey::Rope;
use serde::Deserialize;
use tower_lsp::lsp_types::*;

pub mod common;

pub mod iverilog;
pub mod verible;
pub mod verilator;
pub mod vivado;
pub mod modelsim;

pub use common::*;
pub use iverilog::*;
pub use verible::*;
pub use verilator::*;
pub use vivado::*;
pub use modelsim::*;

/// description
/// 诊断功能需要提供两套函数，一套函数用于从给定路径读取文件并给出诊断结果；一套用于从 lsp 的文件缓冲区直接读取文本然后给出诊断结果。
/// 前者用于扫描整个项目使用，后者在用户实时修改代码时，给出实时的诊断信息。
/// 
/// 诊断模块的每一个子诊断器都需要实现如下的函数
/// - provide_diagnostics: 提供一次诊断
/// - 
/// 获取诊断核心函数
/// - uri: 当前正在诊断的文件的 uri
/// - rope: 当前正在诊断的文件在后端增量更新的文本内容
/// - files: 所有 hdl 文件，方便后续进行联合诊断使用
/// - server: 服务实例
pub fn provide_diagnostics(
    uri: Url,
    rope: &Rope,
    server: &LspServer
) -> PublishDiagnosticsParams {
    let mut diagnostics = Vec::<Diagnostic>::new();
    let language_id = get_language_id_by_uri(&uri);

    let configuration = server.configuration.read().unwrap();

    // 选择对应语言的 lsp
    let linter_configuration = match language_id.as_str() {
        "vhdl" => Some(&configuration.vhdl_linter_configuration),
        "verilog" => Some(&configuration.vlog_linter_configuration),
        "systemverilog" => Some(&configuration.svlog_linter_configuration),
        _ => None
    };

    if linter_configuration.is_none() {
        info!("未知语言 {} 试图发起诊断", language_id);
        return PublishDiagnosticsParams {
            uri, diagnostics,
            version: None
        };
    }

    let linter_configuration = linter_configuration.unwrap();

    // 根据配置决定使用哪一个诊断器
    // 外层代码需要保证只有一个 linter.enable 为 true
    match linter_configuration {
        config if config.iverilog.linter.enabled => {
            info!("iverilog linter enter");
            if let Some(diag) = &mut config.iverilog.provide_diagnostics(&uri, rope, server) {
                diagnostics.append(diag);
            }
        }
        config if config.verilator.linter.enabled => {
            info!("verilator linter enter");
            if let Some(diag) = &mut config.verilator.provide_diagnostics(&uri, rope, server) {
                diagnostics.append(diag);
            }
        }
        config if config.verible.linter.enabled => {
            info!("verible linter enter");
            if let Some(diag) = &mut config.verible.provide_diagnostics(&uri, rope, server) {
                diagnostics.append(diag);
            }
        }
        config if config.modelsim.linter.enabled => {
            info!("modelsim linter enter");
            if let Some(diag) = &mut config.modelsim.provide_diagnostics(&uri, rope, server) {
                diagnostics.append(diag);
            }
        }
        config if config.vivado.linter.enabled => {
            info!("vivado linter enter");
            if let Some(diag) = &mut config.vivado.provide_diagnostics(&uri, rope, server) {
                diagnostics.append(diag);
            }
        }
        _ => {}
    }
    PublishDiagnosticsParams {
        uri, diagnostics,
        version: None
    }
}

/// 根据输入的名字选择诊断器，并更新所有诊断器的基本路径
/// - `linter_name` 为 `"vivado" | "modelsim" | "verilator" | "verible" | "iverilog"`
/// - `language_id` 为 `"vhdl" | "verilog" | "systemverilog"`
/// - `linter_path` 为第三方的可执行文件的路径
pub fn update_diagnostics_configuration(
    server: &LspServer,
    linter_name: &str,
    language_id: &str,
    linter_path: &str
) {
    let mut configuration = server.configuration.write().unwrap();

    // 选择对应语言的 lsp
    let linter_configuration = match language_id {
        "vhdl" => Some(&mut configuration.vhdl_linter_configuration),
        "verilog" => Some(&mut configuration.vlog_linter_configuration),
        "systemverilog" => Some(&mut configuration.svlog_linter_configuration),
        _ => None
    };

    if linter_configuration.is_none() {
        info!("未知语言 {} 试图配置诊断器", language_id);
        return;
    }

    let linter_configuration = linter_configuration.unwrap();

    linter_configuration.iverilog.linter.enabled = false;
    linter_configuration.verilator.linter.enabled = false;
    linter_configuration.verible.linter.enabled = false;
    linter_configuration.modelsim.linter.enabled = false;
    linter_configuration.vivado.linter.enabled = false;

    match linter_name {
        "iverilog" => {
            linter_configuration.iverilog.linter.enabled = true;
            linter_configuration.iverilog.linter.path = linter_path.to_string();
            let invoke_name = linter_configuration.iverilog.get_invoke_name();
            info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, invoke_name);
        }
        "verilator" => {
            linter_configuration.verilator.linter.enabled = true;
            linter_configuration.verilator.linter.path = linter_path.to_string();
            let invoke_name = linter_configuration.verilator.get_invoke_name();
            info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, invoke_name);
        }
        "verible" => {
            linter_configuration.verible.linter.enabled = true;
            linter_configuration.verible.linter.path = linter_path.to_string();
            let invoke_name = linter_configuration.verible.get_invoke_name();
            info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, invoke_name);
        }
        "modelsim" => {
            linter_configuration.modelsim.linter.enabled = true;
            linter_configuration.modelsim.linter.path = linter_path.to_string();
            let invoke_name = {
                if language_id == "vhdl" {
                    linter_configuration.modelsim.get_invoke_name()
                } else {
                    linter_configuration.modelsim.get_invoke_name()
                }
            };
            info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, invoke_name);
        }
        "vivado" => {
            linter_configuration.vivado.linter.enabled = true;
            linter_configuration.vivado.linter.path = linter_path.to_string();
            let invoke_name = {
                if language_id == "vhdl" {
                    linter_configuration.vivado.get_invoke_name()
                } else {
                    linter_configuration.vivado.get_invoke_name()
                }
            };
            info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, invoke_name);
        }
        _ => {
            info!("未找到匹配的诊断器: {}", linter_name);
        }
    }

}

#[derive(Debug, Deserialize)]
#[derive(serde::Serialize)]
pub struct DigitalLinterConfiguration {
    // iverilog 相关的工具配置
    pub iverilog: IverilogConfiguration,
    // verible 相关的工具配置
    pub verible: VeribleConfiguration,
    // verilator 相关的工具配置
    pub verilator: VerilatorConfiguration,
    // modelsim 相关的工具配置
    pub modelsim: ModelsimConfiguration,
    // vivado 相关的工具配置
    pub vivado: VivadoConfiguration
}

impl DigitalLinterConfiguration {
    pub fn new(language_id: &str) -> Self {
        match language_id {
            "vhdl" => {
                DigitalLinterConfiguration {
                    iverilog: IverilogConfiguration::new(
                        language_id, 
                        "iverilog", 
                        vec![
                            "-t null".to_string()
                        ]
                    ),
                    verible: VeribleConfiguration::new(
                        language_id,
                        "verible-verilog-syntax",
                        vec![]
                    ),
                    verilator: VerilatorConfiguration::new(
                        language_id,
                        "verilator",
                        vec![]
                    ),
                    modelsim: ModelsimConfiguration::new(
                        language_id,
                        "vcom",
                        vec![
                            "-quiet".to_string(),
                            "-nologo".to_string(),
                            "-2008".to_string()
                        ]
                    ),
                    vivado: VivadoConfiguration::new(
                        language_id,
                        "xvhdl",
                        vec![
                            "--nolog".to_string()
                        ]
                    )
                }
            }

            "systemverilog" => {
                DigitalLinterConfiguration {
                    iverilog: IverilogConfiguration::new(
                        language_id,
                        "iverilog",
                        vec![
                            "-g2012".to_string()
                        ]
                    ),
                    verible: VeribleConfiguration::new(
                        language_id,
                        "verible-verilog-syntax",
                        vec![]
                    ),
                    verilator: VerilatorConfiguration::new(
                        language_id,
                        "verilator",
                        vec![
                            "--lint-only".to_string(),
                            "-sv".to_string(),
                            "-Wall".to_string()
                        ]
                    ),
                    modelsim: ModelsimConfiguration::new(
                        language_id,
                        "vlog",
                        vec![
                            "-quiet".to_string(),
                            "-nologo".to_string(),
                            "-sv".to_string()
                        ]
                    ),
                    vivado: VivadoConfiguration::new(
                        language_id,
                        "xvlog",
                        vec![
                            "--sv".to_string(),
                            "--nolog".to_string()
                        ]
                    )
                }
            }

            // 默认为 verilog
            _ => {
                DigitalLinterConfiguration {
                    iverilog: IverilogConfiguration::new(
                        language_id,
                        "iverilog", 
                        vec![
                            "-t null".to_string()
                        ]
                    ),
                    verible: VeribleConfiguration::new(
                        language_id,
                        "verible-verilog-syntax",
                        vec![]
                    ),
                    verilator: VerilatorConfiguration::new(
                        language_id,
                        "verilator",
                        vec![
                            "--lint-only".to_string(),
                            "-Wall".to_string()
                        ]
                    ),
                    modelsim: ModelsimConfiguration::new(
                        language_id,
                        "vlog",
                        vec![
                            "-quiet".to_string(),
                            "-nologo".to_string()
                        ]
                    ),
                    vivado: VivadoConfiguration::new(
                        language_id,
                        "xvlog",
                        vec![
                            "--nolog".to_string()
                        ]
                    )
                }
            }
        }
    }
}
