use std::fmt::{Debug, Display};

use crate::{errors::PsdkFatherResult, quick, types::psdk_const};

use super::CommandClause;

#[derive(Clone, Debug)]
pub struct PsdkTextCommand {
  header: Option<String>,
  arguments: Vec<String>,
  symbol_after_header: String,
  symbol_between_arguments: String,
  charset: Option<String>,
}

impl PsdkTextCommand {
  pub fn new(
    symbol_after_header: impl AsRef<str>,
    symbol_between_arguments: impl AsRef<str>,
  ) -> Self {
    Self {
      header: None,
      arguments: vec![],
      symbol_after_header: symbol_after_header.as_ref().to_string(),
      symbol_between_arguments: symbol_between_arguments.as_ref().to_string(),
      charset: None,
    }
  }

  pub fn format_cpcl() -> Self {
    Self::new(" ", " ")
  }

  pub fn format_tspl() -> Self {
    Self::new(" ", ",")
  }

  pub fn with_header_format_tspl(header: impl AsRef<str>) -> Self {
    let mut command = Self::format_tspl();
    command.header(header);
    command
  }

  pub fn with_header_format_cpcl(header: impl AsRef<str>) -> Self {
    let mut command = Self::format_cpcl();
    command.header(header);
    command
  }

  pub fn with_header_format_tspl_and_charset(
    header: impl AsRef<str>,
    charset: impl AsRef<str>,
  ) -> Self {
    let mut command = Self::with_header_format_tspl(header);
    command.charset(charset);
    command
  }

  pub fn with_header_format_cpcl_and_charset(
    header: impl AsRef<str>,
    charset: impl AsRef<str>,
  ) -> Self {
    let mut command = Self::with_header_format_cpcl(header);
    command.charset(charset);
    command
  }
}

impl PsdkTextCommand {
  pub fn charset(&mut self, charset: impl AsRef<str>) -> &mut Self {
    self.charset = Some(charset.as_ref().to_string());
    self
  }

  pub fn header(&mut self, header: impl AsRef<str>) -> &mut Self {
    self.header = Some(header.as_ref().to_string());
    self
  }

  pub fn append(&mut self, argument: impl Display) -> &mut Self {
    let arg = argument.to_string();
    self.arguments.push(arg);
    self
  }

  pub fn append_with_quote(&mut self, argument: impl Display) -> &mut Self {
    let arg = format!("\"{}\"", argument);
    self.append(arg)
  }

  pub fn arguments(&self) -> &Vec<String> {
    &self.arguments
  }

  pub fn output(&self) -> String {
    let mut output = Default::default();

    if let Some(header) = &self.header {
      if !header.is_empty() {
        output = format!("{}{}", header, self.symbol_after_header);
      }
    }
    let arguments = self
      .arguments
      .iter()
      .filter(|argument| !argument.is_empty())
      .collect::<Vec<_>>();
    let arg_len = arguments.len();
    let mut seq = 0;
    for argument in arguments {
      seq += 1;
      output = format!("{}{}", output, argument);
      if seq < arg_len {
        output = format!("{}{}", output, self.symbol_between_arguments);
      }
    }
    output
  }

  pub fn clause(&self) -> CommandClause {
    let output = self.output();
    if output.is_empty() {
      return CommandClause::none();
    }
    if let Some(charset) = &self.charset {
      return CommandClause::with_text_and_charset(output, charset);
    }
    CommandClause::with_text(output)
  }
}

#[derive(Clone, Debug)]
pub struct PsdkBinaryCommand {
  header: Option<Vec<u8>>,
  arguments: Vec<u8>,
}

impl Default for PsdkBinaryCommand {
  fn default() -> Self {
    Self::new()
  }
}

impl PsdkBinaryCommand {
  pub fn new() -> Self {
    Self {
      header: None,
      arguments: vec![],
    }
  }

  pub fn with_header(header: &[u8]) -> Self {
    let mut pbc = Self::new();
    pbc.header = Some(header.to_vec());
    pbc
  }
}

impl PsdkBinaryCommand {
  pub fn append(&mut self, binary: &[u8]) -> &mut Self {
    self.arguments.extend_from_slice(binary);
    self
  }

  pub fn append_u8(&mut self, number: u8) -> &mut Self {
    self.append(&[number])
  }

  pub fn append_u16(&mut self, number: u16) -> &mut Self {
    let bytes = number.to_be_bytes();
    let start = bytes
      .iter()
      .position(|&b| b != 0)
      .unwrap_or(size_of::<u16>() - 1);
    self.append(&bytes[start..])
  }

  pub fn append_u32(&mut self, number: u32) -> &mut Self {
    let bytes = number.to_be_bytes();
    let start = bytes
      .iter()
      .position(|&b| b != 0)
      .unwrap_or(size_of::<u32>() - 1);
    self.append(&bytes[start..])
  }

  pub fn append_u64(&mut self, number: u64) -> &mut Self {
    let bytes = number.to_be_bytes();
    let start = bytes
      .iter()
      .position(|&b| b != 0)
      .unwrap_or(size_of::<u64>() - 1);
    self.append(&bytes[start..])
  }

  pub fn append_usize(&mut self, number: usize) -> &mut Self {
    let bytes = number.to_be_bytes();
    let start = bytes
      .iter()
      .position(|&b| b != 0)
      .unwrap_or(size_of::<usize>() - 1);
    self.append(&bytes[start..])
  }

  pub fn append_text(&mut self, text: impl AsRef<str>) -> PsdkFatherResult<&mut Self> {
    self.append_text_with_charset(text, psdk_const::DEF_CHARSET)
  }

  pub fn append_text_with_charset(
    &mut self,
    text: impl AsRef<str>,
    charset: impl AsRef<str>,
  ) -> PsdkFatherResult<&mut Self> {
    let bytes = quick::quick_text_encode(text, charset)?;
    Ok(self.append(&bytes))
  }

  pub fn output(&self) -> Vec<u8> {
    let mut binary = vec![];
    if let Some(header) = &self.header {
      binary.extend_from_slice(header);
    }
    binary.extend_from_slice(&self.arguments);
    binary
  }

  pub fn clause(&self) -> CommandClause {
    let binary = self.output();
    CommandClause::with_binary(&binary)
  }
}

#[cfg(test)]
mod tests {
  use super::PsdkTextCommand;

  #[test]
  fn test_text_command_omits_empty_arguments_without_extra_separators() {
    let mut command = PsdkTextCommand::new(",", ",");

    command
      .header("BLUETOOTH SETSSID")
      .append("name")
      .append("");

    assert_eq!(command.output(), "BLUETOOTH SETSSID,name");
  }

  #[test]
  fn test_text_command_omits_empty_trailing_argument_like_dart_append() {
    let mut command = PsdkTextCommand::new(" ", ",");

    command.header("LINE").append(1).append("");

    assert_eq!(command.output(), "LINE 1");
  }
}
