//! The `Storage`, `Read`, `Write` and `Seek` driver.
#![allow(non_camel_case_types)]

use generic_array::ArrayLength;
use littlefs2_sys as ll;

use crate::{
    io::Result,
};

/// Users of this library provide a "storage driver" by implementing this trait.
///
/// The `write` method is assumed to be synchronized to storage immediately.
/// littlefs provides more flexibility - if required, this could also be exposed.
/// Do note that due to caches, files still must be synched. And unfortunately,
/// this can't be automatically done in `drop`, since it needs mut refs to both
/// filesystem and storage.
///
/// The `*_SIZE` types must be `generic_array::typenume::consts` such as `U256`.
///
/// Why? Currently, associated constants can not be used (as constants...) to define
/// arrays. This "will be fixed" as part of const generics.
/// Once that's done, we can get rid of `generic-array`s, and replace the
/// `*_SIZE` types with `usize`s.
pub trait Storage {

    // /// Error type for user-provided read/write/erase methods
    // type Error = usize;

    /// Minimum size of block read in bytes. Not in superblock
    const READ_SIZE: usize;

    /// Minimum size of block write in bytes. Not in superblock
    const WRITE_SIZE: usize;

    /// Size of an erasable block in bytes, as unsigned typenum.
    /// Must be a multiple of both `READ_SIZE` and `WRITE_SIZE`.
    /// At least 128 (https://git.io/JeHp9). Stored in superblock.
    const BLOCK_SIZE: usize;

    /// Number of erasable blocks.
    /// Hence storage capacity is `BLOCK_COUNT * BLOCK_SIZE`
    const BLOCK_COUNT: usize;

    /// Suggested values are 100-1000, higher is more performant but
    /// less wear-leveled.  Default of -1 disables wear-leveling.
    /// Value zero is invalid, must be positive or -1.
    const BLOCK_CYCLES: isize = -1;

    /// littlefs uses a read cache, a write cache, and one cache per per file.
    /// Must be a multiple of `READ_SIZE` and `WRITE_SIZE`.
    /// Must be a factor of `BLOCK_SIZE`.
    type CACHE_SIZE: ArrayLength<u8>;

    /// littlefs itself has a `LOOKAHEAD_SIZE`, which must be a multiple of 8,
    /// as it stores data in a bitmap. It also asks for 4-byte aligned buffers.
    /// Hence, we further restrict `LOOKAHEAD_SIZE` to be a multiple of 32.
    /// Our LOOKAHEADWORDS_SIZE is this multiple.
    type LOOKAHEADWORDS_SIZE: ArrayLength<u32>;
    // type LOOKAHEAD_SIZE: ArrayLength<u8>;

    /// Maximum length of a filename plus one. Stored in superblock.
    /// Should default to 255+1, but associated type defaults don't exist currently.
    /// At most 1_022+1.
    ///
    /// TODO: We can't actually change this - need to pass on as compile flag
    /// to the C backend.
    type FILENAME_MAX_PLUS_ONE: ArrayLength<u8>;

    /// Maximum length of a path plus one. Necessary to convert Rust string slices
    /// to C strings, which requires an allocation for the terminating
    /// zero-byte. If in doubt, set to `FILENAME_MAX_PLUS_ONE`.
    /// Must be larger than `FILENAME_MAX_PLUS_ONE`.
    type PATH_MAX_PLUS_ONE: ArrayLength<u8>;

    /// Maximum size of file. Stored in superblock.
    /// Defaults to 2_147_483_647 (or u31, to avoid sign issues in the C code).
    /// At most 2_147_483_647.
    ///
    /// TODO: We can't actually change this - need to pass on as compile flag
    /// to the C backend.
    const FILEBYTES_MAX: usize = ll::LFS_FILE_MAX as _;

    /// Maximum size of custom attributes.
    /// Should default to 1_022, but associated type defaults don't exists currently.
    /// At most 1_022.
    ///
    /// TODO: We can't actually change this - need to pass on as compile flag
    /// to the C backend.
    type ATTRBYTES_MAX: ArrayLength<u8>;

    /// Read data from the storage device.
    /// Guaranteed to be called only with bufs of length a multiple of READ_SIZE.
    fn read(&self, off: usize, buf: &mut [u8]) -> Result<usize>;
    /// Write data to the storage device.
    /// Guaranteed to be called only with bufs of length a multiple of WRITE_SIZE.
    fn write(&mut self, off: usize, data: &[u8]) -> Result<usize>;
    /// Erase data from the storage device.
    /// Guaranteed to be called only with bufs of length a multiple of BLOCK_SIZE.
    fn erase(&mut self, off: usize, len: usize) -> Result<usize>;
    // /// Synchronize writes to the storage device.
    // fn sync(&mut self) -> Result<usize>;
}

// in the future, try to split the megatrait `Storage` into pieces
// like this?
mod future {
    // content of "superblock"
    pub trait DiskFormat {
        // version, upper/lower half-word contain major/minor
        // const DISK_FORMAT_VERSION: u32,

        // block_size, block_count
        const BLOCK_SIZE: usize;
        const BLOCK_COUNT: usize;

        // name_max, file_max, attr_max
        type FILENAME_MAX_PLUS_ONE;
        const FILEBYTES_MAX: usize = super::ll::LFS_FILE_MAX as _;
        type ATTRBYTES_MAX;
    }

    pub trait Driver {
        const READ_SIZE: usize;
        const WRITE_SIZE: usize;

        const BLOCK_SIZE: usize;
        const BLOCK_COUNT: usize;

        // fn read(&self, offset: usize, buf: &mut [u8]) -> Result<usize>;
        // fn write(&mut self, offset: usize, data: &[u8]) -> Result<usize>;
        // fn erase(&mut self, offset: usize, len: usize) -> Result<usize>;
    }

    pub trait MemoryUsage {
        // TODO: this supposedly influences whether files are inlined or not. Clarify
        type CACHE_SIZE;
        type LOOKAHEADWORDS_SIZE;
        // not part of littlefs
        type PATH_MAX_PLUS_ONE;
    }

    pub trait RuntimeParameters {
        const BLOCK_CYCLES: isize = -1;
    }
}
