use std::{
    fs::{self, File},
    io::{self, Read},
    path::PathBuf,
    process::ExitCode,
    str::FromStr,
};

use clap::{Parser, Subcommand};

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

#[derive(Debug, Subcommand)]
enum Action {
    List,
    Get {
        chip: u32,
        channel: u32,
    },
    Set {
        chip: u32,
        channel: u32,
        duty: Nanoseconds,
        period: Option<Nanoseconds>,
    },
}

struct Chip {
    id: u32,
    channels: Vec<u32>,
}

#[derive(Debug, Clone, Copy)]
struct Nanoseconds(u64);

impl From<u64> for Nanoseconds {
    fn from(value: u64) -> Self {
        Self(value)
    }
}

impl From<Nanoseconds> for u64 {
    fn from(value: Nanoseconds) -> Self {
        value.0
    }
}

impl FromStr for Nanoseconds {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let (scale, value) = if let Some(value) = s.strip_suffix("ns") {
            (1, value)
        } else if let Some(value) = s.strip_suffix("us") {
            (1000, value)
        } else if let Some(value) = s.strip_suffix("ms") {
            (1000000, value)
        } else if let Some(value) = s.strip_suffix("s") {
            (1000000000, value)
        } else {
            (1, s)
        };
        let value = value
            .parse::<u64>()
            .map_err(|_| format!("Invalid value: {value}"))?;
        let value = value * scale;
        Ok(Self(value))
    }
}

fn channel_path(chip: u32, channel: u32) -> PathBuf {
    PathBuf::from(format!("/dev/pwm{chip}-{channel}"))
}

fn add_channel(list: &mut Vec<Chip>, chip: u32, channel: Option<u32>) {
    let chip_index = list.iter().position(|p| p.id == chip).unwrap_or_else(|| {
        list.push(Chip {
            id: chip,
            channels: vec![],
        });
        list.len() - 1
    });

    if let Some(channel) = channel {
        let chip = &mut list[chip_index];
        if !chip.channels.contains(&channel) {
            chip.channels.push(channel);
        }
    }
}

fn list_chips() -> io::Result<()> {
    let mut entries = vec![];
    for entry in fs::read_dir("/dev")? {
        let entry = entry?;
        let name = entry.file_name();
        let name = name.to_str().unwrap();
        let Some(name) = name.strip_prefix("pwm") else {
            continue;
        };
        if let Some((chip_id, channel_id)) = name.split_once('-') {
            let Ok(chip) = chip_id.parse::<u32>() else {
                continue;
            };
            let Ok(channel) = channel_id.parse::<u32>() else {
                continue;
            };
            add_channel(&mut entries, chip, Some(channel));
        } else {
            let Ok(chip) = name.parse::<u32>() else {
                continue;
            };
            add_channel(&mut entries, chip, None);
        }
    }
    for entry in entries.iter_mut() {
        entry.channels.sort();
    }
    entries.sort_by_key(|p| p.id);

    for entry in entries {
        print!("pwm{}: ", entry.id);
        for (i, channel) in entry.channels.iter().enumerate() {
            if i != 0 {
                print!(",");
            }
            print!("{channel}");
        }
        println!();
    }

    Ok(())
}

fn get(chip: u32, channel: u32) -> io::Result<()> {
    let path = channel_path(chip, channel);
    let mut buffer = [0; 16];
    let len = File::open(path)?.read(&mut buffer)?;
    let data = &buffer[..len];
    if data.len() >= 8 {
        let duty = u64::from_ne_bytes(data[0..8].try_into().unwrap());
        println!("duty={duty}");
    }
    if data.len() >= 16 {
        let period = u64::from_ne_bytes(data[8..16].try_into().unwrap());
        println!("period={period}");
    }
    Ok(())
}

fn set(chip: u32, channel: u32, duty: Nanoseconds, period: Option<Nanoseconds>) -> io::Result<()> {
    let path = channel_path(chip, channel);
    let mut buffer = [0; 16];
    buffer[0..8].copy_from_slice(&duty.0.to_ne_bytes());
    let mut len = 8;
    if let Some(period) = period {
        buffer[8..16].copy_from_slice(&period.0.to_ne_bytes());
        len += 8;
    }
    fs::write(path, &buffer[..len])?;
    Ok(())
}

fn run(args: Args) -> io::Result<()> {
    match args.action {
        Action::List => list_chips(),
        Action::Set {
            chip,
            channel,
            duty,
            period,
        } => set(chip, channel, duty, period),
        Action::Get { chip, channel } => get(chip, channel),
    }
}

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

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