#![feature(yggdrasil_os)]
use std::{
    io,
    marker::PhantomData,
    os::fd::AsRawFd,
    path::{Path, PathBuf},
    process::ExitCode,
    str::FromStr,
    time::{Duration, Instant},
};

use clap::Parser;
use cross::{
    i2c::{I2CMaster, I2CMasterConfig},
    io::{Poll, PollEvent},
};
use libterm::{Term, TermKey};
use ratatui::{
    Terminal,
    layout::Constraint,
    style::{Color, Style},
    widgets::{Cell, Row, Table},
};

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

#[derive(Debug, Clone, Copy)]
struct Address(u8);

#[derive(Debug, clap::Subcommand)]
enum Subcommand {
    TMP421 {
        #[clap(help = "I²C controller path")]
        device: PathBuf,
        #[clap(default_value = "0x40", help = "I²C device address")]
        address: Address,
    },
    TMP451 {
        #[clap(help = "I²C controller path")]
        device: PathBuf,
        #[clap(default_value = "0x4C", help = "I²C device address")]
        address: Address,
    },
    Scan {
        #[clap(help = "I²C controller path")]
        device: PathBuf,
    },
    Dump {
        #[clap(help = "I²C controller path")]
        device: PathBuf,
        #[clap(help = "I²C device address")]
        address: Address,
    },
}

trait TmpSensorDefinition {
    const REG_LOCAL_TEMP_MSB: u8;
    const REG_LOCAL_TEMP_LSB: u8;
    const REG_REMOTE_TEMP_MSB: u8;
    const REG_REMOTE_TEMP_LSB: u8;

    fn process_temperature(lsb: u8, msb: u8) -> f64;
}

struct Tmp451;
struct Tmp421;

struct TmpSensor<T: TmpSensorDefinition> {
    master: I2CMaster,
    _definition: PhantomData<T>,
}

impl FromStr for Address {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let value = if let Some(v) = s.strip_prefix("0x") {
            u8::from_str_radix(v, 16)
        } else {
            s.parse::<u8>()
        }
        .map_err(|_| format!("Invalid address: {s:?}"))?;
        if value >= 0x80 {
            Err(format!("Invalid address: {s:?}"))
        } else {
            Ok(Self(value))
        }
    }
}

impl<T: TmpSensorDefinition> TmpSensor<T> {
    pub fn open<P: AsRef<Path>>(path: P, address: Address) -> io::Result<Self> {
        let i2c_config = I2CMasterConfig {
            slave_address: Some(address.0 as _),
            speed_hz: None,
        };
        let master = I2CMaster::open(path, &i2c_config)?;
        Ok(Self {
            master,
            _definition: PhantomData,
        })
    }

    fn read_temperature(&mut self, msb_reg: u8, lsb_reg: u8) -> io::Result<f64> {
        let msb = self.master.smbus_read_byte(msb_reg)?;
        let lsb = self.master.smbus_read_byte(lsb_reg)?;
        Ok(T::process_temperature(lsb, msb))
    }

    pub fn read_local_temperature(&mut self) -> io::Result<f64> {
        self.read_temperature(T::REG_LOCAL_TEMP_MSB, T::REG_LOCAL_TEMP_LSB)
    }

    pub fn read_remote_temperature(&mut self) -> io::Result<f64> {
        self.read_temperature(T::REG_REMOTE_TEMP_MSB, T::REG_REMOTE_TEMP_LSB)
    }
}

impl TmpSensorDefinition for Tmp421 {
    const REG_LOCAL_TEMP_MSB: u8 = 0x00;
    const REG_LOCAL_TEMP_LSB: u8 = 0x10;
    const REG_REMOTE_TEMP_MSB: u8 = 0x01;
    const REG_REMOTE_TEMP_LSB: u8 = 0x11;

    fn process_temperature(lsb: u8, msb: u8) -> f64 {
        (msb as i8) as f64 + (lsb >> 4) as f64 * 0.0625
    }
}

impl TmpSensorDefinition for Tmp451 {
    const REG_LOCAL_TEMP_MSB: u8 = 0x00;
    const REG_LOCAL_TEMP_LSB: u8 = 0x15;
    const REG_REMOTE_TEMP_MSB: u8 = 0x01;
    const REG_REMOTE_TEMP_LSB: u8 = 0x10;

    fn process_temperature(lsb: u8, msb: u8) -> f64 {
        msb as f64 + (lsb >> 4) as f64 * 0.0625
    }
}

fn run_dump<P: AsRef<Path>>(device: P, address: Address) -> io::Result<()> {
    let i2c_config = I2CMasterConfig {
        slave_address: Some(address.0 as _),
        speed_hz: None,
    };
    let mut master = I2CMaster::open(device, &i2c_config)?;
    for i in 0..16 {
        print!("{:02x}: ", i * 16);
        for j in 0..16 {
            match master.smbus_read_byte(i * 16 + j) {
                Ok(byte) => print!("{byte:02X}"),
                Err(_) => print!("??"),
            }
            if j % 2 == 1 {
                print!(" ");
            }
        }
        println!();
    }
    Ok(())
}

fn run_scan<P: AsRef<Path>>(device: P) -> io::Result<()> {
    for i in 0..16 {
        print!("{:02x}: ", i * 16);
        for j in 0..16 {
            let address = i * 16 + j;
            let i2c_config = I2CMasterConfig {
                slave_address: Some(address),
                speed_hz: None,
            };
            let Ok(mut master) = I2CMaster::open(&device, &i2c_config) else {
                print!(" ");
                continue;
            };
            match master.smbus_read_byte(0xFF) {
                Ok(_) => print!("+"),
                Err(e) if e.kind() == io::ErrorKind::HostUnreachable => print!("-"),
                _ => print!("X"),
            }
        }
        println!();
    }
    Ok(())
}

fn run_tmp<T: TmpSensorDefinition, P: AsRef<Path>>(device: P, address: Address) -> io::Result<()> {
    let mut poll = Poll::new()?;
    let term = Term::open()?;
    let term_fd = term.as_raw_fd();
    let mut term = Terminal::new(term)?;

    poll.add(&term_fd)?;

    let mut tmp = TmpSensor::<T>::open(device, address)?;
    let mut local_temp = 0.0;
    let mut remote_temp = 0.0;
    let mut last_poll = Instant::now();

    loop {
        if last_poll.elapsed() >= Duration::from_millis(500) {
            local_temp = tmp.read_local_temperature().unwrap_or_default();
            remote_temp = tmp.read_remote_temperature().unwrap_or_default();
            last_poll = Instant::now();
        }

        term.draw(|frame| {
            let rect = frame.area();
            let hint_style = Style::default().bg(Color::Blue).fg(Color::White);
            let header = Row::new(vec![Cell::from("Name"), Cell::from("Value")])
                .height(1)
                .style(hint_style);
            let rows = vec![
                Row::new(vec![
                    Cell::from("Local"),
                    Cell::from(format!("{local_temp:.04}")),
                ]),
                Row::new(vec![
                    Cell::from("Remote"),
                    Cell::from(format!("{remote_temp:.04}")),
                ]),
            ];
            let table =
                Table::new(rows, &[Constraint::Min(6), Constraint::Percentage(100)]).header(header);
            frame.render_widget(table, rect);
        })?;

        let p = poll.wait(Some(Duration::from_millis(100)))?;
        match p {
            PollEvent::Ready(_) => {
                let key = term.backend_mut().read_key()?;
                if key == TermKey::Char('q') {
                    break;
                }
            }
            PollEvent::Error(_, error) => {
                return Err(error);
            }
            PollEvent::None => (),
        }
    }

    Ok(())
}

fn run(action: Subcommand) -> io::Result<()> {
    match action {
        Subcommand::Scan { device } => run_scan(device),
        Subcommand::Dump { address, device } => run_dump(device, address),
        Subcommand::TMP421 { address, device } => run_tmp::<Tmp421, _>(device, address),
        Subcommand::TMP451 { address, device } => run_tmp::<Tmp451, _>(device, address),
    }
}

fn main() -> ExitCode {
    let args = Args::parse();
    match run(args.command) {
        Ok(()) => ExitCode::SUCCESS,
        Err(error) => {
            eprintln!("{error}");
            ExitCode::FAILURE
        }
    }
}
