mod contact;

use contact::ContactGroup;
pub use contact::{
    Contact, ContactIter, MAX_CONTACTS, MAX_CONTACT_CALLSIGN_LENGTH, MAX_CONTACT_NAME_LENGTH,
};

use core::slice::from_raw_parts;
use rp2040_flash::flash;

const SECTOR_SIZE: usize = 4096;
const FLASH_START: usize = 0x10000000;
// First 6MB is for code
const DATA_START: usize = FLASH_START + 6144 * 1024;
// 2MB for data
const DATA_END: usize = DATA_START + 2048 * 1024;

const X25: crc::Crc<u16> = crc::Crc::<u16>::new(&crc::CRC_16_IBM_SDLC);

pub struct Storage {
    contacts: ContactGroup,
}

impl Storage {
    pub fn new() -> Self {
        Self {
            contacts: ContactGroup::new(),
        }
    }

    // Punch through our contact methods

    pub fn new_contact(&self) -> Contact {
        self.contacts.new_contact()
    }

    pub fn save_contact(&mut self, contact: Contact) {
        let mut contacts = self.contacts;
        contacts.save_contact(self, contact);
        self.contacts = contacts;
    }

    pub fn delete_contact(&mut self, contact_id: usize) {
        let mut contacts = self.contacts;
        contacts.delete_contact(self, contact_id);
        self.contacts = contacts;
    }

    pub fn contacts(&self) -> ContactIter {
        self.contacts.contacts()
    }

    pub fn contacts_len(&self) -> usize {
        self.contacts.len()
    }

    fn write_data(&mut self, start_addr: usize, buf: &[u8]) {
        let start_sector = start_addr / SECTOR_SIZE;
        let num_sectors = buf.len() / SECTOR_SIZE + 1;
        let diff = start_addr % SECTOR_SIZE;

        for s in 0..num_sectors {
            let existing_start = if s == 0 { diff } else { 0 };
            let existing_end = (diff + (s + 1) * SECTOR_SIZE).min(existing_start + buf.len());
            let mut existing = self.read_slice((start_sector + s) * SECTOR_SIZE);
            let buf_start = (s * SECTOR_SIZE).min(buf.len());
            let buf_end = (buf_start + SECTOR_SIZE).min(buf.len());
            // only write if there's a difference
            if existing[existing_start..existing_end] != buf[buf_start..buf_end] {
                existing[existing_start..existing_end].copy_from_slice(&buf[buf_start..buf_end]);
                self.write_sector((start_sector + s) * SECTOR_SIZE, &existing);
            }
        }
    }

    fn read_slice<const N: usize>(&self, addr: usize) -> [u8; N] {
        assert!(addr >= DATA_START);
        assert!(addr + N < DATA_END);

        let sector = unsafe { from_raw_parts(addr as *mut u8, N) };

        let mut buf = [0; N];

        buf.copy_from_slice(sector);

        buf
    }

    fn write_sector(&mut self, start_addr: usize, buf: &[u8; 4096]) {
        let addr: u32 = (start_addr - FLASH_START).try_into().unwrap();
        let sector_size = u32::try_from(SECTOR_SIZE).unwrap();
        assert!(addr % sector_size == 0);

        cortex_m::interrupt::free(|_| unsafe {
            flash::flash_range_erase_and_program(addr, buf, true);
        });
    }
}