#![allow(clippy::unnecessary_cast)]
use std::ffi::CString;
use std::fs;
use std::fs::File;
use std::io;
use std::io::Write;
use std::os::unix::fs::{MetadataExt, PermissionsExt};
use std::path::Path;
use std::ptr;
use std::result;
use std::thread;
use std::time::Duration;
use libc::{c_char, group, passwd};
use crate::pwm::Polarity;
pub type Result<T> = result::Result<T, io::Error>;
pub fn user_to_uid(name: &str) -> Option<u32> {
    if let Ok(name_cstr) = CString::new(name) {
        let buf = &mut [0 as c_char; 4096];
        let mut res: *mut passwd = ptr::null_mut();
        let mut pwd = passwd {
            pw_name: ptr::null_mut(),
            pw_passwd: ptr::null_mut(),
            pw_uid: 0,
            pw_gid: 0,
            pw_gecos: ptr::null_mut(),
            pw_dir: ptr::null_mut(),
            pw_shell: ptr::null_mut(),
        };
        unsafe {
            if libc::getpwnam_r(
                name_cstr.as_ptr(),
                &mut pwd,
                buf.as_mut_ptr(),
                buf.len(),
                &mut res,
            ) == 0
                && res as usize > 0
            {
                return Some((*res).pw_uid);
            }
        }
    }
    None
}
pub fn group_to_gid(name: &str) -> Option<u32> {
    if let Ok(name_cstr) = CString::new(name) {
        let buf = &mut [0 as c_char; 4096];
        let mut res: *mut group = ptr::null_mut();
        let mut grp = group {
            gr_name: ptr::null_mut(),
            gr_passwd: ptr::null_mut(),
            gr_gid: 0,
            gr_mem: ptr::null_mut(),
        };
        unsafe {
            if libc::getgrnam_r(
                name_cstr.as_ptr(),
                &mut grp,
                buf.as_mut_ptr(),
                buf.len(),
                &mut res,
            ) == 0
                && res as usize > 0
            {
                return Some((*res).gr_gid);
            }
        }
    }
    None
}
fn check_permissions(path: &str, gid: u32) -> bool {
    if let Ok(metadata) = fs::metadata(path) {
        if metadata.permissions().mode() != 0o040_770 && metadata.permissions().mode() != 0o100_770
        {
            return false;
        }
        if metadata.gid() == gid {
            return true;
        }
    }
    false
}
pub fn export(channel: u8) -> Result<()> {
    if !Path::new(&format!("/sys/class/pwm/pwmchip0/pwm{}", channel)).exists() {
        File::create("/sys/class/pwm/pwmchip0/export")?.write_fmt(format_args!("{}", channel))?;
    }
    if let Some(root_uid) = user_to_uid("root") {
        unsafe {
            if libc::getuid() == root_uid || libc::geteuid() == root_uid {
                return Ok(());
            }
        }
    }
    let gid_gpio = if let Some(gid) = group_to_gid("gpio") {
        gid
    } else {
        0
    };
    let paths = &[
        format!("/sys/class/pwm/pwmchip0/pwm{}", channel),
        format!("/sys/class/pwm/pwmchip0/pwm{}/period", channel),
        format!("/sys/class/pwm/pwmchip0/pwm{}/duty_cycle", channel),
        format!("/sys/class/pwm/pwmchip0/pwm{}/polarity", channel),
        format!("/sys/class/pwm/pwmchip0/pwm{}/enable", channel),
    ];
    let mut counter = 0;
    'counter: while counter < 25 {
        for path in paths {
            if !check_permissions(path, gid_gpio) {
                thread::sleep(Duration::from_millis(40));
                counter += 1;
                continue 'counter;
            }
        }
        break;
    }
    Ok(())
}
pub fn unexport(channel: u8) -> Result<()> {
    if Path::new(&format!("/sys/class/pwm/pwmchip0/pwm{}", channel)).exists() {
        File::create("/sys/class/pwm/pwmchip0/unexport")?.write_fmt(format_args!("{}", channel))?;
    }
    Ok(())
}
pub fn period(channel: u8) -> Result<u64> {
    let period = fs::read_to_string(format!("/sys/class/pwm/pwmchip0/pwm{}/period", channel))?;
    if let Ok(period) = period.trim().parse() {
        Ok(period)
    } else {
        Ok(0)
    }
}
pub fn set_period(channel: u8, period: u64) -> Result<()> {
    File::create(format!("/sys/class/pwm/pwmchip0/pwm{}/period", channel))?
        .write_fmt(format_args!("{}", period))?;
    Ok(())
}
pub fn pulse_width(channel: u8) -> Result<u64> {
    let duty_cycle =
        fs::read_to_string(format!("/sys/class/pwm/pwmchip0/pwm{}/duty_cycle", channel))?;
    if let Ok(duty_cycle) = duty_cycle.trim().parse() {
        Ok(duty_cycle)
    } else {
        Ok(0)
    }
}
pub fn set_pulse_width(channel: u8, pulse_width: u64) -> Result<()> {
    File::create(format!("/sys/class/pwm/pwmchip0/pwm{}/duty_cycle", channel))?
        .write_fmt(format_args!("{}", pulse_width))?;
    Ok(())
}
pub fn polarity(channel: u8) -> Result<Polarity> {
    let polarity = fs::read_to_string(format!("/sys/class/pwm/pwmchip0/pwm{}/polarity", channel))?;
    match polarity.trim() {
        "normal" => Ok(Polarity::Normal),
        _ => Ok(Polarity::Inverse),
    }
}
pub fn set_polarity(channel: u8, polarity: Polarity) -> Result<()> {
    let b_polarity: &[u8] = match polarity {
        Polarity::Normal => b"normal",
        Polarity::Inverse => b"inversed",
    };
    File::create(format!("/sys/class/pwm/pwmchip0/pwm{}/polarity", channel))?
        .write_all(b_polarity)?;
    Ok(())
}
pub fn enabled(channel: u8) -> Result<bool> {
    let enabled = fs::read_to_string(format!("/sys/class/pwm/pwmchip0/pwm{}/enable", channel))?;
    match enabled.trim() {
        "0" => Ok(false),
        _ => Ok(true),
    }
}
pub fn set_enabled(channel: u8, enabled: bool) -> Result<()> {
    File::create(format!("/sys/class/pwm/pwmchip0/pwm{}/enable", channel))?
        .write_fmt(format_args!("{}", enabled as u8))
        .map_err(|e| {
            if e.kind() == io::ErrorKind::InvalidInput {
                io::Error::new(
                    io::ErrorKind::InvalidInput,
                    "Make sure you have set either a period or frequency before enabling PWM",
                )
            } else {
                e
            }
        })?;
    Ok(())
}