Documentation
use crate::core::Core;
use crate::registry;
use crate::tools::get_eva_dir;
use crate::tools::ErrLogger;
use crate::{EResult, Error};
use crate::{BUILD, PRODUCT_CODE, PRODUCT_NAME, VERSION};
use elbus::rpc::RpcClient;
use eva_common::Value;
use log::{debug, info};
use serde::Serialize;
use std::collections::HashMap;
use std::fmt;
use std::str::FromStr;
use std::sync::Arc;

#[derive(Eq, PartialEq, Copy, Clone, Debug)]
pub enum Mode {
    Normal,
    Registry,
    Info,
}

impl FromStr for Mode {
    type Err = Error;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "normal" => Ok(Mode::Normal),
            "registry" => Ok(Mode::Registry),
            "info" => Ok(Mode::Info),
            _ => Err(Error::invalid_data(format!("Invalid mode: {}", s))),
        }
    }
}

impl fmt::Display for Mode {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "{}",
            match self {
                Mode::Normal => "normal",
                Mode::Registry => "registry",
                Mode::Info => "info",
            }
        )
    }
}

#[derive(Serialize)]
struct Info<'a> {
    name: &'a str,
    code: &'a str,
    build: u64,
    version: &'a str,
}

/// # Panics
///
/// Will panic in runtime on init errors
pub fn run(mode: Mode) -> EResult<()> {
    if mode == Mode::Info {
        let info = Info {
            name: PRODUCT_NAME,
            code: PRODUCT_CODE,
            build: BUILD,
            version: VERSION,
        };
        println!("{}", serde_json::to_string(&info)?);
        return Ok(());
    }
    let dir_eva = get_eva_dir();
    let reg_db = yedb::server::create_db();
    let mut db = reg_db.try_write()?;
    let db_path = format!("{dir_eva}/runtime/registry");
    db.set_db_path(&db_path)?;
    db.open()
        .map_err(|e| Error::registry(format!("unable to open the registry {}: {}", db_path, e)))?;
    let mut core = Core::new_from_db(&mut db, &dir_eva)?;
    let log_config: Vec<HashMap<String, Value>> =
        serde_json::from_value(db.key_get(&registry::format_config_key("logs"))?)?;
    crate::logs::init(core.system_name(), "eva-node", log_config, &dir_eva);
    info!("starting EVA ICS node {}", core.system_name());
    info!("mode: {}", mode);
    registry::load(&mut db)?;
    core.set_boot_id(&mut db)?;
    core.log_summary();
    let mut broker = crate::bus::EvaBroker::new_from_db(&mut db, &dir_eva)?;
    let rt = tokio::runtime::Builder::new_multi_thread()
        .worker_threads(core.workers())
        .enable_all()
        .build()?;
    if mode == Mode::Normal {
        core.load_inventory(&mut db)?;
        core.service_manager().load(&mut db)?;
    }
    drop(db);
    debug!("starting runtime");
    rt.block_on(async move {
        crate::logs::start().await.log_err().unwrap();
        broker.init(&mut core).await.log_err().unwrap();
        let core_client = if mode == Mode::Normal {
            let mut core_client = broker.register_client("eva.core").await.log_err().unwrap();
            crate::core::init_core_client(&mut core_client)
                .await
                .log_err()
                .unwrap();
            Some(core_client)
        } else {
            None
        };
        let reg_client = broker
            .register_client("eva.registry")
            .await
            .log_err()
            .unwrap();
        let laucher_client = broker
            .register_client(crate::services::DEFAULT_LAUNCHER)
            .await
            .log_err()
            .unwrap();
        crate::launcher::init(laucher_client).await;
        let _reg_rpc = RpcClient::new(reg_client, yedb::server::ElbusApi::new(reg_db.clone()));
        let core = Arc::new(core);
        if let Some(client) = core_client {
            let core_rpc = RpcClient::new(client, crate::eapi::ElbusApi::new(core.clone()));
            core.set_rpc(Arc::new(core_rpc)).await;
        }
        core.write_pid_file().await.log_err().unwrap();
        core.register_signals().await;
        core.start().await.log_err().unwrap();
        core.mark_loaded().await;
        core.service_manager()
            .start(core.system_name(), core.timeout())
            .await;
        let c = core.clone();
        tokio::spawn(async move {
            c.announce_local().await;
        });
        core.block().await;
    });
    Ok(())
}