use crate::message::*;
use crate::printer::PrinterProvider;
use p101_is::{Instruction, Register};
use p101_enc::encoding::{Encoding, StandardEncoding};
use std::sync::RwLock;
use std::sync::Arc;

/// The test printer features a log which keeps all wrote text.
/// It is intended for test purpose only.
pub struct TestPrinter {
    logs: Arc<RwLock<Vec<String>>>
}

impl TestPrinter{
    /// Creates a new test printer logging data
    /// to a self-created string vector.
    pub fn new() -> (Self, Arc<RwLock<Vec<String>>>) {
        let logs = Arc::new(RwLock::new(Vec::new()));

        let printer = Self {
            logs: logs.clone()
        };

        (printer, logs)
    }

    /// Creates a new test printer logging data
    /// to the provided string vector.
    pub fn new_with_logs(logs: Arc<RwLock<Vec<String>>>) -> Self {
        Self {
            logs
        }
    }

    fn format_value(&mut self, value: rust_decimal::Decimal, register: Register, operator: Operator) -> String {
        let register_name = match register {
            Register::M => "".to_string(),
            _ => register.to_string()
        };

        let operator_symbol = match operator {
            Operator::Add => "+",
            Operator::Sub => "-",
            Operator::Mul => "x",
            Operator::Div => ":",
            Operator::Sqr => "√",
            Operator::CopyM => "⬆",
            Operator::CopyToA => "⬇",
            Operator::SwapA => "⬍",
            Operator::Print => "⯁",
            Operator::Reset => ""
        };

        format!("{} {} {}", 
            value.to_string(),
            register_name,
            operator_symbol)
    }

    fn format_instruction(&mut self, address: Option<usize>, instruction: p101_is::Instruction) -> String {
        let addr = address.map_or("----".to_string(), |v| format!("{:04}", v));
        let code = StandardEncoding::new().encode(&instruction);
        format!("{} {}", addr, code)
    }
}

impl PrinterProvider for TestPrinter{
    fn get_name(&self) -> &str {
        "TestPrinter"
    }

    /// Log the payload of a print value message.
    fn print_value(&mut self, value: rust_decimal::Decimal) {
        let mut logs = self.logs.write().unwrap();
        logs.push(value.to_string());
    }

    /// Log the payload of a print register  message.
    fn print_register(&mut self, register: Register) {
        let mut logs = self.logs.write().unwrap();
        logs.push(register.to_string());
    }

    /// Log the payload of a print operator message.
    fn print(&mut self, payload: PrintPayload) {
        let log = match payload {
            PrintPayload::Value {
                value,
                register,
                operator
            } => self.format_value(value, register, operator),
            PrintPayload::Instruction {
                address,
                instruction
            } => self.format_instruction(address, instruction)
        };

        let mut logs = self.logs.write().unwrap();
        logs.push(log);
    }

    /// Advance the "paper" to a new line.
    fn newline(&mut self) {
        let mut logs = self.logs.write().unwrap();
        logs.push("".to_string());
    }
}


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

    #[test]
    pub fn log_everything_that_gets_printed() {
        let value = rust_decimal::Decimal::new(12345, 2);
        let (mut printer, log) = TestPrinter::new();

        printer.print_value(value);
        printer.print_register(Register::A);
        printer.print(PrintPayload::Value {
            value,
            register: Register::A,
            operator: Operator::Add
        });
        printer.print(PrintPayload::Instruction {
            address: Some(12usize),
            instruction: Instruction::Add(Register::F)
        });
        printer.newline();

        let log = log.read().unwrap();
        assert_eq!(5, log.len());
        assert_eq!("123.45", log[0]);
        assert_eq!("A", log[1]);
        assert_eq!("123.45 A +", log[2]);
        assert_eq!("0012 F+", log[3]);
        assert_eq!("", log[4]);
    }
}

