//! # Extension for constructing Rustic values
use assembly_core::displaydoc::Display;
use thiserror::Error;

use super::{DatabaseBufReader, DatabaseReader};
use crate::fdb::{
    common::{UnknownValueType, ValueType},
    core::Field,
    file::FDBFieldData,
};

use std::{
    convert::TryFrom,
    io::{self, BufRead, Seek},
};

/// Errors generated by the builder module
#[derive(Debug, Display, Error)]
pub enum BuilderError {
    /// Failed IO
    IO(#[from] io::Error),
    /// Unknown Value Type
    UnknownValueType(#[from] UnknownValueType),
}

/// Results for this module
pub type Result<T> = std::result::Result<T, BuilderError>;

impl<T: ?Sized> DatabaseBuilder for T where T: DatabaseBufReader + DatabaseReader + Seek + BufRead {}

/// Extension trait for `Seek + BufRead + DatabaseBufReader + DatabaseReader`
pub trait DatabaseBuilder
where
    Self: Seek + BufRead + DatabaseBufReader + DatabaseReader,
{
    /// Try to load a field value
    fn try_load_field(&mut self, data: &FDBFieldData) -> Result<Field> {
        let bytes = data.value;
        match ValueType::try_from(data.data_type)? {
            ValueType::Nothing => Ok(Field::Nothing),
            ValueType::Integer => Ok(bytes).map(i32::from_le_bytes).map(Field::Integer),
            ValueType::Float => Ok(bytes)
                .map(u32::from_le_bytes)
                .map(f32::from_bits)
                .map(Field::Float),
            ValueType::Text => Ok(bytes)
                .map(u32::from_le_bytes)
                .and_then(|addr| self.get_string(addr))
                .map(Field::Text)
                .map_err(Into::into),
            ValueType::Boolean => Ok(bytes).map(|v| v != [0; 4]).map(Field::Boolean),
            ValueType::BigInt => Ok(bytes)
                .map(u32::from_le_bytes)
                .and_then(|addr| self.get_i64(addr))
                .map(Field::BigInt)
                .map_err(Into::into),
            ValueType::VarChar => Ok(bytes)
                .map(u32::from_le_bytes)
                .and_then(|addr| self.get_string(addr))
                .map(Field::VarChar)
                .map_err(Into::into),
        }
    }
}
