use std::{collections::HashMap, vec};

use crate::{
  args::{PsdkArg, PsdkArgGenericType},
  errors::PsdkFatherResult,
  types::psdk_const,
};

use super::{CommandClause, DefaultCommand, PsdkCommand};

#[derive(Clone, Debug)]
pub struct Commander {
  variables: HashMap<String, String>,
  clauses: Vec<CommandClause>,
}

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

impl Commander {
  pub fn new() -> Self {
    Self {
      variables: HashMap::new(),
      clauses: vec![],
    }
  }
}

impl Commander {
  pub fn push_clause(&mut self, clause: CommandClause, newline: bool) -> &mut Self {
    self.clauses.push(clause);
    if newline {
      self.newline();
    }
    self
  }

  pub fn newline(&mut self) -> &mut Self {
    self.clauses.push(CommandClause::newline());
    self
  }

  pub fn push_text(&mut self, command: impl AsRef<str>) -> &mut Self {
    self.push_text_with_charse(command, psdk_const::DEF_CHARSET)
  }

  pub fn push_text_with_charse(
    &mut self,
    command: impl AsRef<str>,
    charset: impl AsRef<str>,
  ) -> &mut Self {
    self.push_text_with_charset_and_newline(command, charset, psdk_const::DEF_ENABLED_NEWLINE)
  }

  pub fn push_text_with_newline(&mut self, command: impl AsRef<str>, newline: bool) -> &mut Self {
    self.push_text_with_charset_and_newline(command, psdk_const::DEF_CHARSET, newline)
  }

  pub fn push_text_with_charset_and_newline(
    &mut self,
    command: impl AsRef<str>,
    charset: impl AsRef<str>,
    newline: bool,
  ) -> &mut Self {
    let command = command
      .as_ref()
      .strip_prefix(psdk_const::CRLF)
      .unwrap_or(command.as_ref());
    let clause = CommandClause::with_text_and_charset(command, charset);
    self.push_clause(clause, newline)
  }

  pub fn push_binary_with_newline(&mut self, binary: &[u8], newline: bool) -> &mut Self {
    self.push_clause(CommandClause::with_binary(binary), newline)
  }

  pub fn push_binary(&mut self, binary: &[u8]) -> &mut Self {
    self.push_binary_with_newline(binary, psdk_const::DEF_ENABLED_NEWLINE)
  }

  pub fn push_arg<T: PsdkArgGenericType>(
    &mut self,
    arg: impl PsdkArg<T>,
  ) -> PsdkFatherResult<&mut Self> {
    let clauses = arg.clauses()?;
    for clause in clauses {
      self.push_clause(clause, false);
    }
    Ok(self)
  }

  pub fn variable(&mut self, name: impl AsRef<str>, value: impl AsRef<str>) -> &mut Self {
    self
      .variables
      .insert(name.as_ref().to_string(), value.as_ref().to_string());
    self
  }

  pub fn clear(&mut self) -> &mut Self {
    self.clauses.clear();
    self.variables.clear();
    self
  }

  pub fn command(&self) -> PsdkFatherResult<impl PsdkCommand> {
    let command = DefaultCommand::new(self.clauses.clone(), &self.variables)?;
    Ok(command)
  }
}

#[cfg(test)]
mod tests {
  use super::{Commander, PsdkCommand};

  #[test]
  fn test_push_text_strips_leading_crlf_like_dart() {
    let command = Commander::new()
      .push_text("\r\nTEXT COMMAND")
      .command()
      .unwrap();

    assert_eq!(command.binary(), b"TEXT COMMAND\r\n");
  }
}
