use std::{fmt, io};

#[derive(Debug)]
pub enum ArgsError {
  #[cfg(feature = "clap")]
  Clap(clap::Error),
  Msg(String)
}

/// Indicate failure for server applicatino callbacks.
#[derive(Debug, Default)]
pub struct AppErrors<ApEr> {
  pub init: Option<ApEr>,
  pub run: Option<ApEr>,
  pub shutdown: Option<ApEr>
}

impl<ApEr> AppErrors<ApEr> {
  pub const fn init_failed(&self) -> bool {
    self.init.is_some()
  }

  pub const fn run_failed(&self) -> bool {
    self.run.is_some()
  }

  pub const fn shutdown_failed(&self) -> bool {
    self.shutdown.is_some()
  }
}


/// Errors that can be returned from functions that call application callbacks.
#[derive(Debug)]
#[allow(clippy::module_name_repetitions)]
pub enum CbErr<ApEr> {
  /// An qsu library error was generated.
  Lib(Error),

  /// Application-defined error.
  ///
  /// Applications can use this variant to pass application-specific errors
  /// through the runtime back to itself.
  App(ApEr),

  /// Returned by [`RunCtx::run()`](crate::rt::RunCtx) to indicate which
  /// server application callbacks that failed.
  #[cfg(feature = "rt")]
  #[cfg_attr(docsrs, doc(cfg(feature = "rt")))]
  SrvApp(AppErrors<ApEr>)
}

impl<ApEr: fmt::Debug> std::error::Error for CbErr<ApEr> {}

impl<ApEr> fmt::Display for CbErr<ApEr> {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    match self {
      Self::Lib(e) => e.fmt(f),
      Self::App(_ae) => {
        // ToDo: Add ApErr: Error bound and forward the call to it
        write!(f, "Application-defined error")
      }
      #[cfg(feature = "rt")]
      Self::SrvApp(e) => {
        let mut v = vec![];
        if e.init.is_some() {
          v.push("init");
        }
        if e.run.is_some() {
          v.push("run");
        }
        if e.shutdown.is_some() {
          v.push("shutdown");
        }
        write!(f, "Server application failed [{}]", v.join(","))
      }
    }
  }
}

impl<ApEr> CbErr<ApEr> {
  /// Extract application-specific error.
  ///
  /// # Panics
  /// The error must be [`CbErr::App`] of this method will panic.
  pub fn unwrap_apperr(self) -> ApEr {
    let Self::App(ae) = self else {
      panic!("Not CbErr::App()");
    };
    ae
  }
}

impl<ApEr> From<Error> for CbErr<ApEr> {
  fn from(err: Error) -> Self {
    Self::Lib(err)
  }
}

#[cfg(feature = "rocket")]
impl<ApEr> From<rocket::Error> for CbErr<ApEr> {
  fn from(err: rocket::Error) -> Self {
    Self::Lib(Error::Rocket(err.to_string()))
  }
}

impl<ApEr> From<std::io::Error> for CbErr<ApEr> {
  fn from(err: std::io::Error) -> Self {
    Self::Lib(Error::IO(err.to_string()))
  }
}


/// Errors that qsu will return to application.
#[derive(Debug)]
pub enum Error {
  ArgP(ArgsError),
  BadFormat(String),
  Internal(String),
  IO(String),

  /// An error related to logging occurred.
  ///
  /// This includes both initialization and actual logging.
  ///
  /// On Windows errors such as failure to register an event source will be
  /// treated as this error variant as well.
  LumberJack(String),

  /// Missing expected data.
  Missing(String),

  /// Rocket-specific errors.
  #[cfg(feature = "rocket")]
  #[cfg_attr(docsrs, doc(cfg(feature = "rocket")))]
  Rocket(String),
  SubSystem(String),

  Unsupported
}


#[allow(clippy::needless_pass_by_value)]
impl Error {
  pub fn bad_format(s: impl ToString) -> Self {
    Self::BadFormat(s.to_string())
  }

  pub fn internal(s: impl ToString) -> Self {
    Self::Internal(s.to_string())
  }

  pub fn io(s: impl ToString) -> Self {
    Self::IO(s.to_string())
  }

  pub fn lumberjack(s: impl ToString) -> Self {
    Self::LumberJack(s.to_string())
  }

  pub fn missing(s: impl ToString) -> Self {
    Self::Missing(s.to_string())
  }
}


impl std::error::Error for Error {}

impl fmt::Display for Error {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    match self {
      Self::ArgP(s) => {
        // ToDo: Handle the ArgsError::Clap and ArgsError::Msg differently
        write!(f, "Argument parser; {s:?}")
      }
      Self::BadFormat(s) => write!(f, "Bad format error; {s}"),
      Self::Internal(s) => write!(f, "Internal error; {s}"),
      Self::IO(s) => write!(f, "I/O error; {s}"),
      Self::LumberJack(s) => write!(f, "LumberJack error; {s}"),
      Self::Missing(s) => write!(f, "Missing data; {s}"),
      #[cfg(feature = "rocket")]
      Self::Rocket(s) => write!(f, "Rocket error; {s}"),
      Self::SubSystem(s) => write!(f, "Service subsystem error; {s}"),
      Self::Unsupported => {
        write!(f, "Operation is unsupported [on this platform]")
      }
    }
  }
}

/*
#[cfg(feature = "clap")]
impl From<clap::error::Error> for Error {
  fn from(err: clap::error::Error) -> Self {
    Error::ArgP(err.to_string())
  }
}
*/


#[cfg(windows)]
impl From<fltevtlog::InitError> for Error {
  /// Map eventlog initialization errors to `Error::LumberJack`.
  fn from(err: fltevtlog::InitError) -> Self {
    Self::LumberJack(err.to_string())
  }
}

#[cfg(windows)]
impl From<fltevtlog::Error> for Error {
  /// Map eventlog errors to `Error::LumberJack`.
  fn from(err: fltevtlog::Error) -> Self {
    Self::LumberJack(err.to_string())
  }
}

impl From<io::Error> for Error {
  fn from(err: io::Error) -> Self {
    Self::IO(err.to_string())
  }
}

#[cfg(windows)]
impl From<windows_result::Error> for Error {
  fn from(err: windows_result::Error) -> Self {
    Self::SubSystem(err.to_string())
  }
}

#[cfg(feature = "rocket")]
impl From<rocket::Error> for Error {
  fn from(err: rocket::Error) -> Self {
    Self::Rocket(err.to_string())
  }
}

#[cfg(feature = "installer")]
impl From<sidoc::Error> for Error {
  fn from(err: sidoc::Error) -> Self {
    Self::Internal(err.to_string())
  }
}

#[cfg(windows)]
impl From<windows_service::Error> for Error {
  fn from(err: windows_service::Error) -> Self {
    Self::SubSystem(err.to_string())
  }
}

#[cfg(windows)]
impl<ApEr> From<windows_service::Error> for CbErr<ApEr> {
  fn from(err: windows_service::Error) -> Self {
    Self::Lib(Error::SubSystem(err.to_string()))
  }
}


/*
impl<ApEr> From<ApEr> for Error<ApEr> {
  /// Wrap an [`AppErr`] in an [`Error`].
  fn from(err: ApEr) -> Self {
    Error::App(err)
  }
}
*/

// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :
