use std::{
    error::Error,
    io::{BufRead, BufReader, Write},
    process::{Child, ChildStdin, Command, Stdio},
    thread::JoinHandle,
};

use winit::event_loop::EventLoopProxy;

use crate::{
    AppMessage, Rpc,
    json_ui::{self, Response},
};

pub struct Kakoune {
    child: Child,
    stdin: ChildStdin,
    join_handle: JoinHandle<()>,
}
impl Kakoune {
    pub fn new(event_loop_proxy: EventLoopProxy<AppMessage>) -> Self {
        let mut args: Vec<String> = std::env::args().collect();
        args.remove(0);
        args.insert(0, String::from("json"));
        args.insert(0, String::from("-ui"));
        let mut child = Command::new("kak")
            .args(&args[..])
            .stdout(Stdio::piped())
            .stdin(Stdio::piped())
            .spawn()
            .expect("Failed to spawn Kakoune");
        let mut stdout = BufReader::new(child.stdout.take().expect("Failed to get Kakoune stdout"));
        let stdin = child.stdin.take().expect("Failed to get stdin");

        let join_handle = std::thread::spawn(move || {
            let mut buf = String::new();
            loop {
                buf.clear();
                let Ok(read) = stdout.read_line(&mut buf) else {
                    break;
                };
                if read == 0 {
                    break;
                }
                let content: Rpc<json_ui::Request> =
                    serde_json::from_str(&buf).expect("Kakoune json_ui is broken");
                if event_loop_proxy
                    .send_event(AppMessage::Kakoune(content.inner))
                    .is_err()
                {
                    return;
                }
            }
            _ = event_loop_proxy.send_event(AppMessage::Exit);
        });
        Self {
            child,
            stdin,
            join_handle,
        }
    }
    pub fn send_response(&mut self, resp: Response) -> Result<(), Box<dyn Error>> {
        let resp = serde_json::to_string(&Rpc::from(resp)).unwrap();
        self.stdin.write_all(resp.as_bytes())?;
        self.stdin.write_all(b"\n")?;
        Ok(())
    }
    pub fn join(mut self) {
        self.child.kill().unwrap();
        self.child.wait().unwrap();
        self.join_handle.join().unwrap();
    }
}