#[macro_use]
extern crate failure;
#[macro_use]
extern crate serde_derive;
extern crate serde;
extern crate serde_json;
extern crate languageserver_types;
extern crate drop_bomb;
#[macro_use]
extern crate crossbeam_channel;
extern crate threadpool;
#[macro_use]
extern crate log;
extern crate url_serde;
extern crate flexi_logger;
extern crate walkdir;
extern crate libeditor;
extern crate libanalysis;
extern crate libsyntax2;
extern crate im;
extern crate relative_path;

mod io;
mod caps;
mod req;
mod dispatch;
mod conv;
mod main_loop;
mod vfs;
mod path_map;
mod server_world;

use threadpool::ThreadPool;
use crossbeam_channel::bounded;
use flexi_logger::{Logger, Duplicate};

use ::{
    io::{Io, RawMsg, RawResponse, RawNotification},
};

pub type Result<T> = ::std::result::Result<T, ::failure::Error>;

fn main() -> Result<()> {
    Logger::with_env_or_str("m=error")
        .duplicate_to_stderr(Duplicate::All)
        .log_to_file()
        .directory("log")
        .start()?;
    info!("lifecycle: server started");
    match ::std::panic::catch_unwind(|| main_inner()) {
        Ok(res) => {
            info!("lifecycle: terminating process with {:?}", res);
            res
        }
        Err(_) => {
            error!("server panicked");
            bail!("server panicked")
        }
    }
}

fn main_inner() -> Result<()> {
    let mut io = Io::from_stdio();
    let res = initialize(&mut io);
    info!("shutting down IO...");
    let io_res = io.stop();
    info!("... IO is down");
    match (res, io_res) {
        (Ok(()), Ok(())) => Ok(()),
        (res, Ok(())) => res,
        (Ok(()), io_res) => io_res,
        (res, Err(io_err)) => {
            error!("shutdown error: {:?}", io_err);
            res
        }
    }
}

fn initialize(io: &mut Io) -> Result<()> {
    match io.recv()? {
        RawMsg::Notification(n) =>
            bail!("expected initialize request, got {:?}", n),
        RawMsg::Response(res) =>
            bail!("expected initialize request, got {:?}", res),

        RawMsg::Request(req) => {
            let req = dispatch::handle_request::<req::Initialize, _>(req, |_params, resp| {
                let res = req::InitializeResult { capabilities: caps::server_capabilities() };
                let resp = resp.into_response(Ok(res))?;
                io.send(RawMsg::Response(resp));
                Ok(())
            })?;
            if let Err(req) = req {
                bail!("expected initialize request, got {:?}", req)
            }
            match io.recv()? {
                RawMsg::Notification(n) => {
                    if n.method != "initialized" {
                        bail!("expected initialized notification");
                    }
                }
                _ => bail!("expected initialized notification"),
            }
        }
    }
    initialized(io)
}

enum Task {
    Respond(RawResponse),
    Notify(RawNotification),
    Die(::failure::Error),
}

fn initialized(io: &mut Io) -> Result<()> {
    {
        let mut pool = ThreadPool::new(4);
        let (task_sender, task_receiver) = bounded::<Task>(16);
        let (fs_events_receiver, watcher) = vfs::watch(vec![
            ::std::env::current_dir()?,
        ]);
        info!("lifecycle: handshake finished, server ready to serve requests");
        let res = main_loop::main_loop(
            io,
            &mut pool,
            task_sender,
            task_receiver.clone(),
            fs_events_receiver,
        );

        info!("waiting for background jobs to finish...");
        task_receiver.for_each(drop);
        pool.join();
        info!("...background jobs have finished");

        info!("waiting for file watcher to finish...");
        watcher.stop()?;
        info!("...file watcher has finished");

        res
    }?;

    match io.recv()? {
        RawMsg::Notification(n) => {
            if n.method == "exit" {
                info!("lifecycle: shutdown complete");
                return Ok(());
            }
            bail!("unexpected notification during shutdown: {:?}", n)
        }
        m => {
            bail!("unexpected message during shutdown: {:?}", m)
        }
    }
}