use crate::hal;
use crate::hal::gpio::Speed;
use crate::hal::prelude::*;
use crate::hal::xspi::{Config, Qspi, QspiMode, QspiWord};
use crate::pins::FMCPins;
const WRITE_STATUS_REGISTRY_CMD: u8 = 0x01; const WRITE_CMD: u8 = 0x02; const READ_STATUS_REGISTRY_CMD: u8 = 0x05; const WRITE_ENABLE_CMD: u8 = 0x06; const ENTER_QPI_MODE_CMD: u8 = 0x35; const SET_READ_PARAMETERS_CMD: u8 = 0xC0; const SECTOR_ERASE_CMD: u8 = 0xD7; const FAST_READ_QUAD_IO_CMD: u8 = 0xEB; 
const SECTOR_SIZE: u32 = 4096;
const PAGE_SIZE: u32 = 256;
const MAX_ADDRESS: u32 = 0x7FFFFF;
pub struct Flash {
    driver: Qspi<hal::pac::QUADSPI>,
}
impl Flash {
                                                                                                        pub fn new(
        clocks: &hal::rcc::CoreClocks,
        qspi_device: hal::device::QUADSPI,
        qspi_peripheral: hal::rcc::rec::Qspi,
        pins: FMCPins,
    ) -> Self {
                let _cs = pins.CS.into_alternate::<10>().set_speed(Speed::VeryHigh);
        let sck = pins.SCK.into_alternate().speed(Speed::VeryHigh);
        let io0 = pins.IO0.into_alternate().speed(Speed::VeryHigh);
        let io1 = pins.IO1.into_alternate().speed(Speed::VeryHigh);
        let io2 = pins.IO2.into_alternate().speed(Speed::VeryHigh);
        let io3 = pins.IO3.into_alternate().speed(Speed::VeryHigh);
        let qspi = qspi_device.bank1(
            (sck, io0, io1, io2, io3),
            Config::new(133.MHz()).mode(QspiMode::OneBit),
            clocks,
            qspi_peripheral,
        );
        let mut flash = Self { driver: qspi };
        flash.reset_status_register();
        flash.reset_read_register();
        flash.enable_qpi_mode();
        flash
    }
                                        pub fn read(&mut self, address: u32, buffer: &mut [u8]) {
        assert!(address <= MAX_ADDRESS);
                for (i, chunk) in buffer.chunks_mut(32).enumerate() {
            self.driver
                .read_extended(
                    QspiWord::U8(FAST_READ_QUAD_IO_CMD),
                    QspiWord::U24(address + i as u32 * 32),
                    QspiWord::U8(0x00),
                    8,
                    chunk,
                )
                .unwrap();
        }
    }
                                                                pub fn write(&mut self, mut address: u32, data: &[u8]) {
        assert!(address <= MAX_ADDRESS);
        assert!(data.len() > 0);
        self.erase(address, data.len() as u32);
        let mut length = data.len() as u32;
        let mut start_cursor = 0;
        loop {
                        let page_remainder = PAGE_SIZE - (address & (PAGE_SIZE - 1));
                        let size = page_remainder.min(length) as usize;
            for (i, chunk) in data[start_cursor..start_cursor + size]
                .chunks(32)
                .enumerate()
            {
                self.enable_write();
                self.driver
                    .write_extended(
                        QspiWord::U8(WRITE_CMD),
                        QspiWord::U24(address + i as u32 * 32),
                        QspiWord::None,
                        chunk,
                    )
                    .unwrap();
                self.wait_for_write();
            }
            start_cursor += size;
                        if length <= page_remainder {
                break;
            }
            length -= page_remainder;
                        address += page_remainder;
            address %= MAX_ADDRESS;
        }
    }
                                                                    pub fn erase(&mut self, mut address: u32, mut length: u32) {
        assert!(address <= MAX_ADDRESS);
        assert!(length > 0);
        loop {
                        self.enable_write();
            self.driver
                .write_extended(
                    QspiWord::U8(SECTOR_ERASE_CMD),
                    QspiWord::U24(address),
                    QspiWord::None,
                    &[],
                )
                .unwrap();
            self.wait_for_write();
                        let sector_remainder = SECTOR_SIZE - (address & (SECTOR_SIZE - 1));
                        if length <= sector_remainder {
                break;
            }
            length -= sector_remainder;
                        address += sector_remainder;
            address %= MAX_ADDRESS;
        }
    }
            fn reset_status_register(&mut self) {
        self.enable_write();
        self.driver
            .write_extended(
                QspiWord::U8(WRITE_STATUS_REGISTRY_CMD),
                QspiWord::U8(0b0000_0010),
                QspiWord::None,
                &[],
            )
            .unwrap();
        self.wait_for_write();
    }
            fn reset_read_register(&mut self) {
        self.enable_write();
        self.driver
            .write_extended(
                QspiWord::U8(SET_READ_PARAMETERS_CMD),
                QspiWord::U8(0b1111_1000),
                QspiWord::None,
                &[],
            )
            .unwrap();
        self.wait_for_write();
    }
        fn enable_qpi_mode(&mut self) {
        self.enable_write();
        self.driver
            .write_extended(
                QspiWord::U8(ENTER_QPI_MODE_CMD),
                QspiWord::None,
                QspiWord::None,
                &[],
            )
            .unwrap();
        self.driver.configure_mode(QspiMode::FourBit).unwrap();
        self.wait_for_write();
    }
            fn enable_write(&mut self) {
        self.driver
            .write_extended(
                QspiWord::U8(WRITE_ENABLE_CMD),
                QspiWord::None,
                QspiWord::None,
                &[],
            )
            .unwrap();
    }
                fn wait_for_write(&mut self) {
        loop {
            let mut status: [u8; 1] = [0xFF; 1];
            self.driver
                .read_extended(
                    QspiWord::U8(READ_STATUS_REGISTRY_CMD),
                    QspiWord::None,
                    QspiWord::None,
                    0,
                    &mut status,
                )
                .unwrap();
            if status[0] & 0x01 == 0 {
                break;
            }
        }
    }
}