use crate::printer::PrinterProvider;
use crate::message::{Operator, PrintPayload};
use p101_is::{Instruction, Register};
use p101_enc::encoding::{Encoding, StandardEncoding};
use rust_decimal::Decimal;

pub struct CliPrinter { }

impl CliPrinter {
    pub fn new() -> Self {
        Self {}
    }

    fn format_value(value: Decimal, register: Register, operator: Operator) -> String {
        let mut output = String::from("");

        output.push_str(&value.to_string());
        output.push(' ');

        output.push_str(&register.to_string());

        match operator {
            Operator::Add => output.push('+'),
            Operator::Sub => output.push('-'),
            Operator::Mul => output.push('x'),
            Operator::Div => output.push(':'),
            Operator::Sqr => output.push('√'),
            Operator::CopyM => output.push('↑'),
            Operator::CopyToA => output.push('↓'),
            Operator::SwapA => output.push('↕'),
            Operator::Print => output.push('\u{25C6}'),
            Operator::Reset => todo!()
        }

        output
    }

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

    fn print_line(text: &str) {
        println!("PRN: |{}|", text);
    }
}

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

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

    fn print_value(&mut self, _value: Decimal) {
        // Do nothing
    }

    /// Prints the register in the register column.
    fn print_register(&mut self, _register: Register) {
        // Do nothing
    }

    fn print(&mut self, payload: PrintPayload) {
        match payload { PrintPayload::Value { value, register, operator } => {
                let text = Self::format_value(value, register, operator);
                Self::print_line(&text);
            },
            PrintPayload::Instruction { address, instruction } => {
                let text = Self::format_instruction(address, instruction);
                Self::print_line(&text);
            }
        }
    }

    /// Advance the "paper" to a new line.
    fn newline(&mut self) {
        Self::print_line("");
    }
}
