// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0

use std::collections::HashMap;
use std::fmt;
use std::path::PathBuf;

use anyhow::Result;
use serde::{Deserialize, Serialize};
use thiserror::Error;

use crate::impl_serializable_error;

/// Error related to the `Emulator` trait.
#[derive(Error, Debug, Serialize, Deserialize)]
pub enum EmuError {
    #[error("Invalid argument name: {0}")]
    InvalidArgumetName(String),
    #[error("Argument name: {0} has invalid value: {1}")]
    InvalidArgumentValue(String, String),
    #[error("Start failed with cause: {0}")]
    StartFailureCause(String),
    #[error("Stop failed with cause: {0}")]
    StopFailureCause(String),
    #[error("Can't restore resource to initial state: {0}")]
    ResetError(String),
    #[error("Runtime error: {0}")]
    RuntimeError(String),
}
impl_serializable_error!(EmuError);

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum EmuValue {
    Empty,
    String(String),
    FilePath(PathBuf),
    StringList(Vec<String>),
    FilePathList(Vec<PathBuf>),
}

impl From<String> for EmuValue {
    fn from(s: String) -> Self {
        EmuValue::String(s)
    }
}

impl From<Vec<String>> for EmuValue {
    fn from(str_array: Vec<String>) -> Self {
        EmuValue::StringList(str_array)
    }
}

impl From<PathBuf> for EmuValue {
    fn from(p: PathBuf) -> Self {
        EmuValue::FilePath(p)
    }
}

impl From<Vec<PathBuf>> for EmuValue {
    fn from(path_array: Vec<PathBuf>) -> Self {
        EmuValue::FilePathList(path_array)
    }
}

impl fmt::Display for EmuValue {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            EmuValue::Empty => write!(f, " "),
            EmuValue::String(value) => write!(f, "{}", value),
            EmuValue::FilePath(value) => write!(f, "{}", value.display()),
            EmuValue::StringList(value_list) => write!(f, "{}", value_list.join(",")),
            EmuValue::FilePathList(value_list) => {
                for item in value_list.iter() {
                    write!(f, "{} ", item.display())?;
                }
                Ok(())
            }
        }
    }
}

#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)]
pub enum EmuState {
    Off,
    On,
    Busy,
    Error,
}

impl fmt::Display for EmuState {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            EmuState::Off => write!(f, "Off"),
            EmuState::On => write!(f, "On"),
            EmuState::Busy => write!(f, "Busy"),
            EmuState::Error => write!(f, "Error"),
        }
    }
}

/// A trait which represents a `Emulator` instance
pub trait Emulator {
    /// Simple function with return `EmuState` representing current state of Emulator instance.
    fn get_state(&self) -> Result<EmuState>;

    /// Start Emulator with provided arguments.
    /// Parameter `factory_reset` is a flag that describe if internal state
    /// and "resources" should be set to its "initial state" before applying
    /// the changes from the parameter `args`.
    /// If `factory_reset` is set to true - all internal state and "resources"
    /// will be set to its "initial state". The "initial state" for most devices
    /// means that the content is set to zero.
    /// If `factory_reset` is set to false - all internal state will be preserved
    /// form last run.
    /// Parameter `args` describe set of named flags, value and resources passed
    /// to Emulator in order to update the content of its internal state.
    /// In the context of the function below, "resources" denote files
    /// representing the state of devices, for example: EEPROM, FUSES, FLASH, SRAM...
    /// (most often binary files). Resources files sent via parameter `args`
    /// cannot be modified by the Emulator directly, the Emulator backend should have
    /// copy-on-write implementations to enable resource modification during DUT Emulation.
    /// It should be noted that the method of resources interpretation
    /// and their format depends on the backend implementing the Emulator trait.
    /// More detail about supported `args` value and its content can be found
    /// in the documentation of individual backend.
    fn start(&self, factory_reset: bool, args: &HashMap<String, EmuValue>) -> Result<()>;

    /// Stop emulator instance.
    fn stop(&self) -> Result<()>;
}
