#![no_std]

use embedded_hal::{delay::DelayNs, digital::OutputPin};

const BIAS: u32 = 0xa40;
const SYS_EN: u32 = 0x40;
const LCD_OFF: u32 = 0x80;
const LCD_ON: u32 = 0xc0;
const RC256: u32 = 0x600;

const COMMAND: u32 = 0x10000;
const WRITE: u32 = 0x14000;

const ABC_HEX: [u32; 64] = [
    0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x80, 0x00,
    0x7D, 0x60, 0x3e, 0x7a, 0x63, 0x5b, 0x5f, 0x70, 0x7f, 0x7b, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00,
    0x00, 0x77, 0x4f, 0x1d, 0x6e, 0x1f, 0x17, 0x5d, 0x47, 0x05, 0x68, 0x27, 0x0d, 0x54, 0x75, 0x4e,
    0x37, 0x73, 0x06, 0x59, 0x0f, 0x6d, 0x23, 0x29, 0x67, 0x6b, 0x3c, 0x00, 0x00, 0x00, 0x10, 0x08,
];

pub struct Pdc6x1<DATA, WR, CS, DELAY>
where
    DATA: OutputPin,
    WR: OutputPin,
    CS: OutputPin,
    DELAY: DelayNs,
{
    data: DATA,
    wr: WR,
    cs: CS,
    delay: DELAY,
    digits_buf: [char; 9],
    bat: u32,
}

impl<DATA, WR, CS, DELAY> Pdc6x1<DATA, WR, CS, DELAY>
where
    DATA: OutputPin,
    WR: OutputPin,
    CS: OutputPin,
    DELAY: DelayNs,
{
    pub fn new(data: DATA, wr: WR, cs: CS, delay: DELAY) -> Self {
        let mut lcd = Self {
            data,
            wr,
            cs,
            delay,
            digits_buf: [' '; 9],
            bat: 0,
        };
        lcd.init();
        lcd.clear();
        lcd
    }

    pub fn init(&mut self) {
        self.cs.set_high().ok();
        self.wr.set_high().ok();
        self.data.set_high().ok();

        for data in [SYS_EN, RC256, BIAS, LCD_ON] {
            self.send(COMMAND | data);
        }
    }

    /// ***decimal_places*** must be 0, 1, 2, or 3.
    ///
    /// You should ensure that your number fits on the display.
    ///
    /// The display prints from right to left.
    /// 
    /// If you send ```.print_f32(-123456.789, 1);``` then **-3456.7** will be printed.
    pub fn print_f32(&mut self, digit: f32, decimal_places: u32) {
        let sign = digit.is_sign_negative();
        let mut val: u32 = (digit.abs() * 10_i32.pow(decimal_places) as f32 + 0.5) as u32;
        let mut pos = 0;

        while pos < self.digits_buf.len() {
            self.digits_buf[pos] = ((val % 10) as u8 + b'0') as char;
            val /= 10;
            pos += 1;

            if decimal_places > 0 && pos == decimal_places as usize && pos < self.digits_buf.len() {
                self.digits_buf[pos] = '.';
                pos += 1;
            }

            let min_pos = if decimal_places > 0 {
                decimal_places + 1
            } else {
                1
            };
            if val == 0 && pos > min_pos as usize {
                break;
            }
        }

        if sign && pos < self.digits_buf.len() {
            self.digits_buf[pos] = '-';
        }
        self.print_chars();
    }

    /// **Display degrees**
    /// 
    ///Prints the number and adds the `º` character at the end.
    /// 
    /// `-125` -> `-125º`
    pub fn print_degrees(&mut self, digit: i32) {
        self.digits_buf[0] = '%';
        self.print_i32(digit);
    }

    /// You should ensure that your number fits on the display.
    /// 
    /// The display prints from right to left.
    ///
    /// If you send ```.print_i32(-123456);``` then **-23456** will be printed.
    pub fn print_i32(&mut self, digit: i32) {
        let sign = digit.is_negative();
        let mut val = digit.unsigned_abs();

        let mut pos = self.digits_buf.iter().position(|&c| c == ' ').unwrap_or(0);

        while pos < self.digits_buf.len() {
            self.digits_buf[pos] = ((val % 10) as u8 + b'0') as char;
            val /= 10;
            pos += 1;
            if val == 0 {
                break;
            }
        }

        if sign && pos < self.digits_buf.len() {
            self.digits_buf[pos] = '-';
        }
        self.print_chars();
    }

    /// For example ```.print_str("Error");```
    ///
    /// or ```.print_str("25%");```-> **25º**
    pub fn print_str(&mut self, text: &str) {
        let mut chars = text.chars().rev();
        for i in self.digits_buf.iter_mut() {
            *i = chars.next().unwrap_or(' ');
        }
        self.print_chars();
    }

    fn print_chars(&mut self) {
        let buf_copy = self.digits_buf;
        let mut chars = buf_copy.iter().peekable();

        for address in (0..0xc00).step_by(0x200) {
            let mut dot: u32 = 0;

            let char_ = chars.next().unwrap_or(&' ');

            if let Some(&&'.') = chars.peek() {
                dot = 0x80;
                chars.next();
            }

            if address >= 0x600 && address <= self.bat {
                dot = 0x80;
            }

            let code = ABC_HEX
                .get((char_.to_ascii_uppercase() as usize).saturating_sub(32))
                .unwrap_or(&0);

            self.send(WRITE | address | code | dot);
        }
        self.digits_buf.fill(' ');
    }

    /// Clears the display.
    pub fn clear(&mut self) {
        self.digits_buf.fill(' ');
        self.print_chars();
    }

    /// Battery level must be 0, 1, 2 or 3.
    pub fn battery(&mut self, level: u32) {
        if (0..=3).contains(&level) {
            self.bat = (level * 0x200) + 0x400;
        }
    }

    pub fn on(&mut self) {
        self.send(COMMAND | LCD_ON);
    }

    pub fn off(&mut self) {
        self.send(COMMAND | LCD_OFF);
    }

    fn send(&mut self, data: u32) {
        self.cs.set_low().ok();
        for i in (0..17).rev() {
            self.wr.set_low().ok();
            if (data >> i) & 1 == 1 {
                self.data.set_high().ok();
            } else {
                self.data.set_low().ok();
            }
            self.delay.delay_ns(1_000);
            self.wr.set_high().ok();
            self.delay.delay_ns(1_000);
        }
        self.cs.set_high().ok();
    }
}
