#[cfg(test)]
mod memory_builder;

#[cfg(test)]
pub use memory_builder::*;

use rust_decimal::Decimal;
use crate::alu::register::*;
use p101_is::{Instruction, JumpDestination};
use std::convert::TryInto;

const REG_LENGHT: usize = 24;
const REG_HALF: usize = 11;

#[derive(Debug, Clone, PartialEq)]
pub struct Memory {
    // La memoria è composta da 10 registri: 2 per il programma, 3 operativi e 5 numerici.
    // I 2 registri programma possono contenere sino a un massimo di 48 istruzioni. Essi non sono
    // rappresentati in tastiera.
    pub p1: [Option<Instruction>; REG_LENGHT],
    pub p2: [Option<Instruction>; REG_LENGHT],

    // I 3 registri operativi M, A ed R, hanno la capacità
    // di 22 cifre più virgola e segno algebrico

    // nel registro M sono contenuti i dati impostati 
    // in tastiera attraverso i tasti numerici
    pub m: Register,

    // nel registro A si formano i risultati delle operazioni
    // con il numero dei decimali predisposto
    pub a: Register,

    // nel registro R si formano i risultati dell'addizione, sottrazione, moltiplicazione
    // ed il resto della divisione, senza limitazione di decimali
    pub r: SplitRegister,

    // I 5 registri numerici B, C, D, E, F, hanno la capacità di 22 cifre più virgola e segno;
    // sono utilizzati per la memorizzazione di dati costanti e risultati intermedi di operazioni
    // aritmetiche eseguite nei registri operativi.
    // Ognuno di essi può essere diviso in due parti, ciascuna con capacita di 11 cifre più virgola e
    // segno. Le due parti di un registro così diviso funzionano in modo autonomo e vengono identificate:
    // -la parte di destra con la sua denominazione originale (esempio: B)
    // -la parte di sinistra con la corrispondente lettera minuscola (esempio: b).
    // Si può disporre quindi di 5 registri interi con capacità di 22 cifre (B, C, D, E, F)
    // oppure di 10 registri ridotti con capacità di 11 cifre (b, B, c, C, d, D, e, E, f, F). 
    // I registri F, E, D, invece di dati numerici possono contenere istruzioni di programma.
    // Quando le istruzioni superano la capacità dei due registri programma (48 istruzioni) 
    // esse occupano nell'ordine i registri F, E, D, ciascuno dei quali ha una capacità
    // massima di 24 istruzioni. 
    // Se l’ultimo registro occupato contiene 11 o meno istruzioni, la meta destra 
    // può contenere istruzioni e quella sinistra cifre.
    pub b: SplitRegister,
    pub c: SplitRegister,
    pub d: MemoryRegister,
    pub e: MemoryRegister,
    pub f: MemoryRegister,

    // Indica la precisione richiesta
    pub precision: u32,

    // The instruction pointer. Can have values between 1 and 120.
    pub ip: usize,
}

impl Memory {
    pub fn new() -> Memory {
        Memory {
            p1: [None; REG_LENGHT],
            p2: [None; REG_LENGHT],
            m: Register::new(),
            a: Register::new(),
            r: SplitRegister::new(),
            b: SplitRegister::new(),
            c: SplitRegister::new(),
            d: MemoryRegister::new(),
            e: MemoryRegister::new(),
            f: MemoryRegister::new(),
            precision: 0,
            ip: 0,
        }
    }

    /// Loads a program in memory. In the real machine, instructions are ancoded
    /// in a single byte. Instructions are laid out in the following order:
    /// Instruction 0-23    -> P1 register
    /// Instruction 24-47   -> P2 register
    /// Instruction 48-58   -> F  register
    /// Instruction 59-71   -> f  register
    /// Instruction 72-82   -> E  register
    /// Instruction 83-95   -> e  register
    /// Instruction 96-106  -> D  register
    /// Instruction 107-119- > d  register
    pub fn from_program(program: &Vec<Instruction>) -> Self {
        Memory {
            p1: Self::mem_slice(program, 0),
            p2: Self::mem_slice(program, REG_LENGHT),
            m: Register::new(),
            a: Register::new(),
            r: SplitRegister::new(),
            b: SplitRegister::new(),
            c: SplitRegister::new(),
            d: Self::mem_slice_to_reg(program, REG_LENGHT * 4),
            e: Self::mem_slice_to_reg(program, REG_LENGHT * 3),
            f: Self::mem_slice_to_reg(program, REG_LENGHT * 2),
            precision: 0,
            ip: 0,
        }
    }

    #[allow(clippy::explicit_counter_loop)]
    fn mem_slice(program: &Vec<Instruction>, offset: usize) -> [Option<Instruction>; REG_LENGHT] {
        let mut slice: [Option<Instruction>; REG_LENGHT] = [None; REG_LENGHT];
        let mut j: usize = 0;

        for i in offset .. offset + REG_LENGHT {
            slice[j] = program.get(i).copied();
            j += 1;
        }

        slice
    }

    fn mem_slice_to_reg(program: &Vec<Instruction>, offset: usize) -> MemoryRegister {
        let slice = Self::mem_slice(program, offset);
        let mut values = 0;

        for i in slice.iter() {
            if i.is_some() {
                values += 1;
            }
        }

        if values == 0 {
            MemoryRegister::new()
        } else if values <= REG_HALF {
            let mem: [Option<Instruction>; REG_HALF] = slice[0..REG_HALF].try_into().unwrap();
            (Decimal::new(0, 0), mem).into()
        } else if values <= REG_LENGHT {
            slice.into()
        } else {
            // The slide cannot be longer than a register.
            // A programming error occured. Halting the program
            // is the safest option.
            panic!("Slice exceed register length")
        }
    }

    /// Reads the instruction at the specified address.
    /// Panics if the memory address does not contains an instructions.
    pub fn read_instruction(&self, address: usize) -> Option<Instruction> {
        match address {
            0 ..= 23 => self.p1[address],
            24 ..= 47 => self.p2[address - 24],
            48 ..= 71 => self.f.read_instruction(address - 48).unwrap(),
            72 ..= 95 => self.e.read_instruction(address - 72).unwrap(),
            96 ..= 119 => self.d.read_instruction(address - 96).unwrap(),
            _ => panic!("Program counter out of bounds"),
        }
    }

    pub fn find_label(&self, label: JumpDestination) -> Option<usize> {
        for address in 0 ..= 119 {
            if let Some(Instruction::Label(l)) = self.read_instruction(address) {
                if l == label {
                    return Some(address);
                }
            }
        }

        None
    }
}

impl Default for Memory {
    fn default() -> Self {
        Self::new()
    }
}

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

    #[test]
    fn program_is_correctly_layd_out_in_memory() {
        let p = vec!(
            // To be loaded in P1
            Instruction::Abs,
            Instruction::Stop,
            Instruction::NewLine,
            Instruction::CaiStart,
            Instruction::DrExchange,
            Instruction::Abs,
            Instruction::Stop,
            Instruction::NewLine,
            Instruction::CaiStart,
            Instruction::DrExchange,
            Instruction::Abs,
            Instruction::Stop,
            Instruction::NewLine,
            Instruction::CaiStart,
            Instruction::DrExchange,
            Instruction::Abs,
            Instruction::Stop,
            Instruction::NewLine,
            Instruction::CaiStart,
            Instruction::DrExchange,
            Instruction::Abs,
            Instruction::Stop,
            Instruction::NewLine,
            Instruction::CaiStart,

            // To be loaded in P2
            Instruction::DrExchange,
            Instruction::Abs,
            Instruction::Stop,
            Instruction::NewLine,
            Instruction::CaiStart,
            Instruction::DrExchange,
            Instruction::Abs,
            Instruction::Stop,
            Instruction::NewLine,
            Instruction::CaiStart,
            Instruction::DrExchange,
            Instruction::Abs,
            Instruction::Stop,
            Instruction::NewLine,
            Instruction::CaiStart,
            Instruction::DrExchange,
            Instruction::Abs,
            Instruction::Stop,
            Instruction::NewLine,
            Instruction::CaiStart,
            Instruction::DrExchange,
            Instruction::Abs,
            Instruction::Stop,
            Instruction::NewLine,

            // To be loaded in F
            Instruction::CaiStart,
            Instruction::DrExchange,
            Instruction::Abs,
            Instruction::Stop,
            Instruction::NewLine,
            Instruction::CaiStart,
            Instruction::DrExchange,
            Instruction::Abs,
            Instruction::Stop,
            Instruction::NewLine,
            Instruction::CaiStart,
            Instruction::DrExchange,
            Instruction::Abs,
            Instruction::Stop,
            Instruction::NewLine,
            Instruction::CaiStart,
            Instruction::DrExchange,
            Instruction::Abs,
            Instruction::Stop,
            Instruction::NewLine,
            Instruction::CaiStart,
            Instruction::DrExchange,
            Instruction::Abs,
            Instruction::Stop,
  
            // To be loaded in E
            Instruction::NewLine,
            Instruction::CaiStart,
            Instruction::DrExchange,
            Instruction::Abs,
            Instruction::Stop,
            Instruction::NewLine,
            Instruction::CaiStart,
            Instruction::DrExchange,
            Instruction::Abs,
            Instruction::Stop,
            Instruction::NewLine,
            Instruction::CaiStart,
            Instruction::DrExchange,
            Instruction::Abs,
            Instruction::Stop,
            Instruction::NewLine,
            Instruction::CaiStart,
            Instruction::DrExchange,
            Instruction::Abs,
            Instruction::Stop,
            Instruction::NewLine,
            Instruction::CaiStart,
            Instruction::DrExchange,
            Instruction::Abs,
  
            // To be loaded in D
            Instruction::Stop,
            Instruction::CaiStart,
            Instruction::DrExchange,
            Instruction::Abs,
            Instruction::Stop,
            Instruction::NewLine,
            Instruction::CaiStart,
            Instruction::DrExchange,
            Instruction::Abs,
            Instruction::Stop,
            Instruction::NewLine,
            Instruction::CaiStart,
            Instruction::DrExchange,
            Instruction::Abs,
            Instruction::Stop,
            Instruction::NewLine,
            Instruction::CaiStart,
            Instruction::DrExchange,
            Instruction::Abs,
            Instruction::Stop,
            Instruction::NewLine,
            Instruction::CaiStart,
            Instruction::DrExchange,
            Instruction::Abs,
        );

        let memory = Memory::from_program(&p);

        // Test P1
        assert_eq!(Some(p[0]), memory.p1[0]);
        assert_eq!(Some(p[1]), memory.p1[1]);
        assert_eq!(Some(p[2]), memory.p1[2]);
        assert_eq!(Some(p[3]), memory.p1[3]);
        assert_eq!(Some(p[4]), memory.p1[4]);
        assert_eq!(Some(p[5]), memory.p1[5]);
        assert_eq!(Some(p[6]), memory.p1[6]);
        assert_eq!(Some(p[7]), memory.p1[7]);
        assert_eq!(Some(p[8]), memory.p1[8]);
        assert_eq!(Some(p[9]), memory.p1[9]);
        assert_eq!(Some(p[10]), memory.p1[10]);
        assert_eq!(Some(p[11]), memory.p1[11]);
        assert_eq!(Some(p[12]), memory.p1[12]);
        assert_eq!(Some(p[13]), memory.p1[13]);
        assert_eq!(Some(p[14]), memory.p1[14]);
        assert_eq!(Some(p[15]), memory.p1[15]);
        assert_eq!(Some(p[16]), memory.p1[16]);
        assert_eq!(Some(p[17]), memory.p1[17]);
        assert_eq!(Some(p[18]), memory.p1[18]);
        assert_eq!(Some(p[19]), memory.p1[19]);
        assert_eq!(Some(p[20]), memory.p1[20]);
        assert_eq!(Some(p[21]), memory.p1[21]);
        assert_eq!(Some(p[22]), memory.p1[22]);
        assert_eq!(Some(p[23]), memory.p1[23]);

        // Test P2
        assert_eq!(Some(p[24]), memory.p2[0]);
        assert_eq!(Some(p[25]), memory.p2[1]);
        assert_eq!(Some(p[26]), memory.p2[2]);
        assert_eq!(Some(p[27]), memory.p2[3]);
        assert_eq!(Some(p[28]), memory.p2[4]);
        assert_eq!(Some(p[29]), memory.p2[5]);
        assert_eq!(Some(p[30]), memory.p2[6]);
        assert_eq!(Some(p[31]), memory.p2[7]);
        assert_eq!(Some(p[32]), memory.p2[8]);
        assert_eq!(Some(p[33]), memory.p2[9]);
        assert_eq!(Some(p[34]), memory.p2[10]);
        assert_eq!(Some(p[35]), memory.p2[11]);
        assert_eq!(Some(p[36]), memory.p2[12]);
        assert_eq!(Some(p[37]), memory.p2[13]);
        assert_eq!(Some(p[38]), memory.p2[14]);
        assert_eq!(Some(p[39]), memory.p2[15]);
        assert_eq!(Some(p[40]), memory.p2[16]);
        assert_eq!(Some(p[41]), memory.p2[17]);
        assert_eq!(Some(p[42]), memory.p2[18]);
        assert_eq!(Some(p[43]), memory.p2[19]);
        assert_eq!(Some(p[44]), memory.p2[20]);
        assert_eq!(Some(p[45]), memory.p2[21]);
        assert_eq!(Some(p[46]), memory.p2[22]);
        assert_eq!(Some(p[47]), memory.p2[23]);

        // Test F
        assert_eq!(Some(p[48]), memory.f.read_instruction(0).unwrap());
        assert_eq!(Some(p[49]), memory.f.read_instruction(1).unwrap());
        assert_eq!(Some(p[50]), memory.f.read_instruction(2).unwrap());
        assert_eq!(Some(p[51]), memory.f.read_instruction(3).unwrap());
        assert_eq!(Some(p[52]), memory.f.read_instruction(4).unwrap());
        assert_eq!(Some(p[53]), memory.f.read_instruction(5).unwrap());
        assert_eq!(Some(p[54]), memory.f.read_instruction(6).unwrap());
        assert_eq!(Some(p[55]), memory.f.read_instruction(7).unwrap());
        assert_eq!(Some(p[56]), memory.f.read_instruction(8).unwrap());
        assert_eq!(Some(p[57]), memory.f.read_instruction(9).unwrap());
        assert_eq!(Some(p[58]), memory.f.read_instruction(10).unwrap());
        assert_eq!(Some(p[59]), memory.f.read_instruction(11).unwrap());
        assert_eq!(Some(p[60]), memory.f.read_instruction(12).unwrap());
        assert_eq!(Some(p[61]), memory.f.read_instruction(13).unwrap());
        assert_eq!(Some(p[62]), memory.f.read_instruction(14).unwrap());
        assert_eq!(Some(p[63]), memory.f.read_instruction(15).unwrap());
        assert_eq!(Some(p[64]), memory.f.read_instruction(16).unwrap());
        assert_eq!(Some(p[65]), memory.f.read_instruction(17).unwrap());
        assert_eq!(Some(p[66]), memory.f.read_instruction(18).unwrap());
        assert_eq!(Some(p[67]), memory.f.read_instruction(19).unwrap());
        assert_eq!(Some(p[68]), memory.f.read_instruction(20).unwrap());
        assert_eq!(Some(p[69]), memory.f.read_instruction(21).unwrap());
        assert_eq!(Some(p[70]), memory.f.read_instruction(22).unwrap());
        assert_eq!(Some(p[71]), memory.f.read_instruction(23).unwrap());

        // Test E
        assert_eq!(Some(p[72]), memory.e.read_instruction(0).unwrap());
        assert_eq!(Some(p[73]), memory.e.read_instruction(1).unwrap());
        assert_eq!(Some(p[74]), memory.e.read_instruction(2).unwrap());
        assert_eq!(Some(p[75]), memory.e.read_instruction(3).unwrap());
        assert_eq!(Some(p[76]), memory.e.read_instruction(4).unwrap());
        assert_eq!(Some(p[77]), memory.e.read_instruction(5).unwrap());
        assert_eq!(Some(p[78]), memory.e.read_instruction(6).unwrap());
        assert_eq!(Some(p[79]), memory.e.read_instruction(7).unwrap());
        assert_eq!(Some(p[80]), memory.e.read_instruction(8).unwrap());
        assert_eq!(Some(p[81]), memory.e.read_instruction(9).unwrap());
        assert_eq!(Some(p[82]), memory.e.read_instruction(10).unwrap());
        assert_eq!(Some(p[83]), memory.e.read_instruction(11).unwrap());
        assert_eq!(Some(p[84]), memory.e.read_instruction(12).unwrap());
        assert_eq!(Some(p[85]), memory.e.read_instruction(13).unwrap());
        assert_eq!(Some(p[86]), memory.e.read_instruction(14).unwrap());
        assert_eq!(Some(p[87]), memory.e.read_instruction(15).unwrap());
        assert_eq!(Some(p[88]), memory.e.read_instruction(16).unwrap());
        assert_eq!(Some(p[89]), memory.e.read_instruction(17).unwrap());
        assert_eq!(Some(p[90]), memory.e.read_instruction(18).unwrap());
        assert_eq!(Some(p[91]), memory.e.read_instruction(19).unwrap());
        assert_eq!(Some(p[92]), memory.e.read_instruction(20).unwrap());
        assert_eq!(Some(p[93]), memory.e.read_instruction(21).unwrap());
        assert_eq!(Some(p[94]), memory.e.read_instruction(22).unwrap());
        assert_eq!(Some(p[95]), memory.e.read_instruction(23).unwrap());

        // Test D
        assert_eq!(Some(p[96]), memory.d.read_instruction(0).unwrap());
        assert_eq!(Some(p[97]), memory.d.read_instruction(1).unwrap());
        assert_eq!(Some(p[98]), memory.d.read_instruction(2).unwrap());
        assert_eq!(Some(p[99]), memory.d.read_instruction(3).unwrap());
        assert_eq!(Some(p[100]), memory.d.read_instruction(4).unwrap());
        assert_eq!(Some(p[101]), memory.d.read_instruction(5).unwrap());
        assert_eq!(Some(p[102]), memory.d.read_instruction(6).unwrap());
        assert_eq!(Some(p[103]), memory.d.read_instruction(7).unwrap());
        assert_eq!(Some(p[104]), memory.d.read_instruction(8).unwrap());
        assert_eq!(Some(p[105]), memory.d.read_instruction(9).unwrap());
        assert_eq!(Some(p[106]), memory.d.read_instruction(10).unwrap());
        assert_eq!(Some(p[107]), memory.d.read_instruction(11).unwrap());
        assert_eq!(Some(p[108]), memory.d.read_instruction(12).unwrap());
        assert_eq!(Some(p[109]), memory.d.read_instruction(13).unwrap());
        assert_eq!(Some(p[110]), memory.d.read_instruction(14).unwrap());
        assert_eq!(Some(p[111]), memory.d.read_instruction(15).unwrap());
        assert_eq!(Some(p[112]), memory.d.read_instruction(16).unwrap());
        assert_eq!(Some(p[113]), memory.d.read_instruction(17).unwrap());
        assert_eq!(Some(p[114]), memory.d.read_instruction(18).unwrap());
        assert_eq!(Some(p[115]), memory.d.read_instruction(19).unwrap());
        assert_eq!(Some(p[116]), memory.d.read_instruction(20).unwrap());
        assert_eq!(Some(p[117]), memory.d.read_instruction(21).unwrap());
        assert_eq!(Some(p[118]), memory.d.read_instruction(22).unwrap());
        assert_eq!(Some(p[119]), memory.d.read_instruction(23).unwrap());
    }
}
