use std::{
    io::{self, Write, stdout},
    path::{Path, PathBuf},
    process::ExitCode,
    thread,
    time::Duration,
};

use clap::Parser;
use cross::{
    i2c::{I2CMaster, I2CMasterConfig},
    spi::{SpiConfig, SpiDevice},
};

#[derive(Debug, Parser)]
struct Args {
    #[clap(subcommand)]
    command: Subcommand,
}

#[derive(Debug, clap::Subcommand)]
enum Subcommand {
    M41T80 {
        #[clap(short, long, default_value_t = 0x40, help = "I²C device address")]
        address: u8,
        #[clap(help = "I²C controller path")]
        device: PathBuf,
    },
    JedecFlash {
        #[clap(short, long, default_value_t = 4 * 1024 * 1024, help = "SPI flash capacity")]
        capacity: usize,
        #[clap(help = "SPI device path")]
        device: PathBuf,
    },
}

struct M41T80 {
    master: I2CMaster,
}

#[derive(Debug, PartialEq)]
struct Time {
    year: u16,
    mon: u8,
    day: u8,

    hour: u8,
    min: u8,
    sec: u8,
    millis: u16,
}

impl M41T80 {
    fn open<P: AsRef<Path>>(path: P, config: &I2CMasterConfig) -> io::Result<Self> {
        let master = I2CMaster::open(path, config)?;
        Ok(Self { master })
    }

    fn read_time(&mut self) -> io::Result<Time> {
        let mut buffer = [0; 9];
        self.master.smbus_read(0x00, &mut buffer)?;

        let millis = ((buffer[0] >> 4) as u16 * 100) | ((buffer[0] & 0xF) as u16 * 10);
        let sec = ((buffer[1] >> 4) & 0x7) * 10 + (buffer[1] & 0xF);
        let min = ((buffer[2] >> 4) & 0x7) * 10 + (buffer[2] & 0xF);
        let hour = ((buffer[3] >> 4) & 0x3) * 10 + (buffer[3] & 0xF);

        let day = ((buffer[5] >> 4) & 0x3) * 10 + (buffer[5] & 0xF);
        let mon = ((buffer[6] >> 4) & 0x1) * 10 + (buffer[6] & 0xF);
        let year = ((buffer[7] >> 4) as u16 * 10) + ((buffer[7] & 0xF) as u16) + 2000;

        Ok(Time {
            millis,
            sec,
            min,
            hour,

            year,
            day,
            mon,
        })
    }
}

fn run_m41t80(device: PathBuf, address: u8) -> io::Result<()> {
    // let device = "/dev/i2c0";
    let i2c_config = I2CMasterConfig {
        slave_address: Some(address as _),
        speed_hz: None,
    };
    let mut m41t80 = M41T80::open(device, &i2c_config)?;
    loop {
        let time = m41t80.read_time()?;
        print!(
            "\r{:04}/{:02}/{:02} {:02}:{:02}:{:02}",
            time.year, time.mon, time.day, time.hour, time.min, time.sec,
        );
        stdout().flush().ok();
        thread::sleep(Duration::from_millis(100));
    }
}

fn run_jedec(device: PathBuf, capacity: usize) -> io::Result<()> {
    let config = SpiConfig::default();
    let mut spi = SpiDevice::open(device, &config)?;

    let tx = [0x13, 0x00, 0x00, 0x00, 0x00];
    let mut rx = [0; 5];
    spi.transfer(&tx, &mut rx).unwrap();

    let mut memory = vec![0xFF; capacity];

    const READ_BUFFER: usize = 1024;
    let tx_word = [0xFF; READ_BUFFER];
    let mut rx_word = [0; READ_BUFFER];
    let mut bytes_read = 0;
    for i in 0..capacity / READ_BUFFER {
        if spi.transfer(&tx_word, &mut rx_word)? != READ_BUFFER {
            break;
        }
        memory[i * READ_BUFFER..i * READ_BUFFER + READ_BUFFER].copy_from_slice(&rx_word);
        bytes_read += READ_BUFFER;
    }

    for (i, &byte) in memory.iter().enumerate() {
        if i >= bytes_read {
            print!("??");
        } else {
            print!("{:02x}", byte);
        }
        if (i + 1) % 2 == 0 {
            print!(" ");
        }
        if (i + 1) % 32 == 0 {
            println!();
        }
    }

    Ok(())
}

fn run(args: Args) -> io::Result<()> {
    match args.command {
        Subcommand::M41T80 { address, device } => run_m41t80(device, address),
        Subcommand::JedecFlash { capacity, device } => run_jedec(device, capacity),
    }
}

fn main() -> ExitCode {
    logsink::setup_logging(true);
    let args = Args::parse();

    match run(args) {
        Ok(()) => ExitCode::SUCCESS,
        Err(error) => {
            eprintln!("{error}");
            ExitCode::FAILURE
        }
    }
}
