Documentation
use super::encode::auto_decode;
use crate::Result;
use serde::de;
use serde::{Deserialize, Serialize};
use std::ffi::OsStr;
use std::io;
#[cfg(target_os = "windows")]
use std::os::windows::process::CommandExt as _;
use std::path::PathBuf;
use std::process::Command;
use std::process::{ExitStatus, Stdio};
use std::str;

/// 弹窗
pub const CREATE_NEW_CONSOLE: u32 = 0x00000010;
/// 不弹窗
pub const CREATE_NO_WINDOW: u32 = 0x08000000;
/// 新的进程组
pub const CREATE_NEW_PROCESS_GROUP: u32 = 0x00000200;
/// 返回结果
#[derive(Debug, Clone)]
pub struct CmdOutput {
  /// 输出
  pub stdout: String,
  /// 状态
  pub status: ExitStatus,
  /// 错误输出
  pub stderr: Vec<u8>,
}

/// # 命令调用 Example
/// ```rust
/// use e_utils::system::cmd;
/// use std::path::Path;
/// fn main() {
///     // 执行 slmgr.vbs 命令
///     let output = cmd(
///         "cscript",
///         ["/nologo", "slmgr.vbs", "-xpr"],
///         Some(Path::new("C:\\windows\\system32").to_path_buf()),
///         false,
///         true,
///     )
///     .unwrap();
///     println!("out -> {:?}", output);
///     // 在输出中查找激活状态
///     if output.contains("Windows is activated") {
///         println!("Windows 已激活");
///     } else {
///         println!("Windows 未激活");
///     }
/// }
/// ```
pub fn cmd<I, S>(
  exe: S,
  args: I,
  cwd: Option<PathBuf>,
  has_window: bool,
  _autodecode: bool,
) -> io::Result<CmdOutput>
where
  I: IntoIterator<Item = S>,
  S: AsRef<OsStr>,
{
  let exe_full = if let Some(x) = &cwd {
    x.join(exe.as_ref()).to_string_lossy().to_string()
  } else {
    exe.as_ref().to_string_lossy().to_string()
  };
  let mut binding = Command::new(&*exe_full);
  let cmd = binding
    .args(args)
    .stdin(Stdio::null())
    .stdout(Stdio::piped())
    .stderr(Stdio::piped());
  if let Some(x) = cwd {
    cmd.current_dir(x);
  }
  // 设置 CREATE_NO_WINDOW 标志
  #[cfg(target_os = "windows")]
  {
    let flag = if has_window {
      CREATE_NEW_CONSOLE
    } else {
      CREATE_NO_WINDOW
    };
    cmd.creation_flags(flag | CREATE_NEW_PROCESS_GROUP);
  }
  let output = cmd.output()?;
  let stdout =
    { auto_decode(&output.stdout).unwrap_or(String::from_utf8_lossy(&output.stdout).to_string()) };

  Ok(CmdOutput {
    stdout,
    status: output.status,
    stderr: output.stderr,
  })
}

/// 命令调用不等待
pub fn cmd_spawn<I, S>(
  exe: S,
  args: I,
  cwd: Option<PathBuf>,
  has_window: bool,
) -> io::Result<std::process::Child>
where
  I: IntoIterator<Item = S>,
  S: AsRef<OsStr>,
{
  let exe_full = if let Some(x) = &cwd {
    x.join(exe.as_ref()).to_string_lossy().to_string()
  } else {
    exe.as_ref().to_string_lossy().to_string()
  };
  let mut binding = Command::new(&*exe_full);
  let cmd = binding
    .args(args)
    .stdin(Stdio::null())
    .stdout(Stdio::piped())
    .stderr(Stdio::piped());
  if let Some(x) = cwd {
    cmd.current_dir(x);
  }
  // 设置 CREATE_NO_WINDOW 标志
  #[cfg(target_os = "windows")]
  {
    let flag = if has_window {
      CREATE_NEW_CONSOLE
    } else {
      CREATE_NO_WINDOW
    };
    cmd.creation_flags(flag);
  }
  cmd.spawn()
}

/// CMD结构
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CmdResult<T> {
  /// 内容
  pub content: String,
  /// 状态
  pub status: bool,
  /// 其他参数
  pub opts: T,
}

impl<T> CmdResult<T> {
  /// 设置Opts
  pub fn set_opts(&mut self, opts: T) -> &mut Self {
    self.opts = opts;
    self
  }
  /// 合并数据除了opts
  pub fn merge(&mut self, target: Self) -> &mut Self {
    self.content = target.content;
    self.status = target.status;
    self
  }
  /// 设置状态
  pub fn set_status(&mut self, state: bool) -> &mut Self {
    self.status = state;
    self
  }
  /// 设置内容
  pub fn set_content(&mut self, content: String) -> &mut Self {
    self.content = content;
    self
  }
  /// 获取OPts
  pub fn opts(&self) -> &T {
    &self.opts
  }
}
impl<'a, T> CmdResult<T>
where
  T: de::Deserialize<'a>,
{
  /// #解析如
  /// ```rust
  /// use e_utils::system::cmd::CmdResult;
  /// let s = r#"R<{"content":"Windows(R), Education edition:\r\n    批量激活将于 2024/11/18 8:31:18 过期\r\n\r\n","status":true,"opts":{"api":"Os","task":"check","command":["1"]}}>R"#;
  /// println!("{}", CmdResult::from_str(s).unwrap());
  /// ```
  pub fn from_str(value: &'a str) -> Result<Self> {
    let s = value.trim().trim_start_matches("R<").trim_end_matches(">R");
    let res: CmdResult<T> = serde_json::from_str(s)?;
    Ok(res)
  }
}
impl<T> CmdResult<T>
where
  T: Serialize,
{
  /// #
  /// ```rust
  /// ```
  pub fn to_str(&self) -> Result<String> {
    let s = format!("R<{}>R", serde_json::to_string(&self)?);
    Ok(s)
  }
  /// #
  /// ```rust
  /// ```
  pub fn to_string_pretty(&self) -> Result<String> {
    let s = format!("R<{}>R", serde_json::to_string_pretty(&self)?);
    Ok(s)
  }
}

/// 简易的目标打开
#[cfg(feature = "fs")]
pub fn shell_open(target: &str) -> Result<()> {
  let binding = crate::fs::convert_path(target);
  let pathname: &str = binding.as_str();
  #[cfg(target_os = "macos")]
  crate::system::cmd_spawn("open", ["-R", pathname], None, false)?;
  #[cfg(target_os = "windows")]
  crate::system::cmd_spawn("explorer.exe", [pathname], None, false)?;
  // https://askubuntu.com/a/31071
  #[cfg(target_os = "linux")]
  crate::system::cmd_spawn("xdg-open", [pathname], None, false)?;
  Ok(())
}