use std::error;
use std::fmt;
use std::fs;
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::result;
const PERIPHERAL_BASE_RPI: u32 = 0x2000_0000;
const PERIPHERAL_BASE_RPI2: u32 = 0x3f00_0000;
const PERIPHERAL_BASE_RPI4: u32 = 0xfe00_0000;
const PERIPHERAL_BASE_RP1: u32 = 0x4000_0000;
const GPIO_OFFSET: u32 = 0x20_0000;
const GPIO_OFFSET_RP1: u32 = 0x0d_0000;
const GPIO_LINES_BCM283X: u8 = 54;
const GPIO_LINES_BCM2711: u8 = 58;
const GPIO_LINES_RP1: u8 = 28;
#[derive(Debug)]
pub enum Error {
    UnknownModel,
}
impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match *self {
            Error::UnknownModel => write!(f, "Unknown Raspberry Pi model"),
        }
    }
}
impl error::Error for Error {}
pub type Result<T> = result::Result<T, Error>;
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[non_exhaustive]
pub enum Model {
    RaspberryPiA,
    RaspberryPiAPlus,
    RaspberryPiBRev1,
    RaspberryPiBRev2,
    RaspberryPiBPlus,
    RaspberryPi2B,
    RaspberryPi3APlus,
    RaspberryPi3B,
    RaspberryPi3BPlus,
    RaspberryPi4B,
    RaspberryPi400,
    RaspberryPi5,
    RaspberryPiComputeModule,
    RaspberryPiComputeModule3,
    RaspberryPiComputeModule3Plus,
    RaspberryPiComputeModule4,
    RaspberryPiComputeModule4S,
    RaspberryPiZero,
    RaspberryPiZeroW,
    RaspberryPiZero2W,
}
impl fmt::Display for Model {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match *self {
            Model::RaspberryPiA => write!(f, "Raspberry Pi A"),
            Model::RaspberryPiAPlus => write!(f, "Raspberry Pi A+"),
            Model::RaspberryPiBRev1 => write!(f, "Raspberry Pi B Rev 1"),
            Model::RaspberryPiBRev2 => write!(f, "Raspberry Pi B Rev 2"),
            Model::RaspberryPiBPlus => write!(f, "Raspberry Pi B+"),
            Model::RaspberryPi2B => write!(f, "Raspberry Pi 2 B"),
            Model::RaspberryPi3B => write!(f, "Raspberry Pi 3 B"),
            Model::RaspberryPi3BPlus => write!(f, "Raspberry Pi 3 B+"),
            Model::RaspberryPi3APlus => write!(f, "Raspberry Pi 3 A+"),
            Model::RaspberryPi4B => write!(f, "Raspberry Pi 4 B"),
            Model::RaspberryPi400 => write!(f, "Raspberry Pi 400"),
            Model::RaspberryPi5 => write!(f, "Raspberry Pi 5"),
            Model::RaspberryPiComputeModule => write!(f, "Raspberry Pi Compute Module"),
            Model::RaspberryPiComputeModule3 => write!(f, "Raspberry Pi Compute Module 3"),
            Model::RaspberryPiComputeModule3Plus => write!(f, "Raspberry Pi Compute Module 3+"),
            Model::RaspberryPiComputeModule4 => write!(f, "Raspberry Pi Compute Module 4"),
            Model::RaspberryPiComputeModule4S => write!(f, "Raspberry Pi Compute Module 4S"),
            Model::RaspberryPiZero => write!(f, "Raspberry Pi Zero"),
            Model::RaspberryPiZeroW => write!(f, "Raspberry Pi Zero W"),
            Model::RaspberryPiZero2W => write!(f, "Raspberry Pi Zero 2 W"),
        }
    }
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub(crate) enum GpioInterface {
    Bcm,
    Rp1,
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[non_exhaustive]
pub enum SoC {
    Bcm2835,
    Bcm2836,
    Bcm2837A1,
    Bcm2837B0,
    Bcm2711,
    Bcm2712,
}
impl fmt::Display for SoC {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match *self {
            SoC::Bcm2835 => write!(f, "BCM2835"),
            SoC::Bcm2836 => write!(f, "BCM2836"),
            SoC::Bcm2837A1 => write!(f, "BCM2837A1"),
            SoC::Bcm2837B0 => write!(f, "BCM2837B0"),
            SoC::Bcm2711 => write!(f, "BCM2711"),
            SoC::Bcm2712 => write!(f, "BCM2712"),
        }
    }
}
fn parse_proc_cpuinfo() -> Result<Model> {
    let proc_cpuinfo = BufReader::new(match File::open("/proc/cpuinfo") {
        Ok(file) => file,
        Err(_) => return Err(Error::UnknownModel),
    });
    let mut hardware: String = String::new();
    let mut revision: String = String::new();
    for line in proc_cpuinfo.lines().flatten() {
        if let Some(line_value) = line.strip_prefix("Hardware\t: ") {
            hardware = String::from(line_value);
        } else if let Some(line_value) = line.strip_prefix("Revision\t: ") {
            revision = String::from(line_value).to_lowercase();
        }
    }
    match &hardware[..] {
        "BCM2708" | "BCM2835" | "BCM2709" | "BCM2836" | "BCM2710" | "BCM2837" | "BCM2837A1"
        | "BCM2837B0" | "RP3A0-AU" | "BCM2710A1" | "BCM2711" | "BCM2712" => {}
        _ => return Err(Error::UnknownModel),
    }
    let model = if (revision.len() == 4) || (revision.len() == 8) {
        match &revision[revision.len() - 4..] {
            "0007" | "0008" | "0009" | "0015" => Model::RaspberryPiA,
            "beta" | "0002" | "0003" => Model::RaspberryPiBRev1,
            "0004" | "0005" | "0006" | "000d" | "000e" | "000f" => Model::RaspberryPiBRev2,
            "0012" => Model::RaspberryPiAPlus,
            "0010" | "0013" => Model::RaspberryPiBPlus,
            "0011" | "0014" => Model::RaspberryPiComputeModule,
            _ => return Err(Error::UnknownModel),
        }
    } else if revision.len() >= 6 {
        let revision_type = match u64::from_str_radix(&revision, 16) {
            Ok(revision_type) => (revision_type >> 4) & 0xff,
            Err(_) => return Err(Error::UnknownModel),
        };
        match revision_type {
            0x00 => Model::RaspberryPiA,
            0x01 => Model::RaspberryPiBRev2,
            0x02 => Model::RaspberryPiAPlus,
            0x03 => Model::RaspberryPiBPlus,
            0x04 => Model::RaspberryPi2B,
            0x06 => Model::RaspberryPiComputeModule,
            0x08 => Model::RaspberryPi3B,
            0x09 => Model::RaspberryPiZero,
            0x0a => Model::RaspberryPiComputeModule3,
            0x0c => Model::RaspberryPiZeroW,
            0x0d => Model::RaspberryPi3BPlus,
            0x0e => Model::RaspberryPi3APlus,
            0x10 => Model::RaspberryPiComputeModule3Plus,
            0x11 => Model::RaspberryPi4B,
            0x12 => Model::RaspberryPiZero2W,
            0x13 => Model::RaspberryPi400,
            0x14 => Model::RaspberryPiComputeModule4,
            0x15 => Model::RaspberryPiComputeModule4S,
            0x17 => Model::RaspberryPi5,
            _ => return Err(Error::UnknownModel),
        }
    } else {
        return Err(Error::UnknownModel);
    };
    Ok(model)
}
fn parse_base_compatible() -> Result<Model> {
    let base_compatible = match fs::read_to_string("/sys/firmware/devicetree/base/compatible") {
        Ok(buffer) => buffer,
        Err(_) => return Err(Error::UnknownModel),
    };
    for comp_id in base_compatible.split('\0') {
        let model = match comp_id {
            "raspberrypi,model-b-i2c0" => Model::RaspberryPiBRev1,
            "raspberrypi,model-b" => Model::RaspberryPiBRev1,
            "raspberrypi,model-a" => Model::RaspberryPiA,
            "raspberrypi,model-b-rev2" => Model::RaspberryPiBRev2,
            "raspberrypi,model-a-plus" => Model::RaspberryPiAPlus,
            "raspberrypi,model-b-plus" => Model::RaspberryPiBPlus,
            "raspberrypi,2-model-b" => Model::RaspberryPi2B,
            "raspberrypi,compute-module" => Model::RaspberryPiComputeModule,
            "raspberrypi,3-model-b" => Model::RaspberryPi3B,
            "raspberrypi,model-zero" => Model::RaspberryPiZero,
            "raspberrypi,3-compute-module" => Model::RaspberryPiComputeModule3,
            "raspberrypi,3-compute-module-plus" => Model::RaspberryPiComputeModule3Plus,
            "raspberrypi,model-zero-w" => Model::RaspberryPiZeroW,
            "raspberrypi,model-zero-2" => Model::RaspberryPiZero2W,
            "raspberrypi,3-model-b-plus" => Model::RaspberryPi3BPlus,
            "raspberrypi,3-model-a-plus" => Model::RaspberryPi3APlus,
            "raspberrypi,4-model-b" => Model::RaspberryPi4B,
            "raspberrypi,400" => Model::RaspberryPi400,
            "raspberrypi,4-compute-module" => Model::RaspberryPiComputeModule4,
            "raspberrypi,4-compute-module-s" => Model::RaspberryPiComputeModule4S,
            "raspberrypi,5-model-b" => Model::RaspberryPi5,
            _ => continue,
        };
        return Ok(model);
    }
    Err(Error::UnknownModel)
}
fn parse_base_model() -> Result<Model> {
    let mut base_model = match fs::read_to_string("/sys/firmware/devicetree/base/model") {
        Ok(mut buffer) => {
            if let Some(idx) = buffer.find('\0') {
                buffer.truncate(idx);
            }
            buffer
        }
        Err(_) => return Err(Error::UnknownModel),
    };
    match &base_model[..] {
        "Raspberry Pi Model B Rev 2.0" => return Ok(Model::RaspberryPiBRev2),
        "Raspberry Pi Model B rev2 Rev 2.0" => return Ok(Model::RaspberryPiBRev2),
        _ => (),
    }
    if let Some(idx) = base_model.find(" Rev ") {
        base_model.truncate(idx);
    }
    let model = match &base_model[..] {
        "Raspberry Pi Model B (no P5)" => Model::RaspberryPiBRev1,
        "Raspberry Pi Model B" => Model::RaspberryPiBRev1,
        "Raspberry Pi Model A" => Model::RaspberryPiA,
        "Raspberry Pi Model B rev2" => Model::RaspberryPiBRev2,
        "Raspberry Pi Model A+" => Model::RaspberryPiAPlus,
        "Raspberry Pi Model A Plus" => Model::RaspberryPiAPlus,
        "Raspberry Pi Model B+" => Model::RaspberryPiBPlus,
        "Raspberry Pi Model B Plus" => Model::RaspberryPiBPlus,
        "Raspberry Pi 2 Model B" => Model::RaspberryPi2B,
        "Raspberry Pi Compute Module" => Model::RaspberryPiComputeModule,
        "Raspberry Pi 3 Model B" => Model::RaspberryPi3B,
        "Raspberry Pi Zero" => Model::RaspberryPiZero,
        "Raspberry Pi Compute Module 3" => Model::RaspberryPiComputeModule3,
        "Raspberry Pi Compute Module 3 Plus" => Model::RaspberryPiComputeModule3Plus,
        "Raspberry Pi Zero W" => Model::RaspberryPiZeroW,
        "Raspberry Pi Zero 2" => Model::RaspberryPiZero2W,
        "Raspberry Pi 3 Model B+" => Model::RaspberryPi3BPlus,
        "Raspberry Pi 3 Model B Plus" => Model::RaspberryPi3BPlus,
        "Raspberry Pi 3 Model A Plus" => Model::RaspberryPi3APlus,
        "Raspberry Pi 4 Model B" => Model::RaspberryPi4B,
        "Raspberry Pi 400" => Model::RaspberryPi400,
        "Raspberry Pi Compute Module 4" => Model::RaspberryPiComputeModule4,
        "Raspberry Pi Compute Module 4S" => Model::RaspberryPiComputeModule4S,
        "Raspberry Pi 5 Model B" => Model::RaspberryPi5,
        _ => return Err(Error::UnknownModel),
    };
    Ok(model)
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub struct DeviceInfo {
    model: Model,
    soc: SoC,
    peripheral_base: u32,
    gpio_offset: u32,
    gpio_lines: u8,
    gpio_interface: GpioInterface,
}
impl DeviceInfo {
    pub fn new() -> Result<DeviceInfo> {
        let model = parse_proc_cpuinfo()
            .or_else(|_| parse_base_compatible().or_else(|_| parse_base_model()))?;
        match model {
            Model::RaspberryPiA
            | Model::RaspberryPiAPlus
            | Model::RaspberryPiBRev1
            | Model::RaspberryPiBRev2
            | Model::RaspberryPiBPlus
            | Model::RaspberryPiComputeModule
            | Model::RaspberryPiZero
            | Model::RaspberryPiZeroW => Ok(DeviceInfo {
                model,
                soc: SoC::Bcm2835,
                peripheral_base: PERIPHERAL_BASE_RPI,
                gpio_offset: GPIO_OFFSET,
                gpio_lines: GPIO_LINES_BCM283X,
                gpio_interface: GpioInterface::Bcm,
            }),
            Model::RaspberryPi2B => Ok(DeviceInfo {
                model,
                soc: SoC::Bcm2836,
                peripheral_base: PERIPHERAL_BASE_RPI2,
                gpio_offset: GPIO_OFFSET,
                gpio_lines: GPIO_LINES_BCM283X,
                gpio_interface: GpioInterface::Bcm,
            }),
            Model::RaspberryPi3B | Model::RaspberryPiComputeModule3 | Model::RaspberryPiZero2W => {
                Ok(DeviceInfo {
                    model,
                    soc: SoC::Bcm2837A1,
                    peripheral_base: PERIPHERAL_BASE_RPI2,
                    gpio_offset: GPIO_OFFSET,
                    gpio_lines: GPIO_LINES_BCM283X,
                    gpio_interface: GpioInterface::Bcm,
                })
            }
            Model::RaspberryPi3BPlus
            | Model::RaspberryPi3APlus
            | Model::RaspberryPiComputeModule3Plus => Ok(DeviceInfo {
                model,
                soc: SoC::Bcm2837B0,
                peripheral_base: PERIPHERAL_BASE_RPI2,
                gpio_offset: GPIO_OFFSET,
                gpio_lines: GPIO_LINES_BCM283X,
                gpio_interface: GpioInterface::Bcm,
            }),
            Model::RaspberryPi4B
            | Model::RaspberryPi400
            | Model::RaspberryPiComputeModule4
            | Model::RaspberryPiComputeModule4S => Ok(DeviceInfo {
                model,
                soc: SoC::Bcm2711,
                peripheral_base: PERIPHERAL_BASE_RPI4,
                gpio_offset: GPIO_OFFSET,
                gpio_lines: GPIO_LINES_BCM2711,
                gpio_interface: GpioInterface::Bcm,
            }),
            Model::RaspberryPi5 => Ok(DeviceInfo {
                model,
                soc: SoC::Bcm2712,
                peripheral_base: PERIPHERAL_BASE_RP1,
                gpio_offset: GPIO_OFFSET_RP1,
                gpio_lines: GPIO_LINES_RP1,
                gpio_interface: GpioInterface::Rp1,
            }),
        }
    }
    pub fn model(&self) -> Model {
        self.model
    }
    pub fn soc(&self) -> SoC {
        self.soc
    }
    pub(crate) fn peripheral_base(&self) -> u32 {
        self.peripheral_base
    }
    pub(crate) fn gpio_offset(&self) -> u32 {
        self.gpio_offset
    }
    pub(crate) fn gpio_lines(&self) -> u8 {
        self.gpio_lines
    }
    pub(crate) fn gpio_interface(&self) -> GpioInterface {
        self.gpio_interface
    }
}