use std::borrow::Cow;
use std::fmt::Display;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use rusb::{
  Context, Device, DeviceDescriptor, DeviceHandle, DeviceList, Direction, TransferType, UsbContext,
};
use crate::error::{Error, Result};
use crate::constants::{
  ADSB_FREQUENCY_HZ, BLOCK_USB, DEFAULT_BUFFER_LENGTH, REQ_CTRL_OUT, SAMPLE_RATE_HZ, TIMEOUT,
  USB_EPA_CTL, USB_SYSCTL,
};

/// rusb/libusb implementation of `RtlSdrDevice`
pub struct RtlSdrDevice {
  /// Device handle
  handle: DeviceHandle<Context>,
  endpoint: Endpoint,
  frequency: u32,
  rate: u32,
}

impl RtlSdrDevice {
  /// Display RTL SDR information
  pub fn info(vid: u16, pid: u16) {
    let device_list = match DeviceList::new() {
      Ok(d) => d,
      Err(err) => {
        eprintln!("Unable to get device list: {:?}", err);
        return;
      }
    };
    for device in device_list.iter() {
      match device.device_descriptor() {
        Ok(device_desc) => {
          if vid != device_desc.vendor_id() || pid != device_desc.product_id() {
            continue;
          }
          println!(
            "Bus: {:03}, Device: {:03} VID: 0x{:04X}, PID: 0x{:04X}",
            device.bus_number(),
            device.address(),
            device_desc.vendor_id(),
            device_desc.product_id()
          );

          match device.open() {
            Ok(handle) => {
              println!("{}", device_info(&handle, &device_desc, "  ", true));
            }
            Err(err) => {
              eprintln!("  Unable to open device: {:?}", err);
              continue;
            }
          };
        }
        Err(err) => {
          eprintln!("Unable to get device descriptor: {:?}", err);
          continue;
        }
      };
    }
  }

  /// Open the RTL SDR device and return a wrapper
  pub fn open(vid: u16, pid: u16) -> Result<Self> {
    // Create a new libusb context
    let ctx = Context::new().map_err(|_| Error::new("Unable to create libusb context"))?;

    for device in ctx.devices()?.iter() {
      let device_desc = match device.device_descriptor() {
        Ok(d) => d,
        Err(_) => continue,
      };

      if device_desc.vendor_id() == vid && device_desc.product_id() == pid {
        let handle = device.open()?;

        log::debug!("{}", device_info(&handle, &device_desc, "", false));

        // Find the endpoint
        let endpoint = match Endpoint::find(&device_desc, &device, TransferType::Bulk) {
          Some(e) => e,
          None => return Err(Error::new("Unable to find endpoint on device")),
        };
        log::debug!("Found readable endpoint: {:?}", endpoint.to_string());

        let mut sdr = Self::new(handle, endpoint);

        sdr.initialize()?;

        return Ok(sdr);
      }
    }
    Err(Error::new("No valid device found"))
  }

  /// Close the RTL SDR device
  pub fn close(&self) -> Result<()> {
    log::debug!("Closing device...");
    self.attach_kernel_driver(self.endpoint.interface);
    self.handle.release_interface(self.endpoint.interface)?;
    Ok(())
  }

  /// Process the USB data
  pub fn process(&mut self, running: Arc<AtomicBool>) -> Result<()> {
    log::debug!(
      "Reading from active configuration: {}",
      self.handle.active_configuration()?
    );

    // Read endpoint
    let mut buffer = [0u8; DEFAULT_BUFFER_LENGTH];
    while running.load(Ordering::SeqCst) {
      let s = self.read(&mut buffer)?;
      log::debug!("Read: {}", s);
    }

    self.close()
  }

  fn new(handle: DeviceHandle<Context>, endpoint: Endpoint) -> Self {
    Self {
      handle,
      endpoint,
      frequency: 0,
      rate: 0,
    }
  }

  fn read(&self, buffer: &mut [u8; DEFAULT_BUFFER_LENGTH]) -> Result<String> {
    let length = match self.endpoint.transfer_type {
      TransferType::Interrupt => self
        .handle
        .read_interrupt(self.endpoint.address, buffer, TIMEOUT)
        .map_err(|err| Error::new(format!("Unable to read interrupt from endpoint: {:?}", err)))?,
      TransferType::Bulk => self
        .handle
        .read_bulk(self.endpoint.address, buffer, TIMEOUT)
        .map_err(|err| Error::new(format!("Unable to read bulk from endpoint: {:?}", err)))?,
      _ => 0,
    };
    log::trace!("Received {} bytes", length);
    let s = match String::from_utf8_lossy(&buffer[..length]) {
      Cow::Borrowed(s) => s.to_string(),
      Cow::Owned(s) => s,
    };
    Ok(s.to_string())
  }

  fn initialize(&mut self) -> Result<()> {
    // Configure the device for the endpoint
    self.set_active_configuration(self.endpoint.config)?;
    self.claim_interface(self.endpoint.interface)?;
    self.set_alternate_setting(self.endpoint.interface, self.endpoint.setting)?;
    self.detach_kernel_driver(self.endpoint.interface);

    self.test_write()?;

    // Reset the internal USB buffer
    self.reset_buffer()?;

    // Set the center-frequency in Hz
    self.set_center_frequency(ADSB_FREQUENCY_HZ)?;

    // Set the sample rate
    self.set_sample_rate(SAMPLE_RATE_HZ)?;

    Ok(())
  }

  fn set_active_configuration(&self, configuration: u8) -> Result<()> {
    self
      .handle
      .set_active_configuration(configuration)
      .map_err(|err| Error::new(format!("Failed to set active configuration: {:?}", err)))
  }

  fn claim_interface(&self, interface: u8) -> Result<()> {
    self
      .handle
      .claim_interface(interface)
      .map_err(|err| Error::new(format!("Failed to claim interface: {:?}", err)))
  }

  fn set_alternate_setting(&self, interface: u8, setting: u8) -> Result<()> {
    self
      .handle
      .set_alternate_setting(interface, setting)
      .map_err(|err| Error::new(format!("Failed to set alternate setting: {:?}", err)))
  }

  fn test_write(&self) -> Result<()> {
    log::trace!("Testing write to device...");
    let length = ctrl_write_register(&self.handle, BLOCK_USB, USB_SYSCTL, 0x89, 1)?;
    if length == 0 {
      log::info!("Resetting device");
      self
        .handle
        .reset()
        .map_err(|err| Error::new(format!("Failed to reset device: {:?}", err)))?;
    } else {
      log::trace!("Test write was successful");
    }
    Ok(())
  }

  fn reset_buffer(&self) -> Result<()> {
    log::trace!("Resetting buffer...");
    ctrl_write_register(&self.handle, BLOCK_USB, USB_EPA_CTL, 0x1002, 2)
      .map_err(|err| Error::new(format!("Failed to reset the internal buffer: {:?}", err)))?;
    ctrl_write_register(&self.handle, BLOCK_USB, USB_EPA_CTL, 0x0000, 2)
      .map_err(|err| Error::new(format!("Failed to reset the internal buffer: {:?}", err)))?;
    Ok(())
  }

  fn set_center_frequency(&mut self, frequency: u32) -> Result<()> {
    log::trace!("Setting center_frequency to {}Hz", frequency);
    self.frequency = frequency;
    Ok(())
  }

  fn set_sample_rate(&mut self, rate: u32) -> Result<()> {
    log::trace!("Setting sample_rate to {}Hz", rate);
    self.rate = rate;
    Ok(())
  }

  fn detach_kernel_driver(&self, interface: u8) {
    // Detach the kernel driver if applicable
    if let Ok(true) = self.handle.kernel_driver_active(interface) {
      log::trace!("Detaching active kernel driver");
      self.handle.detach_kernel_driver(interface).ok();
    }
  }

  fn attach_kernel_driver(&self, interface: u8) {
    // Attach the kernel driver if applicable
    if let Ok(true) = self.handle.kernel_driver_active(interface) {
      log::trace!("Attaching active kernel driver");
      self.handle.attach_kernel_driver(interface).ok();
    }
  }
}

#[derive(Debug)]
pub struct Endpoint {
  config: u8,
  interface: u8,
  setting: u8,
  address: u8,
  transfer_type: TransferType,
}

impl Display for Endpoint {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    write!(
      f,
      "Config: {}, Interface: {}, Setting: {}, Address: 0x{:04X}, Transfer Type: {:?}",
      self.config, self.interface, self.setting, self.address, self.transfer_type
    )
  }
}

impl Endpoint {
  fn find<T: UsbContext>(
    device_desc: &DeviceDescriptor,
    device: &Device<T>,
    transfer_type: TransferType,
  ) -> Option<Self> {
    for n in 0..device_desc.num_configurations() {
      let config_desc = match device.config_descriptor(n) {
        Ok(c) => c,
        Err(_) => continue,
      };

      for interface in config_desc.interfaces() {
        for interface_desc in interface.descriptors() {
          for endpoint_desc in interface_desc.endpoint_descriptors() {
            if endpoint_desc.direction() == Direction::In
              && endpoint_desc.transfer_type() == transfer_type
            {
              return Some(Endpoint {
                config: config_desc.number(),
                interface: interface_desc.interface_number(),
                setting: interface_desc.setting_number(),
                address: endpoint_desc.address(),
                transfer_type,
              });
            }
          }
        }
      }
    }
    None
  }
}

fn device_info<T: UsbContext>(
  handle: &DeviceHandle<T>,
  device_desc: &DeviceDescriptor,
  offset: &str,
  full: bool,
) -> String {
  let languages = match handle.read_languages(TIMEOUT) {
    Ok(l) => l,
    Err(err) => {
      return format!("{} Unable to get languages: {:?}", offset, err);
    }
  };
  let descriptor_type = device_desc.descriptor_type();
  let mut output = String::new();
  if full {
    output = format!("{}Device Descriptor ({})\n", offset, descriptor_type);
  }
  if !languages.is_empty() {
    for language in languages {
      let manufacturer = handle
        .read_manufacturer_string(language, device_desc, TIMEOUT)
        .unwrap_or_else(|err| err.to_string());
      let product = handle
        .read_product_string(language, device_desc, TIMEOUT)
        .unwrap_or_else(|err| err.to_string());
      let serial_number = handle
        .read_serial_number_string(language, device_desc, TIMEOUT)
        .unwrap_or_else(|err| err.to_string());
      output.push_str(&format!(
        "{}{}Manufacturer: {}, Product: {}, Serial Number: {}",
        offset, offset, manufacturer, product, serial_number
      ));

      if full {
        let length = device_desc.length();
        let version = format!(
          " v{}.{}.{}",
          device_desc.usb_version().major(),
          device_desc.usb_version().minor(),
          device_desc.usb_version().sub_minor()
        );
        output.push_str(&format!(
          "\n{}{}Length: {}, USB:{}\n",
          offset, offset, length, version,
        ));
        let class = device_desc.class_code();
        let sub_class = device_desc.sub_class_code();
        let protocol = device_desc.protocol_code();
        let max_packet_size = device_desc.max_packet_size();
        output.push_str(&format!(
          "{}{}Class: {:#04x}, Subclass: {:#04x}, Protocol: {:#04x}, Max Packet Size: {}",
          offset, offset, class, sub_class, protocol, max_packet_size
        ))
      }
    }
  }
  output
}

fn ctrl_write_register<T: UsbContext>(
  handle: &DeviceHandle<T>,
  block: u16,
  address: u16,
  value: u16,
  length: usize,
) -> rusb::Result<usize> {
  assert!(length == 1 || length == 2);

  let data: [u8; 2] = value.to_be_bytes();
  let buffer = if length == 1 { &data[1..2] } else { &data };
  let index = (block << 8) | 0x10;
  log::trace!(
    "Received block {}, address 0x{:04X}, value 0x{:04X}, length {} \
    - writing control register: {} 0x{:04X} 0x{:04X} {:?}",
    block,
    address,
    value,
    length,
    REQ_CTRL_OUT,
    address,
    index,
    buffer
  );

  handle.write_control(REQ_CTRL_OUT, 0x00, address, index, buffer, TIMEOUT)
}

fn demod_ctrl_write_register<T: UsbContext>(
  handle: &DeviceHandle<T>,
  page: u16,
  address: u16,
  value: u16,
  length: usize,
) -> rusb::Result<usize> {
  assert!(length == 1 || length == 2);

  let data: [u8; 2] = value.to_be_bytes();
  let buffer = if length == 1 { &data[1..2] } else { &data };
  let index = 0x10 | page;
  let address = (address << 8) | 0x20;
  handle.write_control(REQ_CTRL_OUT, 0x00, address, index, buffer, TIMEOUT)
}
