use crate::message::*;
use crate::alu::{Memory, RegisterError, AluProvider, Digit};
use p101_is::*;
use std::cmp::Ordering;
use std::sync::mpsc;
use rust_decimal::{Decimal, MathematicalOps};
use rust_decimal::prelude::*;

#[derive(Debug)]
pub struct DefaultAlu {
    memory: Memory,
    system: mpsc::Sender<Message>,
    decimals: Option<u8>
}

impl DefaultAlu {
    const PROVIDER_NAME: &'static str = "DefaultAlu";

    pub fn new(system: mpsc::Sender<Message>) -> Self {
        Self {
            memory: Memory::new(),
            system,
            decimals: None
        }
    }

    fn post(&self, message: Message) -> Result<(), String> {
        match self.system.send(message) {
            Ok(_) => Ok(()),
            Err(e) => panic!("Cannot post on system bus: {}", e)
        }
    }

    // ========= Registers management =========

    /// Returns the numeric value of a register.
    /// It returns a Decimal which is large enough
    /// to represent both registers and splits.
    fn read_register(&mut self, reg: Register) -> Decimal {
        match reg {
            Register::A => self.memory.a.read(),
            Register::B => self.memory.b.read(),
            Register::b => self.memory.b.read_left(),
            Register::C => self.memory.c.read(),
            Register::c => self.memory.c.read_left(),
            Register::D => self.memory.d.read(),
            Register::d => self.memory.d.read_left(),
            Register::E => self.memory.e.read(),
            Register::e => self.memory.e.read_left(),
            Register::F => self.memory.f.read(),
            Register::f => self.memory.f.read_left(),
            Register::M => self.memory.m.read(),
            Register::R => self.memory.r.read()
        }
    }

    /// Writes the value to the register. Depending on write result,
    /// there are some cases to manage.
    /// - If the write is successful, a RegisterUpdated debug event
    ///   is emitted. Some component may be interested in this event
    ///   to update a representation of ALU registers, for instance.
    /// - If the register overflows, a MessageError is posted on the
    ///   system bus. System components must consume this event to
    ///   properly update computer status and user interface.
    /// - If the register cannot be split because it already contains
    ///   data, probably the computer has not been properly operated.
    ///   A MessageError is posted on the system bus.
    /// - The NoMemory result signifies that the emulator tried to
    ///   write an instruction to an address outside of the 24 bytes.
    ///   Basically it signifies a programming error in the emulator
    ///   so it simply panics.
    fn write_register(&mut self, reg: Register, v: Decimal) -> Result<(), RegisterError> {
        let result = match reg {
            Register::A => self.memory.a.write(v),
            Register::B => self.memory.b.write(v),
            Register::b => self.memory.b.write_left(v),
            Register::C => self.memory.c.write(v),
            Register::c => self.memory.c.write_left(v),
            Register::D => self.memory.d.write(v),
            Register::d => self.memory.d.write_left(v),
            Register::E => self.memory.e.write(v),
            Register::e => self.memory.e.write_left(v),
            Register::F => self.memory.f.write(v),
            Register::f => self.memory.f.write_left(v),
            Register::M => self.memory.m.write(v),
            Register::R => self.memory.r.write(v)
        };

        match result {
            Ok(_) => {
                self.post(Message::Debug(DebugEvent::RegisterUpdated(reg, v))).unwrap();
                Ok(())
            },
            Err(RegisterError::Overflow) => {
                self.post(Message::Error).unwrap();
                result
            },
            Err(RegisterError::CannotSplit) => {
                self.post(Message::Error).unwrap();
                result
            },
            Err(RegisterError::InvalidAddress) => {
                panic!("Write failed to register {}", reg);
            }
        }
    }

    /// Returs the register's name.
    fn register_name(&self, reg: Register) -> &str {
        match reg {
            Register::A => "A",
            Register::B => "B",
            Register::b => "b",
            Register::C => "C",
            Register::c => "c",
            Register::D => "D",
            Register::d => "d",
            Register::E => "E",
            Register::e => "e",
            Register::F => "F",
            Register::f => "f",
            Register::M => "M",
            Register::R => "R"
        }
    }

    /// Fetch the operand into register M and returns its value.
    fn fetch_operand(&mut self, operand: Register) -> Decimal {
        if operand == Register::M {
            self.memory.m.read()
        } else {
            let value = self.read_register(operand);
            self.memory.m.write(value).unwrap();
            value
        }
    }

    fn set_precision(&mut self, precision: u32) {
        self.memory.precision = precision;
    }

    fn round_off(&self, value: Decimal) -> Decimal {
        match value.scale().cmp(&self.memory.precision) {
            // No rouding required if value has less decimals
            // than the required precision
            Ordering::Less => {
                let scale_factor = Decimal::new(10_i64.pow(self.memory.precision), 0);
                let v = (value * scale_factor).to_i64();
                Decimal::new(v.unwrap(), self.memory.precision)
            },
            Ordering::Equal => value,
            Ordering::Greater => {
                // Split value into integer and decimal parts
                let i = value.trunc();
                let mut f = value.fract();

                // Truncate decimals to required precision
                let scale_factor = Decimal::new(10_i64.pow(self.memory.precision), 0);
                f *= scale_factor;
                let mut f = f.trunc();
                f /= scale_factor;

                i + f
            }
        }
    }
    
    fn cai(&mut self) -> Result<(), String> {
        let mut value = 0;
        let mut scale = 0;
        let mut exp = 0;
        let mut sign_factor = 0;

        while let Some(Instruction::Cai(sign, order, digit, comma)) = self.read_instruction(self.memory.ip) {
            value += match exp {
                0 => i64::from(digit),
                exp => 10_i64.pow(exp) * i64::from(digit),
            };

            if comma == CaiComma::Yes {
                if scale == 0 {
                    scale = exp;
                } else {
                    panic!("Illegal CAI instruction at IP {}: cannot set comma twice", self.memory.ip);
                }
            }

            if sign_factor == 0 {
                sign_factor = match sign {
                    CaiSign::Positive => 1,
                    CaiSign::Negative => -1,
                };
            } else if sign_factor > 0 && sign == CaiSign::Negative {
                panic!("Illegal CAI instruction at IP {}: positive sign already set", self.memory.ip);
            } else if sign_factor < 0 && sign == CaiSign::Positive {
                panic!("Illegal CAI instruction at IP {}: negative sign already set", self.memory.ip);
            }

            exp += 1;
            self.memory.ip += 1;

            if order == CaiOrder::High {
                break;
            }
        }

        let m = Decimal::new(value * sign_factor, scale);
        self.write_register(Register::M, m)?;

        Ok(())
    }

    fn jump_to(&mut self, destination: JumpDestination) -> Result<(), String> {
        for address in 0 ..= 119 {
            if let Some(Instruction::Label(l)) = self.read_instruction(address) {
                if l == destination {
                    self.memory.ip = address;
                    return Ok(());
                }
            }
        }

        Err(format!("Jump destination {} not found", destination))
    }

    // ========= Control instructions =========

    pub fn stop(&mut self) -> Result<(), String> {
        self.post(Message::ProgramStopped)
    }
}


impl AluProvider for DefaultAlu {
    fn get_provider_name(&self) -> &'static str {
        Self::PROVIDER_NAME
    }

    // ========= Memory management =========

    fn update_memory(&mut self, program: Vec<Instruction>) -> Result<(), String> {
        let memory = Memory::from_program(&program);
        self.memory = memory;
        Ok(())
    }

    /// Reads the instruction at the specified address.
    /// The address parameter is the offset from memory location 0.
    /// On the actual machine addresses are 1-based.
    fn read_instruction(&self, address: usize) -> Option<Instruction> {
        self.memory.read_instruction(address)
    }

    fn read_ip(&mut self) -> usize {
        self.memory.ip
    }

    fn increment_ip(&mut self, step: usize) -> Result<(), String> {
        self.memory.ip += step;
        Ok(())
    }


    // ======== Register instructions =========

    fn copy_decimal(&mut self) -> Result<(), String> {
        let value = self.read_register(Register::A).fract();

        self.write_register(Register::M, value)?;
        self.memory.ip += 1;

        Ok(())
    }

    fn copy_to_a(&mut self, operand: Register) -> Result<(), String> {
        let value = self.read_register(operand);

        self.write_register(Register::A, value)?;
        self.post(Message::Print(PrintPayload::Value {
            value: value,
            register: operand,
            operator: Operator::CopyToA
        }))?;
        self.memory.ip += 1;

        Ok(())
    }
    
    fn copy_m(&mut self, operand: Register) -> Result<(), String> {
        match operand {
            Register::A => {
                self.memory.ip += 1;
                self.cai()
            },
            Register::M => {
                // Inoperative
                self.memory.ip += 1;
                Ok(())
            }, 
            Register::R => {
                // Inoperative
                self.memory.ip += 1;
                Ok(())
            },
            _=> {
                let value = self.memory.m.read();

                self.write_register(operand, value)?;
                self.post(Message::Print(PrintPayload::Value {
                    value: value,
                    register: operand,
                    operator: Operator::CopyM
                }))?;
                self.memory.ip += 1;

                Ok(())
            }
        }
    }
    
    fn print(&mut self, operand: Register) -> Result<(), String> {
        let value = match operand {
            Register::A => self.memory.a.read(),
            Register::B => self.memory.b.read(),
            Register::b => self.memory.b.read_left(),
            Register::C => self.memory.c.read(),
            Register::c => self.memory.c.read_left(),
            Register::D => self.memory.d.read(),
            Register::d => self.memory.d.read_left(),
            Register::E => self.memory.e.read(),
            Register::e => self.memory.e.read_left(),
            Register::F => self.memory.f.read(),
            Register::f => self.memory.f.read_left(),
            Register::M => self.memory.m.read(),
            Register::R => self.memory.r.read(),
        };

        self.post(Message::Print(PrintPayload::Value {
            value: value,
            register: operand,
            operator: Operator::Print
        }))
    }

    fn newline(&mut self) -> Result<(), String> {
        match self.post(Message::NewLine) {
            Ok(()) => {
                self.memory.ip += 1;
                Ok(())
            },
            Err(e) => Err(e)
        }
    }

    fn reset(&mut self, operand: Register) -> Result<(), String> {
        let value = self.read_register(operand);
        let zero = Decimal::new(0, 0);

        if operand != Register::M {
            self.write_register(operand, zero)?;
        }

        self.post(Message::Print(PrintPayload::Value {
            value: value,
            register: operand,
            operator: Operator::Reset
        }))
    }

    // TODO: Need to post RegisterUpdated messages?
    fn swap_a(&mut self, operand: Register) -> Result<(), String> {
        match operand {
            Register::A => Ok(()),
            Register::R => panic!("Illegal instruction: swap a and r"),
            _ => {
                let a = self.read_register(Register::A);
                let operand_value = self.read_register(operand);

                self.write_register(Register::A, operand_value)?;
                self.write_register(operand, a)?;

                // TODO: Actually the P101 does not print a value
                // for the swap operation. Define the value as
                // optional and pass None.
                self.post(Message::Print(PrintPayload::Value {
                    value: 0.into(),
                    register: operand,
                    operator: Operator::SwapA
                }))
            }
        }
    }
    
    /// Exchange value of D and R registers.
    /// As D is a memory register (it can contain a long data, be split or holding instructions),
    /// R must conform too. Instructions are not copied from D to R
    fn dr_exchange(&mut self) -> Result<(), String> {
        self.memory.r = self.memory.d.exchange(self.memory.r);
        Ok(())
    }

    fn clear_entry(&mut self) -> Result<(), String> {
        let zero = Decimal::new(0, 0);
        self.write_register(Register::M, zero)?;
        Ok(())
    }

    fn clear_m(&mut self) -> Result<(), String> {
        let zero = Decimal::new(0, 0);

        self.memory.m.write(zero)
            .map_err(|e| e.to_string())
    }

    fn write_to_m(&mut self, value: Digit) -> Result<(), String> {
        let mut m = self.memory.m.read();

        if let Some(mut decimals) = self.decimals {
            decimals += 1;
            self.decimals = Some(decimals);
            m += Decimal::new(value.into(), decimals.into());
        } else {
            m *= Decimal::new(10, 0);
            m += Decimal::new(value.into(), 0);
        }

        self.write_register(Register::M, m)?;
        Ok(())
    }

    fn entering_decimals(&mut self, value: bool) {
        self.decimals = if value {
            Some(0)
        } else {
            None
        };
    }

    fn invert_sign(&mut self) {
        let m = self.memory.m.read() * Decimal::new(-1, 0);
        self.memory.m.write(m).unwrap();

        self.post(Message::NewLine).unwrap();
        self.post(Message::Debug(DebugEvent::RegisterUpdated(Register::M, m))).unwrap();
    }

    // ======== Math instructions =========

    fn add(&mut self, operand: Register) -> Result<(), String> {
        let value = self.fetch_operand(operand);
        let result = self.memory.a.read().checked_add(value).unwrap();
        let a = self.round_off(result);

        self.write_register(Register::R, result)
            .and_then(|()| self.write_register(Register::A, a))
            .map_err(|e| e.to_string())

        //TODO: Evaluate if a debug message to propagate add operator could be useful.
    }

    fn subtract(&mut self, operand: Register) -> Result<(), String> {
        let value = self.fetch_operand(operand);
        let result = self.memory.a.read().checked_sub(value).unwrap();
        let a = self.round_off(result);

        self.write_register(Register::R, result)
            .and_then(|()| self.write_register(Register::A, a))
            .map_err(|e| e.to_string())

        //TODO: Evaluate if a debug message to propagate substract operator could be useful.
    }

    fn multiply(&mut self, operand: Register) -> Result<(), String> {
        let value = self.fetch_operand(operand);
        let result = self.memory.a.read().checked_mul(value).unwrap();
        let a = self.round_off(result);

        self.write_register(Register::R, result)?;
        self.write_register(Register::A, a)?;

        self.post(Message::Print(PrintPayload::Value {
            value: value,
            register: operand,
            operator: Operator::Mul
        }))?;

        self.post(Message::Print(PrintPayload::Value {
            value: a,
            register: Register::A,
            operator: Operator::Print
        }))
    }

    fn divide(&mut self, operand: Register) -> Result<(), String> {
        let v = self.fetch_operand(operand);

        if v == Decimal::new(0, 0) {
            let _ = self.system.send(Message::Error);
        }
        
        let q = self.memory.a.read().checked_div(v);

        if q.is_none() {
            return Err(format!("{} / {} overflowed", self.memory.a.read(), v));
        }

        let q = self.round_off(q.unwrap());

        let r = self.memory.a.read() - q * v;

        self.write_register(Register::R, r)?;
        self.write_register(Register::A, q)?;

        self.post(Message::Print(PrintPayload::Value {
            value: v,
            register: operand,
            operator: Operator::Div
        }))?;

        self.post(Message::Print(PrintPayload::Value {
            value: q,
            register: Register::A,
            operator: Operator::Print
        }))
    }

    fn sqr(&mut self, operand: Register) -> Result<(), String> {
        let value = self.fetch_operand(operand);

        if let Some(result) = value.sqrt() {
            let a = self.round_off(result);
            self.write_register(Register::A, a)?;
            self.post(Message::Print(PrintPayload::Value {
                value: a,
                register: operand,
                operator: Operator::Sqr
            }))
        } else {
            Err(format!("Cannot calculate sqr({})", value))
        }
    }

    fn abs(&mut self) -> Result<(), String> {
        let v = self.memory.a.read();

        if v < Decimal::new(0, 0) {
            // TODO: can abs produce an overflow or is it safe to ignore the error?
            if let Err(e) = self.memory.a.write(-v) {
                return Err(format!("Error calculating absolute of {}: {}", v, e));
            }
        }

        Ok(())
    }

    // ========= Jump instructions =========

    fn label(&mut self) -> Result<(), String> {
        // NOTE: Not sure if running a label is an error 
        // or the instruction is simply skipped.
        Ok(())
    }

    fn jump(&mut self, origin: Origin) -> Result<(), String> {
        let destination = match origin {
            Origin::V => JumpDestination::AV,
            Origin::W => JumpDestination::AW,
            Origin::Y => JumpDestination::AY,
            Origin::Z => JumpDestination::AZ,
            Origin::CV => JumpDestination::BV,
            Origin::CW => JumpDestination::BW,
            Origin::CY => JumpDestination::BY,
            Origin::CZ => JumpDestination::BZ,
            Origin::DV => JumpDestination::EV,
            Origin::DW => JumpDestination::EW,
            Origin::DY => JumpDestination::EY,
            Origin::DZ => JumpDestination::EZ,
            Origin::RV => JumpDestination::FV,
            Origin::RW => JumpDestination::FW,
            Origin::RY => JumpDestination::FY,
            Origin::RZ => JumpDestination::FZ,
        };

        self.jump_to(destination)
    }

    fn conditional_jump(&mut self, origin: ConditionalOrigin) -> Result<(), String> {
        if self.read_register(Register::A) > 0.into() {
            let destination = match origin {
                ConditionalOrigin::_V => JumpDestination::aV,
                ConditionalOrigin::_W => JumpDestination::aW,
                ConditionalOrigin::_Y => JumpDestination::aY,
                ConditionalOrigin::_Z => JumpDestination::aZ,
                ConditionalOrigin::cV => JumpDestination::bV,
                ConditionalOrigin::cW => JumpDestination::bW,
                ConditionalOrigin::cY => JumpDestination::bY,
                ConditionalOrigin::cZ => JumpDestination::bZ,
                ConditionalOrigin::dV => JumpDestination::eV,
                ConditionalOrigin::dW => JumpDestination::eW,
                ConditionalOrigin::dY => JumpDestination::eY,
                ConditionalOrigin::dZ => JumpDestination::eZ,
                ConditionalOrigin::rV => JumpDestination::fV,
                ConditionalOrigin::rW => JumpDestination::fW,
                ConditionalOrigin::rY => JumpDestination::fY,
                ConditionalOrigin::rZ => JumpDestination::fZ,
            };
    
            self.jump_to(destination)
        } else {
            self.memory.ip += 1;
            Ok(())
        }
    }

    fn cai_start(&mut self) -> Result<(), String> {
        self.memory.ip += 1;
        self.cai()
    }

    fn clear_memory(&mut self) {
        self.memory = Memory::new();
    }

    fn start_stop(&mut self) -> Result<(), String> {
        self.memory.ip += 1;
        Ok(())
    }
}

#[cfg(test)]
impl DefaultAlu {
    pub fn new_with_memory(system: mpsc::Sender<Message>, memory: Memory) -> Self {
        Self {
            memory,
            system,
            decimals: None
        }
    }
}

#[cfg(test)]
mod abs;

#[cfg(test)]
mod cai;

#[cfg(test)]
mod clear_entry;

#[cfg(test)]
mod conditional_jump;

#[cfg(test)]
mod copy_decimal;

#[cfg(test)]
mod copy_m;

#[cfg(test)]
mod copy_to_a;

#[cfg(test)]
mod dr_exchange;

#[cfg(test)]
mod jump;

#[cfg(test)]
mod math;

#[cfg(test)]
mod newline;

#[cfg(test)]
mod print;

#[cfg(test)]
mod reset;

#[cfg(test)]
mod rounding;

#[cfg(test)]
mod swap_a;

#[cfg(test)]
mod write_to_m;

// #[cfg(test)]
// mod running;

