mod support;

use std::{collections::HashMap, time::Instant};

use lsp_types::{
    CodeActionContext, DidOpenTextDocumentParams, DocumentFormattingParams, FormattingOptions,
    Position, Range, TextDocumentItem, TextDocumentPositionParams,
};
use ra_lsp_server::req::{
    CodeActionParams, CodeActionRequest, Completion, CompletionParams, DidOpenTextDocument,
    Formatting, OnEnter, Runnables, RunnablesParams,
};
use serde_json::json;
use tempfile::TempDir;

use crate::support::{project, Project};

const LOG: &'static str = "";
const PROFILE: &'static str = "";
// const PROFILE: &'static str = "*@3>100";

#[test]
fn completes_items_from_standard_library() {
    let project_start = Instant::now();
    let server = Project::with_fixture(
        r#"
//- Cargo.toml
[package]
name = "foo"
version = "0.0.0"

//- src/lib.rs
use std::collections::Spam;
"#,
    )
    .with_sysroot(true)
    .server();
    server.wait_until_workspace_is_loaded();
    eprintln!("loading took    {:?}", project_start.elapsed());
    let completion_start = Instant::now();
    let res = server.send_request::<Completion>(CompletionParams {
        text_document_position: TextDocumentPositionParams::new(
            server.doc_id("src/lib.rs"),
            Position::new(0, 23),
        ),
        context: None,
    });
    assert!(format!("{}", res).contains("HashMap"));
    eprintln!("completion took {:?}", completion_start.elapsed());
}

#[test]
fn test_runnables_no_project() {
    let server = project(
        r"
//- lib.rs
#[test]
fn foo() {
}
",
    );
    server.wait_until_workspace_is_loaded();
    server.request::<Runnables>(
        RunnablesParams { text_document: server.doc_id("lib.rs"), position: None },
        json!([
          {
            "args": [ "test", "--", "foo", "--nocapture" ],
            "bin": "cargo",
            "env": { "RUST_BACKTRACE": "short" },
            "cwd": null,
            "label": "test foo",
            "range": {
              "end": { "character": 1, "line": 2 },
              "start": { "character": 0, "line": 0 }
            }
          },
          {
            "args": [
              "check",
              "--all"
            ],
            "bin": "cargo",
            "env": {},
            "cwd": null,
            "label": "cargo check --all",
            "range": {
              "end": {
                "character": 0,
                "line": 0
              },
              "start": {
                "character": 0,
                "line": 0
              }
            }
          }
        ]),
    );
}

#[test]
fn test_runnables_project() {
    let code = r#"
//- foo/Cargo.toml
[package]
name = "foo"
version = "0.0.0"

//- foo/src/lib.rs
pub fn foo() {}

//- foo/tests/spam.rs
#[test]
fn test_eggs() {}

//- bar/Cargo.toml
[package]
name = "bar"
version = "0.0.0"

//- bar/src/main.rs
fn main() {}
"#;

    let server = Project::with_fixture(code).root("foo").root("bar").server();

    server.wait_until_workspace_is_loaded();
    server.request::<Runnables>(
        RunnablesParams {
            text_document: server.doc_id("foo/tests/spam.rs"),
            position: None,
        },
        json!([
          {
            "args": [ "test", "--package", "foo", "--test", "spam", "--", "test_eggs", "--nocapture" ],
            "bin": "cargo",
            "env": { "RUST_BACKTRACE": "short" },
            "label": "test test_eggs",
            "range": {
              "end": { "character": 17, "line": 1 },
              "start": { "character": 0, "line": 0 }
            },
            "cwd": server.path().join("foo")
          },
          {
            "args": [
              "check",
              "--package",
              "foo",
              "--test",
              "spam"
            ],
            "bin": "cargo",
            "env": {},
            "cwd": server.path().join("foo"),
            "label": "cargo check -p foo",
            "range": {
              "end": {
                "character": 0,
                "line": 0
              },
              "start": {
                "character": 0,
                "line": 0
              }
            }
          }
        ])
    );
}

#[test]
fn test_format_document() {
    let server = project(
        r#"
[package]
name = "foo"
version = "0.0.0"

//- src/lib.rs
mod bar;

fn main() {
}

pub use std::collections::HashMap;
"#,
    );
    server.wait_until_workspace_is_loaded();

    server.request::<Formatting>(
        DocumentFormattingParams {
            text_document: server.doc_id("src/lib.rs"),
            options: FormattingOptions {
                tab_size: 4,
                insert_spaces: false,
                properties: HashMap::new(),
            },
        },
        json!([
            {
                "newText": r#"mod bar;

fn main() {}

pub use std::collections::HashMap;
"#,
                "range": {
                    "end": {
                        "character": 0,
                        "line": 7
                    },
                    "start": {
                        "character": 0,
                        "line": 0
                    }
                }
            }
        ]),
    );
}

#[test]
fn test_missing_module_code_action() {
    let server = project(
        r#"
//- Cargo.toml
[package]
name = "foo"
version = "0.0.0"

//- src/lib.rs
mod bar;

fn main() {}
"#,
    );
    server.wait_until_workspace_is_loaded();
    let empty_context = || CodeActionContext { diagnostics: Vec::new(), only: None };
    server.request::<CodeActionRequest>(
        CodeActionParams {
            text_document: server.doc_id("src/lib.rs"),
            range: Range::new(Position::new(0, 4), Position::new(0, 7)),
            context: empty_context(),
        },
        json!([
          {
            "command": {
              "arguments": [
                {
                  "cursorPosition": null,
                  "label": "create module",
                  "workspaceEdit": {
                    "documentChanges": [
                      {
                        "kind": "create",
                        "uri": "file:///[..]/src/bar.rs"
                      }
                    ]
                  }
                }
              ],
              "command": "rust-analyzer.applySourceChange",
              "title": "create module"
            },
            "title": "create module"
          }
        ]),
    );

    server.request::<CodeActionRequest>(
        CodeActionParams {
            text_document: server.doc_id("src/lib.rs"),
            range: Range::new(Position::new(2, 4), Position::new(2, 7)),
            context: empty_context(),
        },
        json!([]),
    );
}

#[test]
fn test_missing_module_code_action_in_json_project() {
    let tmp_dir = TempDir::new().unwrap();

    let path = tmp_dir.path();

    let project = json!({
        "roots": [path],
        "crates": [ { "root_module": path.join("src/lib.rs"), "deps": [], "edition": "2015" } ]
    });

    let code = format!(
        r#"
//- rust-project.json
{PROJECT}

//- src/lib.rs
mod bar;

fn main() {{}}
"#,
        PROJECT = project.to_string(),
    );

    let server = Project::with_fixture(&code).tmp_dir(tmp_dir).server();

    server.wait_until_workspace_is_loaded();
    let empty_context = || CodeActionContext { diagnostics: Vec::new(), only: None };
    server.request::<CodeActionRequest>(
        CodeActionParams {
            text_document: server.doc_id("src/lib.rs"),
            range: Range::new(Position::new(0, 4), Position::new(0, 7)),
            context: empty_context(),
        },
        json!([
          {
            "command": {
              "arguments": [
                {
                  "cursorPosition": null,
                  "label": "create module",
                  "workspaceEdit": {
                    "documentChanges": [
                      {
                        "kind": "create",
                        "uri": "file:///[..]/src/bar.rs"
                      }
                    ]
                  }
                }
              ],
              "command": "rust-analyzer.applySourceChange",
              "title": "create module"
            },
            "title": "create module"
          }
        ]),
    );

    server.request::<CodeActionRequest>(
        CodeActionParams {
            text_document: server.doc_id("src/lib.rs"),
            range: Range::new(Position::new(2, 4), Position::new(2, 7)),
            context: empty_context(),
        },
        json!([]),
    );
}

#[test]
fn diagnostics_dont_block_typing() {
    let librs: String = (0..10).map(|i| format!("mod m{};", i)).collect();
    let libs: String = (0..10).map(|i| format!("//- src/m{}.rs\nfn foo() {{}}\n\n", i)).collect();
    let server = Project::with_fixture(&format!(
        r#"
//- Cargo.toml
[package]
name = "foo"
version = "0.0.0"

//- src/lib.rs
{}

{}

fn main() {{}}
"#,
        librs, libs
    ))
    .with_sysroot(true)
    .server();

    server.wait_until_workspace_is_loaded();
    for i in 0..10 {
        server.notification::<DidOpenTextDocument>(DidOpenTextDocumentParams {
            text_document: TextDocumentItem {
                uri: server.doc_id(&format!("src/m{}.rs", i)).uri,
                language_id: "rust".to_string(),
                version: 0,
                text: "/// Docs\nfn foo() {}".to_string(),
            },
        });
    }
    let start = std::time::Instant::now();
    server.request::<OnEnter>(
        TextDocumentPositionParams {
            text_document: server.doc_id("src/m0.rs"),
            position: Position { line: 0, character: 5 },
        },
        json!({
          "cursorPosition": {
            "position": { "character": 4, "line": 1 },
            "textDocument": { "uri": "file:///[..]src/m0.rs" }
          },
          "label": "on enter",
          "workspaceEdit": {
            "documentChanges": [
              {
                "edits": [
                  {
                    "newText": "\n/// ",
                    "range": {
                      "end": { "character": 5, "line": 0 },
                      "start": { "character": 5, "line": 0 }
                    }
                  }
                ],
                "textDocument": { "uri": "file:///[..]src/m0.rs", "version": null }
              }
            ]
          }
        }),
    );
    let elapsed = start.elapsed();
    assert!(elapsed.as_millis() < 2000, "typing enter took {:?}", elapsed);
}

#[test]
fn preserves_dos_line_endings() {
    let server = Project::with_fixture(
        &"
//- Cargo.toml
[package]
name = \"foo\"
version = \"0.0.0\"

//- src/main.rs
/// Some Docs\r\nfn main() {}
",
    )
    .server();

    server.request::<OnEnter>(
        TextDocumentPositionParams {
            text_document: server.doc_id("src/main.rs"),
            position: Position { line: 0, character: 8 },
        },
        json!({
          "cursorPosition": {
            "position": { "line": 1, "character": 4 },
            "textDocument": { "uri": "file:///[..]src/main.rs" }
          },
          "label": "on enter",
          "workspaceEdit": {
            "documentChanges": [
              {
                "edits": [
                  {
                    "newText": "\r\n/// ",
                    "range": {
                      "end": { "line": 0, "character": 8 },
                      "start": { "line": 0, "character": 8 }
                    }
                  }
                ],
                "textDocument": { "uri": "file:///[..]src/main.rs", "version": null }
              }
            ]
          }
        }),
    );
}