#[allow(unused)]
pub mod prelude {
    pub use super::BasicFunctions;
    pub use super::Lsp;
    pub use super::LspContext;
    pub use super::LspFunctions;
    pub use super::Notification;
    pub use super::Request;
    pub use super::Value;
    pub use super::base::*;
    pub use super::initialization::*;
    pub use super::text_document::*;
}

mod base;
pub use base::Notification;
pub use base::Position;
pub use base::Range;
pub use base::Request;
pub use base::Response;
pub use base::ResponseError;

mod initialization;
pub use initialization::InitializedResult;

mod text_document;
pub use text_document::TextDocumentFunctions;

pub use serde_json::Value;

mod reader;
use reader::{BodyResult, Reader};

mod writer;
use writer::Writer;

use std::io;
use std::process::exit;
use std::{io::Read, io::Write};

use crate::prelude::InitializeParams;

/// Runs in a loop handling LSP request/responses from stdin/stdout.
pub fn run_std(functions: &mut dyn LspFunctions) -> ! {
    let mut stdin = io::stdin().lock();
    let mut stdout = io::stdout().lock();
    let mut lsp = Lsp::new(&mut stdin, &mut stdout, functions);
    loop {
        lsp.process_one();
    }
}

pub trait LspFunctions: BasicFunctions + TextDocumentFunctions {}
impl<T> LspFunctions for T where T: BasicFunctions + TextDocumentFunctions {}

/// Trait to implement for providing LSP basic functions.
pub trait BasicFunctions {
    /// Triggered when a request was not handled by any of the other methods.
    /// A method not being handled is when it is not being implemented by the
    /// implementor or if the implementor returns None from its response.
    fn on_unknown_request(&mut self, _ctx: &mut dyn LspContext, request: Request) -> Response {
        request.not_found()
    }

    /// Triggered when a notification was not handled by any of the other
    /// methods.
    /// A method not being handled is when it is not being implemented by the
    /// implementor or if the implementor returns None from its response.
    fn on_unknown_notification(&mut self, _ctx: &mut dyn LspContext, _notification: Notification) {}

    /// The client is requesting initialization of the server
    ///
    /// This is the first request that must come from the client, otherwise
    /// the LSP will respond with [`ResponseError::ServerNotInitialized`].
    ///
    /// Initialization will be set internally if the response from this call
    /// contains no errors.
    ///
    /// This method needs to be implemented otherwise it will always return
    /// a method not found error and the LSP will not initialize.
    ///
    /// # Errors
    ///
    /// This follows a common pattern in this library where if Ok, the correct
    /// result for the specific method is retuned, otherwise a generic response
    /// will be returned as an Err. This Err could be an acrual error, or a
    /// different response that the server needs to return in other special
    /// cases that it is not the intended response for this specific method.
    fn initialize(
        &mut self,
        request: Request,
        params: InitializeParams,
    ) -> Result<InitializedResult, Response>;
}

pub trait LspContext {
    fn send_notification(&mut self, notification: Notification);
}

pub struct Lsp<'a> {
    reader: Reader<'a>,
    writer: Writer<'a>,
    functions: &'a mut dyn LspFunctions,
    initialized: bool,
    context: Context,
}

impl<'a> Lsp<'a> {
    pub fn new(
        input: &'a mut dyn Read,
        output: &'a mut dyn Write,
        functions: &'a mut dyn LspFunctions,
    ) -> Self {
        Self {
            reader: Reader::new(input),
            writer: Writer::new(output),
            functions,
            initialized: false,
            context: Context::default(),
        }
    }

    pub fn process_one(&mut self) {
        let Some(item) = self.reader.next() else {
            return;
        };
        match item {
            BodyResult::Request(request) => {
                self.handle_request(request);
            }
            BodyResult::Notification(notification) => {
                self.handle_notification(notification);
            }
        }
    }

    fn handle_request(&mut self, request: Request) {
        let response = self.route_request(request);
        self.writer.write_response(&response);
    }

    fn route_request(&mut self, request: Request) -> Response {
        if !self.initialized {
            return self.handle_initialization(&request);
        }
        let response = match request.method.as_str() {
            "shutdown" => Response::new(request.id, Value::Null),
            "textDocument/completion" => (
                request.id,
                self.functions.completion(
                    &mut self.context,
                    request.clone(),
                    request.into_params(),
                ),
            )
                .into(),
            "textDocument/diagnostic" => (
                request.id,
                self.functions.diagnostic(
                    &mut self.context,
                    request.clone(),
                    request.into_params(),
                ),
            )
                .into(),
            _ => self
                .functions
                .on_unknown_request(&mut self.context, request),
        };
        self.context.consume(&mut self.writer);
        response
    }

    fn handle_notification(&mut self, notification: Notification) {
        match notification.method.as_str() {
            "exit" => exit(0),
            "textDocument/didOpen" => {
                self.functions.did_open(
                    &mut self.context,
                    notification.clone(),
                    notification.into_params(),
                );
            }
            "textDocument/didChange" => self.functions.did_change(
                &mut self.context,
                notification.clone(),
                notification.into_params(),
            ),
            "textDocument/didClose" => {
                self.functions.did_close(
                    &mut self.context,
                    notification.clone(),
                    notification.into_params(),
                );
            }
            _ => self
                .functions
                .on_unknown_notification(&mut self.context, notification),
        }
        self.context.consume(&mut self.writer);
    }

    fn handle_initialization(&mut self, request: &Request) -> Response {
        if request.method == "initialize" {
            let response: Response = (
                request.id,
                self.functions
                    .initialize(request.clone(), request.into_params()),
            )
                .into();
            if response.error.is_none() {
                self.initialized = true;
            }
            return response;
        }
        Response::error(request.id, ResponseError::ServerNotInitialized)
    }
}

#[derive(Default, Clone, Debug)]
struct Context {
    notifications: Vec<Notification>,
}

impl LspContext for Context {
    fn send_notification(&mut self, notification: Notification) {
        self.notifications.push(notification);
    }
}

impl Context {
    fn consume(&mut self, writer: &mut Writer<'_>) {
        for n in &self.notifications {
            writer.write_notification(n);
        }
        self.notifications.clear();
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::prelude::*;
    use std::fmt::Write;

    const INITIALIZED_RESULT: InitializedResult = InitializedResult {
        capabilities: ServerCapabilities {
            position_encoding: None,
            completion_provider: None,
            text_document_sync: None,
        },
        server_info: None,
    };

    #[test]
    fn test_not_initialized() {
        let requests = [Request::new(1, "unknown", Value::Null)];
        let responses = [Response::error(1, ResponseError::ServerNotInitialized)];
        TestFunctions::not_initializable(&requests, &[], &responses).run_all();
    }

    #[test]
    fn test_initializing() {
        let requests = [Request::new(1, "initialize", Value::Null)];
        let responses = [Response::new(
            1,
            serde_json::to_value(INITIALIZED_RESULT).unwrap(),
        )];
        TestFunctions::initializable(&requests, &[], &responses).run_all();
    }

    #[test]
    fn test_initialized() {
        let requests = [
            Request::new(1, "initialize", Value::Null),
            Request::new(1, "custom", Value::Null),
        ];
        let responses = [
            Response::new(1, serde_json::to_value(INITIALIZED_RESULT).unwrap()),
            Response::error(1, ResponseError::MethodNotFound),
        ];
        TestFunctions::initializable(&requests, &[], &responses).run_all();
    }

    #[test]
    fn test_notification_docs() {
        let requests = [Request::new(1, "initialize", Value::Null)];
        let notifications = [
            Notification::empty("textDocument/didOpen"),
            Notification::empty("textDocument/didChange"),
            Notification::empty("textDocument/didClose"),
        ];
        let responses = [Response::new(
            1,
            serde_json::to_value(INITIALIZED_RESULT).unwrap(),
        )];
        let mut test = TestFunctions::initializable(&requests, &notifications, &responses);
        test.run_all();
        assert!(test.did_open_received);
        assert!(test.did_change_received);
        assert!(test.did_close_received);
    }

    #[allow(clippy::struct_excessive_bools)]
    struct TestFunctions<'a> {
        initializable: bool,
        requests: &'a [Request],
        notifications: &'a [Notification],
        responses: &'a [Response],
        did_open_received: bool,
        did_change_received: bool,
        did_close_received: bool,
    }

    impl<'a> TestFunctions<'a> {
        fn not_initializable(
            requests: &'a [Request],
            notifications: &'a [Notification],
            responses: &'a [Response],
        ) -> Self {
            Self {
                initializable: false,
                requests,
                notifications,
                responses,
                did_open_received: false,
                did_change_received: false,
                did_close_received: false,
            }
        }

        fn initializable(
            requests: &'a [Request],
            notifications: &'a [Notification],
            responses: &'a [Response],
        ) -> Self {
            Self {
                initializable: true,
                requests,
                notifications,
                responses,
                did_open_received: false,
                did_change_received: false,
                did_close_received: false,
            }
        }

        fn requests_into_string(&mut self) -> String {
            let mut msg = String::new();
            for request in self.requests {
                let json = serde_json::to_string(request).expect("Cannot serialize request");
                write!(msg, "Content-Length: {}\r\n\r\n{json}", json.len()).unwrap();
            }
            msg
        }

        fn notifications_into_string(&mut self) -> String {
            let mut msg = String::new();
            for notification in self.notifications {
                let json =
                    serde_json::to_string(notification).expect("Cannot serialize notification");
                write!(msg, "Content-Length: {}\r\n\r\n{json}", json.len()).unwrap();
            }
            msg
        }

        fn run_all(&mut self) {
            const HEADERS_END: &str = "\r\n\r\n";

            let runs_count = self.requests.len() + self.notifications.len();
            let mut input = self.requests_into_string();
            input += &self.notifications_into_string();
            let mut out = Vec::<u8>::new();
            let mut input_bytes = input.as_bytes();
            let mut lsp = Lsp::new(&mut input_bytes, &mut out, self);
            for _ in 0..runs_count {
                lsp.process_one();
            }

            let mut ct = 0;
            let mut remaining = str::from_utf8(&out).unwrap();
            loop {
                let Some(splitat) = remaining.find(HEADERS_END) else {
                    return;
                };
                let (headers_block, mut body_rest) = remaining.split_at(splitat);
                body_rest = &body_rest[HEADERS_END.len()..];
                let headers_block = headers_block.trim();
                if headers_block.is_empty() {
                    break;
                }
                let mut headers = headers_block.split('\n');
                let size: usize;
                loop {
                    let mut header_split = headers.next().unwrap().split(':');
                    let name = header_split.next().unwrap_or("");
                    let value = header_split.next().unwrap_or("");
                    if name.eq_ignore_ascii_case("content-length") {
                        size = value.trim().parse().unwrap();
                        break;
                    }
                }
                let body = &body_rest[..size].trim();
                let result = serde_json::from_str::<Response>(body).expect("Invalid json returned");
                assert_eq!(result, self.responses[ct]);
                remaining = &body_rest[size..];
                ct += 1;
            }
            assert_eq!(ct, self.responses.len());
        }
    }

    impl BasicFunctions for TestFunctions<'_> {
        fn initialize(
            &mut self,
            request: Request,
            _params: InitializeParams,
        ) -> Result<InitializedResult, Response> {
            if self.initializable {
                Ok(InitializedResult {
                    capabilities: ServerCapabilities::default(),
                    server_info: None,
                })
            } else {
                Err(request.not_found())
            }
        }
    }

    impl TextDocumentFunctions for TestFunctions<'_> {
        fn did_open(
            &mut self,
            _ctx: &mut dyn LspContext,
            _notification: Notification,
            _params: DidOpenTextDocumentParams,
        ) {
            self.did_open_received = true;
        }

        fn did_change(
            &mut self,
            _ctx: &mut dyn LspContext,
            _notification: Notification,
            _params: DidChangeTextDocumentParams,
        ) {
            self.did_change_received = true;
        }

        fn did_close(
            &mut self,
            _ctx: &mut dyn LspContext,
            _notification: Notification,
            _params: DidCloseTextDocumentParams,
        ) {
            self.did_close_received = true;
        }
    }
}
