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

use crate::{
  device::{ConnectedDevice, DeviceWriteOptions, WroteReporter},
  errors::{PsdkFatherResult, PsdkLockDeviceResult, PsdkLockError},
  types::psdk_const,
};

#[derive(Clone, Debug)]
pub struct DataWrite {
  pub binary: Vec<u8>,
  pub current_times: u32,
  pub total_times: u32,
}

#[derive(Clone, Debug)]
pub struct DataWrote {
  pub binary: Vec<u8>,
  pub current_times: u32,
  pub total_times: u32,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum WriteControl {
  Continue,
  Stop,
}

pub trait DataWriteCallback {
  fn success(&self, data: DataWrote) -> PsdkFatherResult<WriteControl>;
  fn failed(&self, data: DataWrite, error: String) -> PsdkFatherResult<()>;
}

pub struct DoNothingDataWriteCallback;

impl DataWriteCallback for DoNothingDataWriteCallback {
  fn success(&self, _data: DataWrote) -> PsdkFatherResult<WriteControl> {
    Ok(WriteControl::Continue)
  }

  fn failed(&self, _data: DataWrite, _error: String) -> PsdkFatherResult<()> {
    Ok(())
  }
}

pub struct WriteOptions {
  pub enable_chunk_write: bool,
  pub send_done: bool,
  pub chunk_size: u32,
  pub callback: Option<Box<dyn DataWriteCallback>>,
}

impl Default for WriteOptions {
  fn default() -> Self {
    Self {
      enable_chunk_write: true,
      send_done: true,
      chunk_size: psdk_const::DEF_WRITE_CHUNK_SIZE,
      callback: None,
    }
  }
}

pub struct DataWriteOperation {
  options: WriteOptions,
  binary: Vec<u8>,
  connected_device: Arc<Mutex<dyn ConnectedDevice>>,
}

impl DataWriteOperation {
  pub fn new(
    options: WriteOptions,
    binary: Vec<u8>,
    connected_device: Arc<Mutex<dyn ConnectedDevice>>,
  ) -> Self {
    Self {
      options,
      binary,
      connected_device,
    }
  }
}

impl DataWriteOperation {
  pub fn write(&self) -> PsdkLockDeviceResult<WroteReporter> {
    let start = std::time::SystemTime::now();
    let reporter = self.write_inner()?;
    let elapsed = start.elapsed().unwrap();
    println!("Write data use {}ms.", elapsed.as_millis());
    Ok(reporter)
  }

  fn write_binary(&self, binary: &[u8], send_done: bool) -> PsdkLockDeviceResult<WroteReporter> {
    let connected_device = self
      .connected_device
      .lock()
      .map_err(|e| PsdkLockError::LockDevice(format!("{:?}", e)))?;
    let reporter =
      match connected_device.write_with_options(binary, DeviceWriteOptions { send_done }) {
        Ok(_) => WroteReporter {
          ok: true,
          binary: binary.to_vec(),
          control_exit: false,
          exception: None,
        },
        Err(e) => WroteReporter {
          ok: false,
          binary: binary.to_vec(),
          control_exit: false,
          exception: Some(format!("{:?}", e)),
        },
      };
    Ok(reporter)
  }

  fn write_inner(&self) -> PsdkLockDeviceResult<WroteReporter> {
    if !self.options.enable_chunk_write {
      let reporter = self.write_binary(&self.binary, self.options.send_done)?;
      self.do_callback(&reporter, &self.binary, 1, 1);
      return Ok(reporter);
    }

    if self.options.chunk_size == 0 {
      return Err(PsdkLockError::WriteOptions(
        "chunk size must be greater than zero".to_string(),
      ));
    }

    let parts: Vec<Vec<u8>> = self
      .binary
      .chunks(self.options.chunk_size as usize)
      .map(|chunk| chunk.to_vec())
      .collect();

    let total_times = parts.len() as u32;
    let mut current_times = 0;
    for part in parts {
      current_times += 1;
      let send_done = current_times == total_times && self.options.send_done;
      let mut reporter = self.write_binary(&part, send_done)?;
      let control = self.do_callback(&reporter, &part, current_times, total_times);
      if control == WriteControl::Stop {
        reporter.control_exit = true;
        return Ok(reporter);
      }
      if !reporter.ok {
        return Ok(reporter);
      }
    }

    Ok(WroteReporter {
      ok: true,
      binary: self.binary.to_vec(),
      control_exit: false,
      exception: None,
    })
  }

  fn do_callback(
    &self,
    reporter: &WroteReporter,
    binary: &[u8],
    current_times: u32,
    total_times: u32,
  ) -> WriteControl {
    match &self.options.callback {
      Some(callback) => {
        let data_write = DataWrite {
          binary: binary.to_vec(),
          current_times,
          total_times,
        };
        let data_wrote = DataWrote {
          binary: binary.to_vec(),
          current_times,
          total_times,
        };
        if reporter.ok {
          match callback.success(data_wrote) {
            Ok(v) => {
              return v;
            }
            Err(e) => {
              if let Err(cfe) = callback.failed(data_write, e.to_string()) {
                println!("{:?}", cfe);
              }
              return WriteControl::Stop;
            }
          }
        }
        if let Err(e) = callback.failed(data_write, reporter.exception.clone().unwrap_or_default())
        {
          println!("{:?}", e);
        }
        WriteControl::Stop
      }
      None => WriteControl::Continue,
    }
  }
}
