use std::sync::{Arc, Mutex};

use crate::{
  args::{PsdkArg, PsdkArgGenericType, Raw},
  command::{Commander, PsdkCommand},
  datawrite::{DataWriteOperation, WriteOptions},
  device::{ConnectedDevice, ReadOptions, WroteReporter},
  errors::{PsdkFatherResult, PsdkLockError},
  types::psdk_const,
};

pub trait Psdk {
  fn commander(&mut self) -> &mut Commander;
  fn connected_device(&self) -> Arc<Mutex<dyn ConnectedDevice>>;
  fn sversion(&self) -> &'static str {
    env!("CARGO_PKG_VERSION")
  }
  fn authors(&self) -> Vec<&str> {
    psdk_const::DEF_AUTHORS.to_vec()
  }
  fn variable(&mut self, name: impl AsRef<str>, value: impl AsRef<str>) -> &mut Self {
    self.commander().variable(name, value);
    self
  }

  fn clear(&mut self) -> &mut Self {
    self.commander().clear();
    self
  }

  fn newline(&mut self) -> &mut Self {
    self.commander().newline();
    self
  }

  fn command(&mut self) -> PsdkFatherResult<impl PsdkCommand> {
    self.commander().command()
  }

  fn write(&mut self) -> PsdkFatherResult<WroteReporter> {
    let options = WriteOptions {
      enable_chunk_write: true,
      send_done: true,
      chunk_size: psdk_const::DEF_WRITE_CHUNK_SIZE,
      callback: None,
    };
    self.write_with_options(options)
  }

  fn write_with_options(&mut self, options: WriteOptions) -> PsdkFatherResult<WroteReporter> {
    let connected_device = self.connected_device();
    let binary = {
      let command = self.command()?;
      command.binary().to_vec()
    };
    let operation = DataWriteOperation::new(options, binary, connected_device);
    let reporter = operation.write()?;
    self.clear();
    Ok(reporter)
  }

  fn read(&mut self) -> PsdkFatherResult<Vec<u8>> {
    let connected_device = self.connected_device();
    let connected_device = connected_device
      .lock()
      .map_err(|e| PsdkLockError::LockDevice(format!("{:?}", e)))?;
    connected_device.read(None)
  }

  fn read_with_options(&mut self, options: ReadOptions) -> PsdkFatherResult<Vec<u8>> {
    let connected_device = self.connected_device();
    let connected_device = connected_device
      .lock()
      .map_err(|e| PsdkLockError::LockDevice(format!("{:?}", e)))?;
    connected_device.read(Some(options))
  }

  fn push_arg<T: PsdkArgGenericType>(
    &mut self,
    // arg: &mut Box<impl PsdkArg<T>>,
    arg: impl PsdkArg<T>,
  ) -> PsdkFatherResult<&mut Self> {
    self.commander().push_arg(arg)?;
    Ok(self)
  }

  fn push_args<T: PsdkArgGenericType>(
    &mut self,
    // args: Vec<&mut Box<impl PsdkArg<T>>>,
    args: Vec<impl PsdkArg<T>>,
  ) -> PsdkFatherResult<&mut Self> {
    let commander = self.commander();
    for arg in args {
      commander.push_arg(arg)?;
    }
    Ok(self)
  }

  fn raw(&mut self, arg: Raw) -> PsdkFatherResult<&mut Self> {
    self.push_arg(arg)
  }
}
