use tower_lsp::lsp_types::*;

/// 文档：IEEE 1364-2005
/// author: LSTM-Kirigaya
/// date: 2024.12.13

pub const VLOG_DIRECTIVES: &[(&str, &str, &str)] = &[
    // 1800-2009 22.13 `__FILE__ and `__LINE__ 
    (
        "__FILE__",
        "`__FILE__",
r#"用于获取当前文件名
```verilog
// 文件名： /path/to/your/project/test.v
`timescale 1ns/1ps

module test;
    initial begin
        // 输出 当前文件名：/path/to/your/project/test.v
        $display("当前文件名: %s", `__FILE__);
    end
endmodule
```
"#),
    (
        "__LINE__",
        "`__LINE__",
r#"用于获取当前行号
```verilog
// 文件名： /path/to/your/project/test.v
`timescale 1ns/1ps

module test;
    initial begin
        // 使用 __FILE__ 和 __LINE__ 宏输出当前文件名和行号
        $display("当前文件名: %s, 当前行号: %0d", `__FILE__, `__LINE__);
    end
endmodule
```
"#),
    // 19.1 `celldefine and `endcelldefine
    (
        "celldefine",
        "`celldefine\n$1\n`endcelldefine",
r#"开始定义单元格
`celldefine` 和 `endcelldefine` 是用于定义单元库模块的编译指令。它们通常用于定义可重用的硬件模块（如标准单元库），并控制这些模块的编译行为。
```verilog
// 使用 celldefine 和 endcelldefine 定义一个单元库模块
`celldefine
module my_cell (
    input a,
    input b,
    output out
);
    assign out = a & b; // 简单的与门逻辑
endmodule
`endcelldefine

// 测试模块
module test;
    reg a, b;
    wire out;

    // 实例化单元库模块
    my_cell uut (
        .a(a),
        .b(b),
        .out(out)
    );

    initial begin
        // 初始化输入
        a = 0;
        b = 0;

        // 仿真逻辑
        #10;
        a = 1;
        #10;
        b = 1;
        #10;
        $display("输出结果: %b", out);
    end
endmodule
```

使用 `celldefine` 的优势：
- 编译器可以将 `celldefine` 模块视为标准单元库的一部分，从而进行更高效的编译优化。例如，编译器可以提前处理这些模块的逻辑，减少仿真时的计算开销。
- 在大型设计中，标准单元库模块可能会被多次实例化。使用 celldefine 可以避免重复编译相同的模块，从而减少编译时间和内存占用。
"#),
    (
        "endcelldefine",
        "`endcelldefine",
        "结束单元格定义"
    ),
    // 19.2 `default_nettype
    (
        "default_nettype",
        "`default_nettype $1",
        "设置默认的网络类型"
    ),
    // 19.3 `define and `undef
    (
        "define",
        "`define $1 $2",
r#"定义一个宏

### 语法

```verilog
`define MACRO_NAME value
```

### 示例

```verilog
// 使用 `define 定义常量和代码片段
`define PORT_NUM 8       // 定义常量 PORT_NUM 为 8
`define ADD_ONE(x) (x + 1) // 定义带参数的宏 ADD_ONE

module test;
    reg [(`PORT_NUM - 1):0] data; // 使用常量 PORT_NUM
    integer value;

    initial begin
        // 使用宏 ADD_ONE
        value = 5;
        $display("原始值: %0d", value);
        value = `ADD_ONE(value);
        $display("加一后的值: %0d", value);

        // 使用常量 PORT_NUM
        data = 8'hFF;
        $display("数据宽度: %0d 位", `PORT_NUM);
        $display("数据值: %h", data);
    end
endmodule
```
"#),
    (
        "undef",
        "`undef $1",
        "取消定义一个宏"
    ),
    // 1800-2009 22.5 `define, `undef and `undefineall 
    (
        "undefineall",
        "`undefineall",
        "取消定义所有宏"
    ),
    // 19.4 `ifdef, `else, `elsif, `endif, `ifndef
    (
        "ifdef",
        "`ifdef $1\n\t//ifdef\n\t$2\n`else\n//else\n\t$3\n`endif",
r#"ifdef、else 和 endif 是条件编译指令，用于根据宏定义的存在与否来选择性地编译代码。它们通常用于实现条件编译逻辑，以便在不同的编译环境下生成不同的代码。

### 语法

```verilog
`ifdef MACRO_NAME
    // 如果 MACRO_NAME 已定义，编译此段代码
`else
    // 如果 MACRO_NAME 未定义，编译此段代码
`endif
```

### 示例

```verilog
// 使用 `ifdef 和 `else 进行条件编译
`define DEBUG // 定义 DEBUG 宏

module test;
    reg [7:0] data;

    initial begin
        data = 8'hFF;

        // 使用 `ifdef 和 `else 进行条件编译
        `ifdef DEBUG
            $display("调试模式: 数据值 = %h", data);
        `else
            $display("发布模式: 数据值 = %h", data);
        `endif
    end
endmodule
```

假设上述代码保存在文件 test.v 中，仿真输出可能如下：

```
调试模式: 数据值 = FF
```

"#
    ),
    (
        "else",
        "`else",
        "条件编译的 else 分支"
    ),
    (
        "elsif",
        "`elsif $1",
        "条件编译的 elsif 分支"
    ),
    (
        "endif",
        "`endif",
        "结束条件编译"
    ),
    (
        "ifndef",
        "`ifndef $1",
        "如果未定义宏，则编译代码"
    ),
    // 19.5 `include
    (
        "include",
        "`include $1",
        "包含一个外部文件"
    ),
    // 19.6 `resetall
    (
        "resetall",
        "`resetall",
    // resetall 也常用于开始
    // 1834-2005中指出，建议的用法是将 `resetall 放在每个源文本文件的开头，后面紧跟着文件中所需的指令。
    // 通常可用于源文本开头，避免该文件受到其他文件代码的影响。或放置在末尾，避免编译器设置的进一步传递
r#"`resetall` 用于重置所有编译器设置为默认值。通常可用于源文本开头，避免该文件受到其他文件代码的影响。或放置在末尾，避免编译器设置的进一步传递。

1834-2005中指出，建议的用法是将 `resetall 放在每个源文本文件的开头，后面紧跟着文件中所需的指令。

```verilog
`timescale 1ns/1ps // 设置时间单位和精度

module test;
    // 一些 verilog 代码
endmodule

`resetall // 重置所有编译器设置，上文的 timescale 设置自此开始无效
```
"#
    ),
    // 19.7 `line
    (
        "line",
        "`line $1 $2 $3",
r#"`line` 用于指定当前代码的行号和文件名。它通常用于调试或日志记录，以便在仿真或编译时能够追踪代码的来源。

### 语法
```verilog
`line line_number "filename" level
```

### 示例
```verilog
// 使用 `line 指令指定行号和文件名
`line 10 "example.v" 0

module test;
    initial begin
        $display("当前行号: %0d", `__LINE__);
        $display("当前文件名: %s", `__FILE__);
    end
endmodule
```
"#
    ),
    // 19.8 `timescale
    (
        "timescale",
        "`timescale ${1:1ns}/${2:1ps}",
    // 原文是The time_precision argument shall be at least as precise as the time_unit argument; it cannot specify a longer unit of time than time_unit.
    // 也即是，仿真时间单位必须大于等于时间精度
    // `timescale 1ns/1ns不会报错，但是`timescale 1ps/1ns会报错
    // [Synth 8-522] parsing error: error in timescale or timeprecision statement. <timeprecision> must be at least as precise as <timeunit>
r#"设置时间单位和精度，设置时间单位和精度，格式为 <code> `timescale </code> 仿真时间单位/时间精度，<code>仿真时间单位</code> 必须大于等于 <code>时间精度</code>，例如
```verilog
`timescale 1ns/1ps

module test;
    initial begin
        $display("当前时间单位: %0t", $time); // 输出当前时间
        #10; // 延迟 10 纳秒
        $display("延迟 10 纳秒后的时间: %0t", $time);
    end
endmodule
```
如果延迟是 #10.5，仿真器会将其解释为 10.5 纳秒，但精度会精确到皮秒级别。

在编译过程中，`timescale` 指令影响这一编译器指令后面所有模块中的时延值，直至遇到另一个 `timescale` 指令 `resetall` 指令。
"#),
    // 19.9 `unconnected_drive and `nounconnected_drive
    (
        "unconnected_drive",
        "`unconnected_drive ${1:pull1}\n$2\n`nounconnected_drive\n",
r#"`nounconnected_drive` 和 `unconnected_drive` 是配合使用的编译指令，用于控制未连接端口的驱动行为。它们通常成对出现，分别用于禁用和启用未连接端口的驱动处理。
### 语法
```verilog
`unconnected_drive (pull1 | pull0) // 启用未连接端口的驱动，若为 pull1，则默认驱动为高电平，若为 pull0，则默认为低电平
    // 你的 verilog 代码
`nounconnected_drive               // 禁用未连接端口的驱动
```

### 示例
```verilog
// 使用 `line 指令指定行号和文件名
`unconnected_drive pull1
module my_and(y, a, b);
    output y;
    input a, b;
    assign y = a & b;
endmodule

module test(y,b);
    output y;
    input b;
    my_and ul(y, ,b);
endmodule
`nounconnected_drive
```
"#
    ),
    (
        "nounconnected_drive",
        "`nounconnected_drive",
r#"`nounconnected_drive` 和 `unconnected_drive` 是配合使用的编译指令，用于控制未连接端口的驱动行为。它们通常成对出现，分别用于禁用和启用未连接端口的驱动处理。
### 语法
```verilog
`unconnected_drive (pull1 | pull0) // 启用未连接端口的驱动，若为 pull1，则默认驱动为高电平，若为 pull0，则默认为低电平
    // 你的 verilog 代码
`nounconnected_drive               // 禁用未连接端口的驱动
```
"#
    ),
    // 19.10 `pragma
    (
        "pragma",
        "`pragma $1",
        "编译指示，用于传递编译器指令"
    ),
    // 19.11 `begin_keywords, `end_keywords
    (
        "begin_keywords",
        "`begin_keywords\n$1\n`end_keywords ",
    // begin_keywords并不是指定Verilog的编译版本，而是指定使用的Verilog关键字版本
r#"`begin_keywords` 和 `end_keywords` 是用于指定 Verilog 关键字版本的编译指令。它们允许设计者在同一个文件中使用不同版本的 Verilog 关键字语法，从而实现兼容性或逐步迁移到新版本。

`begin_keywords` 支持以下 Verilog 版本：

- `1364-1995`：Verilog-1995 标准。
- `1364-2001`：Verilog-2001 标准。
- `1364-2005`：Verilog-2005 标准。
- `1800-2009`：SystemVerilog-2009 标准。
- `1800-2012`：SystemVerilog-2012 标准。

```verilog
// 使用 begin_keywords 和 end_keywords 指定 Verilog 版本
`begin_keywords "1364-2001" // 指定 Verilog-2001 语法

module my_module;
reg [7:0] data;
initial begin
    data = 8'hFF; // 使用 Verilog-2001 的语法
    $display("Verilog-2001 语法: data = %h", data);
end
endmodule

`end_keywords // 结束 Verilog-2001 语法

// 恢复默认的 Verilog 语法
module another_module;
reg [7:0] data;
initial begin
    data = 8'hAA; // 使用默认的 Verilog 语法
    $display("默认语法: data = %h", data);
end
endmodule
```
"#),
    (
        "end_keywords",
        "`end_keywords",
        "结束关键字版本指定"
    ),
    // Annex D Compiler directives
    (
        "default_decay_time",
        "`default_decay_time $1",
r#"`default_decay_time` 用于设置电荷衰减时间（decay time）的默认值。它通常用于模拟电荷存储元件（如电容或寄生电容）的行为。

### 语法
```verilog
`default_decay_time time_value
```
```

"#
    ),
    (
        "default_trireg_strength",
        "`default_trireg_strength $1",
r#"`default_trireg_strength ` 用于设置三态寄存器（trireg）的默认驱动强度（strength）。三态寄存器是一种特殊的寄存器，可以存储电荷，并且在未被驱动时会衰减。"#
    ),
    (
        "delay_mode_distributed",
        "`delay_mode_distributed",
r#"`delay_mode_distributed` 用于设置延迟模式的类型。它通常用于控制逻辑门和网络的延迟分布方式。"#
    ),
    (
        "delay_mode_path",
        "`delay_mode_path",
r#"`delay_mode_path` 用于设置延迟模式的类型。它通常用于控制路径延迟（path delay）的计算方式，特别是在时序逻辑电路中。"#
    ),
    (
        "delay_mode_unit",
        "`delay_mode_unit",
r#"`delay_mode_unit` 用于设置延迟模式的类型。它通常用于控制逻辑门和网络的延迟计算方式，特别是在仿真中需要精确控制延迟时。"#
    ),
    (
        "delay_mode_zero",
        "`delay_mode_zero",
r#"`delay_mode_zero` 用于设置延迟模式的类型。它通常用于控制逻辑门和网络的延迟计算方式，特别是在仿真中需要忽略延迟时。"#
    ),
];

fn make_function_profile(
    description: &str
) -> MarkupContent {
    MarkupContent {
        kind: MarkupKind::Markdown,
        value: format!("{}", description)
    }
}

pub fn provide_vlog_directives_completions() -> Vec<CompletionItem> {
    let mut completion_items: Vec<CompletionItem> = Vec::new();

    for (label, snippet_code, description) in VLOG_DIRECTIVES {
        let function_profile = make_function_profile(description);
        let label_details = CompletionItemLabelDetails {
            description: Some("directive".to_string()),
            ..Default::default()
        };
        completion_items.push(CompletionItem {
            label: label.to_string(),
            detail: Some("directive".to_string()),
            label_details: Some(label_details),
            documentation: Some(Documentation::MarkupContent(function_profile)),
            kind: Some(CompletionItemKind::FUNCTION),
            insert_text: Some(snippet_code.to_string()),
            insert_text_format: Some(InsertTextFormat::SNIPPET),
            insert_text_mode: Some(InsertTextMode::ADJUST_INDENTATION),
            ..CompletionItem::default()
        });
    }

    completion_items
}