use std::fs;
use std::io::BufRead;
use std::io::BufReader;
use std::io::Lines;
use crate::Machine;

const MEM_SIZE : usize = 4096;


/* TRUCS MANQUANTS
*   Verifier qu'il y a un nombre pair de caractere hexa dans la ligne correspondante d'une section du fichier source
*   Sinon on ne peut pas automatiquement remplir chaque octect car 2 hexa = 1 octet
*/

/* FORMAT FICHIER.TXT Représentant la mémoire apres éxecution d'un prog
*   PC
*   SP
*   Section_1
*   Section_2
*   ...
*   ...
*   Section_n
*/

/* Chaque section se divise en 3 parties, sur 2 lignes de texte
*  addr ESPACE len
*  content
*/

//content est une suite hexadécimale

//Section dans le fichier, champ String car informations proviennent d'un fichier txt
pub struct SectionFormat{
    addr: String,
    len: String,
    content: String,
}

//Section dans le programme
pub struct Section{
    addr: usize, // adresse dans la mémoire
    len: usize, // nombre d'octets de la donnée à addr
    content: Vec<u8>, // la donnée en question
}

/*
* Voir si instanciation d'une structure deplace les valeurs "locales" à la méthode from, je sais plus ....
 */
impl Section{

    fn from(section: &SectionFormat) -> Section {

        let mut content: Vec<u8> = Vec::new();
        let addr: usize = string_hex_to_usize(&section.addr);
        let len: usize = string_hex_to_usize(&section.len);

        let mut tmp_a: char = ' ';

        for (i, c) in section.content.chars().enumerate(){

            if i%2 == 0 {
                tmp_a = c;
            }
            else {
                content.push(two_hex_to_u8(tmp_a,c));
            }
        }

        Section{addr, len, content}
    }


    fn print_section(s: &Section){
        println!("ADDR :: {:x}", s.addr);
        println!("LEN :: {:x}", s.len);
        println!("CONTENT :: {:?}", s.content);
    }
}

/*
* Representation de l'etat de la mémoire (apres execution.... a  confirmer), sous forme de sections
*/
pub struct MemChecker{
    pc: usize,
    sp: usize,
    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 = string_hex_to_usize(vector.get(size - 2).expect("0"));
        *sp = string_hex_to_usize(vector.get(size - 1).expect("0"));
        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: &String) -> MemChecker {

        let file = fs::File::open(path).expect("Wrong filename");
        
        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));
            }

        }
        
        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)
    }

}



fn string_hex_to_usize(s: &String) -> usize {

    if s.is_empty() {
        return 0;
    }

    let max_pow = (s.len()-1) as u32;
    let mut ret_value: usize = 0;
    let base: usize = 16;

    for (i,c )in s.chars().enumerate(){
        //println!("Current char :: {} :: Current pow :: {} ::", c, max_pow - (i as u32));
        let tmp: usize = one_hex_to_dec(c) as usize;
        ret_value += base.pow(max_pow - (i as u32))*tmp;
    }

    ret_value
}



/*
* c doit etre un caractère hexadécimale
 */
fn one_hex_to_dec(c: char) -> u8 {

    match c {
        'A' | 'a' => 10,
        'B' | 'b' => 11,
        'C' | 'c' => 12,
        'D' | 'd' => 13,
        'E' | 'e' => 14,
        'F' | 'f' => 15,
        _ => {
            c.to_digit(10).unwrap() as u8 
        },
    }
}



fn two_hex_to_u8(c1: char, c2: char) -> u8 {
    let a = one_hex_to_dec(c1);
    let b = one_hex_to_dec(c2);

    16*a + b
}



/*
* Juste pour voir si via BufReader les \n sont présent, apres test il s'avère que non
* De toute facon on limitera d'une section la lecture par len
 */
fn test_show_sections_file(){
    let file = fs::File::open("test_file_section.txt").expect("Wrong filename");
    let reader = BufReader::new(file);

    for line in reader.lines() {
        //println!("Tailles de la ligne : {}",
        let current = line.unwrap();
        //println!("Taille de la ligne : {}", current.len()); // En effet pas de \n dans chaque line, parfait
        println!("{}", &current);
    }

}




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

    #[test]
    fn test_fill_memory(){
        let path = "osef".to_string();
        let m_c = MemChecker::from(&path);
        let mut machine = Machine::_init_machine();

        MemChecker::fill_memory_from_mem_checker(&m_c, &mut machine);

        print!("\n Comparing memory from loaded context\n\n");

        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 path: String = "osef".to_string();
        let m_c = MemChecker::from(&path);
        MemChecker::print_mem_checker(&m_c);
    }


    #[test]
    fn test_string_hex_to_usize(){
        let s = String::from("AE1F20");
        //println!("taille de string : {}", s.len());
        let expected: usize = 11411232;
        let result = string_hex_to_usize(&s);

        assert_eq!(expected,result);
    }

    #[test]
    fn tmp_fct_read_file(){
        println!("Reading A file \n");
        test_show_sections_file();
    }

    #[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];

        //println!("Vec from created section {:?}", &section.content);
        //println!("Expected vec {:?}", &expected_vec);

        assert_eq!(section.content, expected_vec);
    }

    #[test]
    fn test_hex_1(){
        let b = two_hex_to_u8('0', '0');
        assert_eq!(0u8, b);
    }

    #[test]
    fn test_hex_2(){
        let b = two_hex_to_u8('F', 'F');
        assert_eq!(255u8, b);
    }

    #[test]
    fn test_hex_3(){
        let b = two_hex_to_u8('0', 'A');
        assert_eq!(10u8, b);
    }

    #[test]
    fn test_hex_4(){
        let b = two_hex_to_u8('A', '0');
        assert_eq!(160u8, b);
    }

    #[test]
    fn test_hex_5(){
        let b = two_hex_to_u8('A', '5');
        assert_eq!(165u8, b);
    }
}
