//! Functions for burritos.cfg configuration file parsing.
//! Needed to set-up machine and system constants without
//! recompiling.

use std::{
    fs::File, 
    path::Path,
    collections::HashMap,
    io::{
        BufReader,
        BufRead,
        Error
    }
};

/// Aliases the rather long HashMap<MachineSettingKey, i32> type
/// to a rather simpler to understand Settings.
pub type Settings = HashMap<MachineSettingKey, u64>;

/// Keys for the Settings HashMap, represented as enums for
/// maintainability.
#[derive(Eq, Hash, PartialEq, Debug)]
pub enum MachineSettingKey {
    /// Number of physical pages.
    NumPhysPages,
    /// Stack size.
    UserStackSize,
    /// Maximum size of a file name
    MaxFileNameSize,
    /// Number of directory entries
    NumDirEntries,
    /// Processor Frequency
    ProcessorFrequency,
    /// Disk sector size
    SectorSize,
    /// Memory page size
    PageSize,
    /// Maximum number of Virtual Pages
    MaxVirtPages,
    /// In case of unknown key in configuration file.
    Unknown
}

/// Allows for converting string slices to correspoding MachineSettingKey
/// enum value.
impl From<&str> for MachineSettingKey {
    fn from(s: &str) -> Self {
        match s {
            "NumPhysPages" => MachineSettingKey::NumPhysPages,
            "UserStackSize" => MachineSettingKey::UserStackSize,
            "MaxFileNameSize" => MachineSettingKey::MaxFileNameSize,
            "NumDirEntries" => MachineSettingKey::NumDirEntries,
            "ProcessorFrequency" => MachineSettingKey::ProcessorFrequency,
            "SectorSize" => MachineSettingKey::SectorSize,
            "PageSize" => MachineSettingKey::PageSize,
            "MaxVirtPages" => MachineSettingKey::MaxVirtPages,
            _ => MachineSettingKey::Unknown
        }
    }
}

/// Tries to return a HashMap containing the user defined burritos configuration
/// in the burritos.cfg file.
/// 
/// If the file is not found, the function will return an io error.
/// 
/// If the configuration is invalid, the function may return a HashMap with missing or
/// non-sensical settings.
/// It is up to the caller to determine whether or not default values should be placed
/// instead of halting the program.
pub fn read_settings() -> Result<Settings, Error> {
    // Opening file
    let file = {
        let file_path = "./burritos.cfg";
        let file_path = Path::new(file_path);
        match File::open(file_path) {
            Ok(opened_file) => opened_file,
            Err(error_message) => Err(error_message)?
        }
    };
    let file_reader = BufReader::new(file);
    let filtered_setting_strings = filter_garbage(file_reader);
    let mut settings_map = Settings::new();
    // Reading settings
    for line in filtered_setting_strings {
        let mut split_line = line.split_whitespace();
        let key = split_line.next().unwrap_or("_");
        split_line.next(); // Skipping '=' character
        let setting = split_line.next().unwrap_or("_");
        settings_map = update_settings_map(settings_map, key, setting);
    }
    Ok(settings_map)
}

/// Returns a mock configuration for Machine unit testing
/// FIXME: Does not cover the whole configuration yet
pub fn get_debug_configuration() -> Settings {
    let mut settings_map = Settings::new();
    settings_map.insert(MachineSettingKey::PageSize, 128);
    settings_map.insert(MachineSettingKey::NumPhysPages, 400);
    settings_map
}

fn filter_garbage<R: std::io::Read>(reader: BufReader<R>) -> Vec<String> {
    reader.lines()
        .map(|l| l.unwrap())
        .filter(|l| !l.is_empty() && !l.starts_with("#"))
        .collect()
}

fn update_settings_map(mut settings_map: Settings, key: &str, setting: &str) -> Settings {
    let key = MachineSettingKey::from(key);
    let setting = u64::from_str_radix(setting, 10).unwrap_or(0);
    settings_map.insert(key, setting);
    settings_map
}