///! FILE.TXT FORMAT Representing machine memory memory
/// - PC
/// - SP
/// - Section_1
/// - Section_2
/// - ...
/// - Section_n
/// 
/// Each section is divided in 3 parts, on two lines of text
/// addr SPACE len
/// content

use std::{fs, io::{BufRead, BufReader, Lines, Error}};
use crate::Machine;

/// File section
pub struct SectionFormat{
    /// Memory address of the section
    addr: String,
    /// The size of data in bytes
    len: String,
    /// The data itself in Hexadecimal format
    content: String,
}

/// # Memory section
/// 
/// Representation of a section of memory from BurritOS or NachOS
pub struct Section{
    /// Memory address of the section
    addr: usize,
    /// The size of data in bytes
    len: usize, 
    /// The data itself in Hexadecimal format
    content: Vec<u8>
}

impl Section {

    /// Creates a memory section from a SectionFormat
    fn from(section: &SectionFormat) -> Section {
        let addr = usize::from_str_radix(&section.addr, 10).unwrap_or_default();
        let len = usize::from_str_radix(&section.len, 10).unwrap_or_default();
        let content: Vec<u8> = section.content.as_bytes().chunks(2).map(|x| {
            u8::from_str_radix(std::str::from_utf8(x).unwrap_or_default(), 16).unwrap_or_default()
        }).collect();
        Section{addr, len, content}
    }

    /// Pretty prints a memory section
    fn print_section(s: &Section){
        println!("ADDR :: {:x}\nLEN :: {:x}\nCONTENT :: {:?}", s.addr, s.len, s.content);
    }
}


/// # Representation of the state of machine memory
/// 
/// Could represent memory at any point in time, before, during, or after execution.
/// The memory is split into sections.
pub struct MemChecker {
    /// Value of the program counter
    pc: usize,
    /// Value of the stack pointer
    sp: usize,
    /// Sections
    sections: Vec<Section>,
}


impl MemChecker{

    ///Translate lines of a file in e Vector of String
    ///We need this method to parse the memory we received
    ///
    /// ### Parameters
    ///
    ///  - **Lines** The file to parse
    ///
    /// ### Return
    ///  - A vector of String where each line of the file os an element of the vector
    fn vect_from_lines(lines: &mut Lines<BufReader<fs::File>>, pc: &mut usize, sp: &mut usize) -> Vec<String>{
        let mut vector = Vec::new();
        for (_,line) in lines.enumerate() {
            vector.push(line.unwrap());
        }
        let size = vector.len();
        *pc = usize::from_str_radix(vector.get(size - 2).expect("0"), 16).unwrap_or_default();
        *sp = usize::from_str_radix(vector.get(size - 1).expect("0"), 16).unwrap_or_default();
        vector
    }

    /// Fill a mem checker from a file (here the mock memory)
    /// Extract the values of pc, sp and sections
    ///
    /// ### Parameter
    /// -**path** addr to the file
    ///
    /// ### Return
    /// Mem-checker filled
    pub fn from(path: &str) -> Result<MemChecker, Error> {

        let file = fs::File::open(path)?;
        
        let reader = BufReader::new(file);
        let mut lines = reader.lines();
        
        let mut pc: usize = 0;
        let mut sp: usize = 0;
        let vector = MemChecker::vect_from_lines(&mut lines, &mut pc, &mut sp);
        
        let mut sections: Vec<Section> = Vec::new();
        let mut tmp_addr_str: String = String::new();
        let mut tmp_len_str: String = String::new();
        
        let default = String::new();
        for i in 0..vector.len()-2 {
            let current_line = vector.get(i).unwrap_or(&default);

            //Lecture des sections
            if i % 2 == 0 {
                //lecture ligne ADDR LEN
                let next_word_index = current_line.find(' ').unwrap();
                tmp_addr_str = String::from(&current_line[0..next_word_index]);
                tmp_len_str = String::from(&current_line[next_word_index+1..]);
            }
            else {
                //lecture ligne CONTENT
                let section_f = SectionFormat{
                    addr: tmp_addr_str.clone(),
                    len: tmp_len_str.clone(),
                    content: current_line.clone().replace(' ', ""),
                };
                sections.push(Section::from(&section_f));
            }

        }
        
        Ok(MemChecker{pc, sp, sections})
    }


    /// Print the content of a Mem_Checker
    ///
    /// ### Parameter
    ///
    /// - **m_c** Contains the data we want to print
    pub fn print_mem_checker(m_c: &MemChecker){
        println!("PC :: {:x}", m_c.pc);
        println!("SP :: {:x}", m_c.sp);

        for(i,s) in m_c.sections.iter().enumerate() {
            println!("\nSection {}\n", i);
            Section::print_section(s);
        }

    }

    /// Fill a machine's memory from a Mem Chacker
    ///
    /// ### Parameters
    ///
    /// - **m_c** contains the data
    /// - **machine** contains the memry to fill
    pub fn fill_memory_from_mem_checker(m_c: &MemChecker, machine: &mut Machine){
        
        machine.sp = m_c.sp;
        machine.int_reg.set_reg(2, m_c.sp as i64);
        machine.pc = m_c.pc as u64;

        for section in m_c.sections.iter() {
            for (i,b) in section.content.iter().enumerate() {
                machine.main_memory[section.addr + i] = *b;
            }
        }
    }


    /// For debug
    fn compare_print_m_c_machine(m_c: &MemChecker, machine: &mut Machine){
        MemChecker::print_mem_checker(m_c);
        for section in m_c.sections.iter() {
            print!("\n\n");
            println!("Content addr : {}", section.addr);
            println!("Content len (number of bytes) : {}", section.len);
            for i in 0..section.len {
                println!("mem[{}] = {}", section.addr + i, machine.main_memory[section.addr + i]);
            }
        }

    }

    /// Compare sections of a memChecker and a machine memory
    ///
    /// ### Parameters
    ///
    /// - **m_c** contains section of the memory checker
    /// - **machine** contains the main memory
    pub fn compare_machine_memory(m_c: &MemChecker, machine: &Machine) -> bool {
        m_c.sections.iter().map(|section| {
            (0..section.len).into_iter().all(|i| machine.main_memory[section.addr + i] == section.content[i])
        }).all(|e| e)
    }

}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    #[ignore]
    fn test_fill_memory(){
        let m_c = MemChecker::from("test/machine/memory.txt").unwrap();
        let mut machine = Machine::init_machine();
        MemChecker::fill_memory_from_mem_checker(&m_c, &mut machine);
        MemChecker::compare_print_m_c_machine(&m_c, &mut machine);
    }

    #[test]
    fn test_enum_start_at_zero(){
        let v = vec![1,2,3];

        for (i,val) in v.iter().enumerate() {
            println!("i = {} :: v[i]  = {}", i, val);
        }
    }

    #[test]
    fn test_create_mem_checker(){
        let m_c = MemChecker::from("test/machine/memory.txt").unwrap();
        MemChecker::print_mem_checker(&m_c);
    }

    #[test]
    fn test_create_section_content(){
        let section_format = SectionFormat{
            addr: "0".to_string(),
            len: "0".to_string(),
            content: "00FF0AA0A5".to_string(),
        };
        let section = Section::from(&section_format);
        let expected_vec:  Vec<u8> = vec![0u8, 255u8, 10u8, 160u8, 165u8];
        assert_eq!(section.content, expected_vec);
    }

}
