use esp_idf_sys as _; // If using the `binstart` feature of `esp-idf-sys`, always keep this module imported

use anyhow::{Result, anyhow, bail};
use esp_idf_hal::{
    i2c::{
        I2cDriver, I2c,
        I2cConfig,
    },
    gpio::{
        Pin, InputPin, OutputPin,
    },
    delay::BLOCK,
    peripheral::Peripheral,
};

use esp_idf_hal::delay::FreeRtos;

use std::thread::sleep;
use std::time::Duration;

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

// struct Ssd1306<'d> {
//     driver: Arc<Mutex<I2cDriver<'d>>>,
//     address: u8,
// }

// impl<'d> Ssd1306<'d> {
//     pub fn new(driver: Arc<Mutex<I2cDriver<'d>>>, address: u8) -> Result<Self>
//     {
//         Ok(Self{ driver, address })
//     }

    
// }


pub struct LcdPcf8574<'d> {
  driver: Arc<Mutex<I2cDriver<'d>>>,
  address: u8,
  control: [(u8, u8); 2],
}

impl<'d> LcdPcf8574<'d> {
  fn write(&self, cmd: u8, value: &[u8], full: u8) -> Result<()> {
    // cmd:: 0: cmd, 1: data
    let (xn, en) = self.control[(cmd % 2) as usize];

    let send: Vec<u8> = match full {
      0b11 => value
        .iter()
        .flat_map(|v| {
          let (h, l) = (v & 0xF0, (v << 4) & 0xF0);
          [h | en, h | xn, l | en, l | xn]
        })
        .collect(),
      0b10 => value
        .iter()
        .flat_map(|v| {
          let (h, l) = (v & 0xF0, (v << 4) & 0xF0);
          [h | en, h | xn]
        })
        .collect(),
      0b01 => value
        .iter()
        .flat_map(|v| {
          let (h, l) = (v & 0xF0, (v << 4) & 0xF0);
          [l | en, l | xn]
        })
        .collect(),
      _ => bail!("Wrong write mode!"),
    };

    loop {
      if let Ok(ref mut driver) = self.driver.try_lock() {
        driver.write(self.address, &send, BLOCK)?;
        break;
      }
      sleep(Duration::from_micros(10));
    }
    sleep(Duration::from_micros(100));
    Ok(())
  }

  pub fn init(&self) -> Result<()> {
    // 4 bit mode initialization
    sleep(Duration::from_millis(50));
    self.write(0b0, &[0x33], 0b10)?;
    sleep(Duration::from_millis(6));
    self.write(0b0, &[0x33], 0b10)?;
    sleep(Duration::from_millis(2));
    self.write(0b0, &[0x33], 0b10)?;
    sleep(Duration::from_millis(10));
    self.write(0b0, &[0x20], 0b10)?; // 4-bit mode
    sleep(Duration::from_millis(10));
    // display initialzation
    self.write(0b0, &[0x28], 0b11)?;
    sleep(Duration::from_millis(1));
    self.write(0b0, &[0x0F], 0b11)?;
    sleep(Duration::from_millis(1));
    self.write(0b0, &[0x06], 0b11)?;
    sleep(Duration::from_millis(1));
    self.write(0b0, &[0x0F], 0b11)?;
    sleep(Duration::from_millis(1));
    self.clear()?;
    //
    Ok(())
  }

  pub fn set_cur(&self, row: u8, col: u8) -> Result<()> {
    let cmd = match row {
      0 => Ok(col | 0x80),
      1 => Ok(col | 0xC0),
      2 => Ok(col | 0x94),
      3 => Ok(col | 0xD4),
      _ => Err(anyhow!("Out of limit, row: {}", row)),
    }?;
    self.write(0b0, &[cmd], 0b11)?;
    sleep(Duration::from_millis(1));
    //
    Ok(())
  }

  pub fn set_light(&mut self, light: bool) {
    self.control = if light {
      [(0x08, 0x0C), (0x09, 0x0D)]
    } else {
      [(0x00, 0x04), (0x01, 0x05)]
    }
  }
  pub fn clear(&self,) -> Result<()> {
      self.write(0b0, &[0x01], 0b11);
      sleep(Duration::from_millis(1));
      Ok(())
  }

  pub fn send_str(&self, s: &str) -> Result<()> {
    self.write(0b1, s.as_bytes(), 0b11)?;
    sleep(Duration::from_millis(1));

    Ok(())
  }

  pub fn send_bytes(&self, s: &[u8]) -> Result<()> {
    self.write(0b1, s, 0b11)?;
    sleep(Duration::from_millis(1));

    Ok(())
  }

}


impl<'d> From<(&Arc<Mutex<I2cDriver<'d>>>, u8)> for LcdPcf8574<'d>  {
    fn from(value: (&Arc<Mutex<I2cDriver<'d>>>, u8)) -> Self {
        let driver = value.0.clone();
        let address = value.1;
        let control = [(0x08, 0x0C), (0x09, 0x0D)];
        LcdPcf8574 { driver, address, control }
    }
}


pub struct Ssd1306<'d> {
  driver: Arc<Mutex<I2cDriver<'d>>>,
  address: u8,
}

impl<'d> Ssd1306<'d> {
  fn write(&self, cmd: bool, data: &[u8]) -> Result<()> {
    let mut send: Vec<u8> = Vec::with_capacity(data.len() + 1);

    send.push(if cmd { 0x00 } else { 0x40 });
    send.extend_from_slice(data);

    loop {
      if let Ok(ref mut driver) = self.driver.try_lock() {
        let rt = driver.write(self.address, &send, BLOCK);
        break;
      }
      sleep(Duration::from_micros(10));
    }

    sleep(Duration::from_micros(1));

    Ok(())
  }

  pub fn init(&self) -> Result<()> {
    self.write(
      true,
      &[
        0xae, // display off
        0xd4, //
        0x80, //
        0xa8, //
        0x3f, //
        0xd3, //
        0x00, //
        0x40, //
        0x8d, //
        0x14, //
        0xa1, //
        0xc8, //
        0xda, //
        0x12, //
        0x81, //
        0xcf, //
        0xf1, //
        0xdb, //
        0x40, //
        0xa4, //
        0xa6, //
        0xaf, // display on
        0x20, 0x02, // display mode
      ],
    )?;
    Ok(())
  }

  pub fn draw(&self, data: &[u8], cur: Option<(u8, u8)>) -> Result<()> {
    if let Some((page, column)) = cur {
      self.write(
        true,
        &[0xB0 + page % 8, (column >> 4) & 0x0F | 0x10, column & 0x0F],
      )?;
    }

    self.write(false, &data)?;

    Ok(())
  }
}


impl<'d> From<(&Arc<Mutex<I2cDriver<'d>>>, u8)> for Ssd1306<'d> {
  fn from(value: (&Arc<Mutex<I2cDriver<'d>>>, u8)) -> Self {
      let driver = value.0.clone();
      let address = value.1;

      Ssd1306 { driver, address }
  }
}


fn main() -> Result<()> {
    use esp_idf_hal::prelude::FromValueType;

    esp_idf_sys::link_patches();
    println!("Hello, world!");

    let peripherals = esp_idf_hal::peripherals::Peripherals::take().unwrap();
    let i2c = peripherals.i2c0;
    let sda = peripherals.pins.gpio0;
    let scl = peripherals.pins.gpio1;
    let config = I2cConfig::new().baudrate(100_u32.kHz().into()).sda_enable_pullup(false);
    let iic = Arc::new(Mutex::new(I2cDriver::new(i2c, sda, scl, &config)?));

    let lcd1602 = LcdPcf8574::from((&iic, 0x27_u8));
    let lcd2004 = LcdPcf8574::from((&iic, 0x29_u8));
      lcd1602.init()?;
      lcd1602.set_cur(0, 0)?;
    lcd1602.send_str("ABCDEFGHIJKLMOPQ")?;
    lcd1602.set_cur(1, 0)?;
    lcd1602.send_str("ABCDEFGHIJKLMOPQ")?;

    lcd2004.init()?;
    lcd2004.set_cur(0, 0)?;
    lcd2004.send_str("ABCDEFGHIJKLMOPQRSTU")?;
    lcd2004.set_cur(1, 0)?;
    lcd2004.send_str("ABCDEFGHIJKLMOPQ")?;
    lcd2004.send_bytes(&[0xC2, 255]);
    lcd2004.set_cur(2, 0)?;
    lcd2004.send_str("ABCDEFGHIJKLMOPQ")?;
    lcd2004.set_cur(3, 0)?;
    lcd2004.send_str("ABCDEFGHIJKLMOPQ")?;

    let mut ssd1306 = Ssd1306::from((&iic, 0x3c));
    // println!("asd");
    ssd1306.init()?;
    // println!("asd");

        
    let i = 0;

    // println!("asd");

    // https://www.23bei.com/tool/218.html
    let data: [u8; 16*4] = [
        0x00,0x04,0x04,0x04,0xff,0x54,0x54,0x54,0x54,0x54,0xff,0x04,0x06,0x84,0x00,0x00,
        0x00,0x00,0xfe,0x22,0x22,0xe2,0x22,0x22,0x22,0x22,0x22,0xa2,0x3f,0x02,0x00,0x00,
        0x20,0x20,0x20,0x20,0x20,0x20,0x20,0xff,0x20,0x20,0x20,0x20,0x20,0x30,0x20,0x00,
        0x80,0x88,0xa8,0xa8,0xa9,0xaa,0xae,0xf8,0xac,0xaa,0xab,0xa8,0xac,0x88,0x80,0x00,
    ];
    ssd1306.draw(&data, Some((0, 0)))?;
    // println!("asd");

    let data: [u8; 16*4] = [
        0x11,0x11,0x89,0x89,0x95,0x93,0x91,0xfd,0x91,0x9b,0x95,0xc5,0x89,0x19,0x09,0x00,
        0x80,0x60,0x1f,0x00,0x00,0x3f,0x44,0x44,0x42,0x42,0x41,0x41,0x40,0x40,0x70,0x00,
        0x40,0x40,0x20,0x20,0x10,0x0c,0x0b,0x30,0x03,0x0c,0x10,0x10,0x20,0x60,0x20,0x00,
        0x80,0x84,0x84,0x44,0x44,0x24,0x14,0x0f,0x14,0x24,0x24,0x44,0x46,0xc4,0x40,0x00
    ];
    ssd1306.draw(&data, Some((1, 0)))?;
    // println!("asd");

    // let wifi_logo: [[u8; 32]; 4] = [
    //     [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0xc0,0xc0,0xc0,0xe0,0xe0,0xe0,0xf0,
    //     0xf0,0xe0,0xe0,0xe0,0xc0,0xc0,0xc0,0x80,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,],
    //     [0x00,0x00,0x00,0x0c,0x1e,0x0f,0x47,0xe7,0xe3,0xf3,0x79,0x79,0x39,0x38,0x1c,0x1c,
    //     0x1c,0x1c,0x38,0x39,0x39,0x79,0xf3,0xe3,0xe7,0x47,0x0f,0x1e,0x0c,0x00,0x00,0x00,],
    //     [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x0e,0x0f,0x8f,0xc7,0xe7,
    //     0xe7,0xc7,0x8f,0x0f,0x0e,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,],
    //     [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x03,0x07,
    //     0x07,0x03,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,],
    // ];
    // for i in 0..4 {
    //     // iic.write(0x3c, &[0, 0xB2 + i, 0x00, 0x15], BLOCK)?;
    //     ssd1306.draw(&wifi_logo[i as usize], Some((2 + i, 0x50)))?;
    //     // println!("asd {}", i);
    // }

    
    let wifi_logo: [[u8; 16]; 2] = [
      [0x00,0xc0,0xf0,0xf8,0x38,0x1c,0x00,0xfe,0xfe,0x00,0x1c,0x38,0xf0,0xe0,0x00,0x00,],
      [0x00,0x03,0x0f,0x1f,0x38,0x30,0x70,0x71,0x71,0x70,0x38,0x3c,0x1f,0x0f,0x00,0x00,],
    ];
    for i in 0..2 {
        // iic.write(0x3c, &[0, 0xB2 + i, 0x00, 0x15], BLOCK)?;
        ssd1306.draw(&wifi_logo[i as usize], Some((2 + i, 0x50)))?;
        // println!("asd {}", i);
    }

    ssd1306.draw(&[0x20,0x1c,0x1c,0x20,0x00,0x00,0x00,0x00], Some((2, 0)))?;

    // loop {
    //     // we are sleeping here to make sure the watchdog isn't triggered
    //     FreeRtos::delay_ms(500);
    //     iic.write(0x3C, &[0, 0xa6], BLOCK)?;
    //     FreeRtos::delay_ms(500);
    //     iic.write(0x3C, &[0, 0xa7], BLOCK)?;
    // }



    Ok(())
}