mod selected_operand;
mod status;

use crate::system::{Component, ComponentLink};
use crate::message::*;
use crate::alu::{AluProvider, Digit};
use p101_is::*;
use p101_enc::{encoder, encoding, filter, pipeline};
use selected_operand::SelectedRegister;
use status::AluComponentStatus;
use std::sync::mpsc;

pub struct AluComponent<T: AluProvider> {
    /// The actual ALU implementation
    alu: T,

    /// Link to the system bus and component channels
    link: ComponentLink,

    /// The ALU state
    status: AluComponentStatus,

    /// The selected register, if any
    register: SelectedRegister,

    /// The M register must be cleared on first write
    clear_m_on_next_input: bool
}

impl<T: AluProvider> AluComponent<T> {
    const COMPONENT_NAME: &'static str = "AluComponent";
    pub fn new(alu: T, system: mpsc::Sender<Message>) -> Self {
        Self {
            alu,
            link: ComponentLink::new(system),
            status: AluComponentStatus::PoweredOff,
            register: SelectedRegister::new(),
            clear_m_on_next_input: false
        }
    }

    fn update_memory(&mut self, text: String) {
        let stages= vec!(pipeline::Stage::new(Box::new(filter::StandardDecoder::new()), false));

        let options = pipeline::PipelineOptions {
            display_messages: false,
            display_errors: false,
        };

        let pipeline = pipeline::Pipeline::new(stages, options);

        match pipeline.run(filter::EncodingData::Text(text)) {
            Ok(x) => if let filter::EncodingData::Program(program) = x.output {
                    match self.alu.update_memory(program) {
                        Ok(_) => log::info!("Memory successfully updated"),
                        Err(e) => log::error!("{}", e),
                    }
                } else {
                    log::error!("Unexpected program data");
                },
            Err(e) => {
                log::error!("{}", e);
            }
        };
    }

    fn store_memory(&self) {

        // Read memory

        let mut program = Vec::new();

        for addr in 0..120 {
            if let Some(instruction) = self.alu.read_instruction(addr) {
                program.push(instruction);
            }
        }

        // Encode program

        let encoder = encoder::Encoder::<encoding::StandardEncoding, encoder::DefaultAnnotator>::new();
        let text = encoder.encode(&program);

        // Post memory

        self.link.system.send(Message::StoreTextOnCard(text)).unwrap();
    }

    fn start(&mut self, origin: Origin) {
        match self.alu.jump(origin) {
            Ok(_) => self.run_program(),
            Err(e) => {
                log::error!("{}", e);
                self.post_error()
            }
        }
    }

    fn run_program(&mut self) {
        match self.status {
            AluComponentStatus::StandBy => self.status.program_started(),
            AluComponentStatus::WaitingInput => self.status.program_restarted(),
            _ => panic!("ALU component status {:?} does not allow to run a program", self.status)
        }
        self.link.system.send(Message::ProgramStarted).unwrap();

        loop {
            let ip = self.alu.read_ip();

            if let Some(instr) = self.alu.read_instruction(ip) {
                log::info!("Executing instruction at IP {}: {:?}", ip, &instr);

                match self.process_instruction(instr) {
                    InstructionResult::Ok => (),
                    InstructionResult::Stop => (),
                    InstructionResult::Err(e) => {
                        log::error!("{}", e);
                        // TODO: Should require a general reset?
                        // self.link.system.send(Message::Error).unwrap();
                    },
                }
            } else {
                // The instruction has not been understood
                // and the program is being terminated.
                break;
            }
        }

        // Notify the systen when the program has finished
        if self.status == AluComponentStatus::ProgramRunning {
            self.status.stand_by();
            self.link.system.send(Message::ProgramEnded).unwrap();
        }
    }

    fn post_error(&mut self) {
        self.link.system.send(Message::Error).unwrap();
    }

    fn stop(&mut self) -> Result<(), String> {
        self.status.wait_user_input();
        self.link.system.send(Message::ProgramStopped)
            .map_err(|e| e.to_string())
    }

    fn process_instruction(&mut self, instr: Instruction) -> InstructionResult {
        let result = match instr {
            Instruction::Add(op) => self.alu.add(op)
                .and_then(|()| { self.alu.increment_ip(1) }),
            Instruction::Sub(op) => self.alu.subtract(op)
                .and_then(|()| { self.alu.increment_ip(1) }),
            Instruction::Mul(op) => self.alu.multiply(op)
                .and_then(|()| { self.alu.increment_ip(1) }),
            Instruction::Div(op) => self.alu.divide(op)
                .and_then(|()| { self.alu.increment_ip(1) }),
            Instruction::Sqr(op) => self.alu.sqr(op)
                .and_then(|()| { self.alu.increment_ip(1) }),
            Instruction::Abs => self.alu.abs()
                .and_then(|()| { self.alu.increment_ip(1) }),
            Instruction::CopyM(op) => self.alu.copy_m(op)
                .and_then(|()| { self.alu.increment_ip(1) }),
            Instruction::CopyToA(op) => self.alu.copy_to_a(op)
                .and_then(|()| { self.alu.increment_ip(1) }),
            Instruction::SwapA(op) => self.alu.swap_a(op)
                .and_then(|()| { self.alu.increment_ip(1) }),
            Instruction::CopyDecimal => self.alu.copy_decimal()
                .and_then(|()| { self.alu.increment_ip(1) }),
            Instruction::Label(_) => self.alu.label()
                .and_then(|()| { self.alu.increment_ip(1) }),
            Instruction::Jump(dest) => self.alu.jump(dest),
            Instruction::ConditionalJump(dest) => self.alu.conditional_jump(dest),
            Instruction::Reset(op) => self.alu.reset(op).
                and_then(|()| { self.alu.increment_ip(1) }),
            Instruction::Print(op) => self.alu.print(op)
                .and_then(|()| { self.alu.increment_ip(1) }),
            Instruction::Stop => {
                match self.stop()
                .and_then(|()| { self.alu.increment_ip(1) }) {
                    Ok(_) => return InstructionResult::Stop,
                    Err(e) => Err(e)
                }
            },
            Instruction::NewLine => self.alu.newline()
                .and_then(|()| { self.alu.increment_ip(1) }),
            Instruction::DrExchange => self.alu.dr_exchange()
                .and_then(|()| { self.alu.increment_ip(1) }),
            Instruction::CaiStart => self.alu.cai_start(),
            Instruction::Cai(_, _, _, _) => panic!("Instruction pointer {} points to a CAI.", self.alu.read_ip())
        };

        match result {
            Ok(()) => InstructionResult::Ok,
            Err(e) => InstructionResult::Err(e)
        }
    }

    fn write_to_m(&mut self, value: Digit) {
        if self.clear_m_on_next_input {
            if let Err(e) = self.alu.clear_m() {
                log::error!("{}({}) cannot clear register m: {:?}", Self::COMPONENT_NAME, self.alu.get_provider_name(), e);
            }

            self.clear_m_on_next_input = false;
        }

        if let Err(e) = self.alu.write_to_m(value) {
            log::error!("{}({}) cannot update register m: {:?}", Self::COMPONENT_NAME, self.alu.get_provider_name(), e);
        }
    }

    fn clear(&mut self) {
        self.alu.clear_m().unwrap();
        self.alu.entering_decimals(false);
        self.register.reset();
        self.link.system.send(Message::NewLine).unwrap();
    }

    fn process_message(&mut self, message: Message) {
        match message {
            // Memory

            Message::TextRead(text) => self.update_memory(text),
            Message::CardInserted(_) => self.store_memory(),

            // Routines

            Message::KeyPressed(Key::V) => self.start(Origin::V),
            Message::KeyPressed(Key::W) => self.start(Origin::W),
            Message::KeyPressed(Key::Y) => self.start(Origin::Y),
            Message::KeyPressed(Key::Z) => self.start(Origin::Z),

            // Accumulate keystrokes in the M register when the ALU is not running a program

            Message::KeyPressed(Key::Zero) => self.write_to_m(Digit::Zero),
            Message::KeyPressed(Key::One) => self.write_to_m(Digit::One),
            Message::KeyPressed(Key::Two) => self.write_to_m(Digit::Two),
            Message::KeyPressed(Key::Three) => self.write_to_m(Digit::Three),
            Message::KeyPressed(Key::Four) => self.write_to_m(Digit::Four),
            Message::KeyPressed(Key::Five) => self.write_to_m(Digit::Five),
            Message::KeyPressed(Key::Six) => self.write_to_m(Digit::Six),
            Message::KeyPressed(Key::Seven) => self.write_to_m(Digit::Seven),
            Message::KeyPressed(Key::Eight) => self.write_to_m(Digit::Eight),
            Message::KeyPressed(Key::Nine) => self.write_to_m(Digit::Nine),
            Message::KeyPressed(Key::Comma) => self.alu.entering_decimals(true),
            Message::KeyPressed(Key::Negative) => self.alu.invert_sign(),

            // Register selection

            Message::KeyPressed(Key::Split) => {
                self.register.update(Key::Split);
                self.post_operand();
            },
            Message::KeyPressed(Key::A) => {
                self.register.update(Key::A);
                self.post_operand();
            },
            Message::KeyPressed(Key::B) => {
                self.register.update(Key::B);
                self.post_operand();
            },
            Message::KeyPressed(Key::C) => {
                self.register.update(Key::C);
                self.post_operand();
            },
            Message::KeyPressed(Key::D) => {
                self.register.update(Key::D);
                self.post_operand();
            },
            Message::KeyPressed(Key::E) => {
                self.register.update(Key::E);
                self.post_operand();
            },
            Message::KeyPressed(Key::F) => {
                self.register.update(Key::F);
                self.post_operand();
            },
            Message::KeyPressed(Key::R) => {
                self.register.update(Key::R);
                self.post_operand();
            },

            // Register operations
            
            Message::KeyPressed(Key::Clear) => self.clear(),
            Message::KeyPressed(Key::ClearEntry) => {
                let _ = self.alu.clear_entry();
                self.register.reset();
            },
            Message::KeyPressed(Key::CopyM) => {
                match self.register.get_operand() {
                    Register::M => (), // Inoperative
                    Register::R => (), // Inoperative
                    Register::A => (), // CAI, P101 General Reference p.19
                    op => {
                        self.alu.copy_m(op).unwrap();
                        self.clear_m_on_next_input = true;
                    }
                };
                self.register.reset();
            },
            Message::KeyPressed(Key::CopyToA) => {
                match self.register.get_operand() {
                    Register::A => (),
                    op => {
                        let _ = self.alu.copy_to_a(op);
                        self.clear_m_on_next_input = true;
                        self.register.reset();
                    }
                }
            },
            Message::KeyPressed(Key::Print) => {
                let op = self.register.get_operand();
                let _ = self.alu.print(op);
                self.clear_m_on_next_input = true;
                self.register.reset();
            },
            Message::KeyPressed(Key::SwapA) => {
                let op = self.register.get_operand();
                let _ = self.alu.swap_a(op);
                self.register.reset();
            },

            // Math

            Message::KeyPressed(Key::Add) => {
                let op = self.register.get_operand();
                let _ = self.alu.add(op);
                self.clear_m_on_next_input = true;
                self.register.reset();
            },
            Message::KeyPressed(Key::Subtract) => {
                let op = self.register.get_operand();
                let _ = self.alu.subtract(op);
                self.clear_m_on_next_input = true;
                self.register.reset();
            },
            Message::KeyPressed(Key::Multiply) => {
                let op = self.register.get_operand();
                let _ = self.alu.multiply(op);
                self.clear_m_on_next_input = true;
                self.register.reset();
            },
            Message::KeyPressed(Key::Divide) => {
                let op = self.register.get_operand();
                let _ = self.alu.divide(op);
                self.clear_m_on_next_input = true;
                self.register.reset();
            },
            Message::KeyPressed(Key::SquareRoot) => {
                let op = self.register.get_operand();
                let _ = self.alu.sqr(op);
                self.clear_m_on_next_input = true;
                self.register.reset();
            },

            // When the Start/Stop key has been pressed,
            // restart the program.
            Message::KeyPressed(Key::Start) => {
                if self.status == AluComponentStatus::WaitingInput {
                    self.run_program()
                // What should do on else branch???
                // } else {
                //    self.stopped = true;
                //    let _ = self.alu.start_stop();
                }
            },

            Message::KeyPressed(Key::GeneralReset) => {
                match self.status {
                    AluComponentStatus::WaitingInput => self.alu.clear_memory(),
                    AluComponentStatus::StandBy => self.alu.clear_memory(),
                    _ => ()
                };
                self.register.reset();
            },
            _ => ()
        };
    }

    fn post_operand(&mut self) {
        let operand = self.register.get_operand();
        let message = Message::Debug(DebugEvent::RegisterSelected(operand));
        self.link.system.send(message).unwrap();
    }
}

#[derive(Debug, PartialEq)]
enum InstructionResult {
    Ok,
    Stop,
    Err(String)
}

impl<T: AluProvider> Component for AluComponent<T> {
    fn get_name(&self) -> &str {
        "AluComponent"
    }

    fn listen_to(&self) -> Vec<MessageType> {
        vec!(MessageType::TextRead,
             MessageType::KeyPressed,
             MessageType::SystemStatusChanged)
    }

    fn get_sender(&self) -> mpsc::Sender<Message> {
        self.link.component_sender.clone()
    }

    fn run(&mut self) {
        log::info!("{}({}) running", Self::COMPONENT_NAME, self.alu.get_provider_name());

        loop {
            let message = self.link.component_receiver.recv();

            log::debug!("{}({}) received a message: {:?}", Self::COMPONENT_NAME, self.alu.get_provider_name(), &message);

            match message {
                Ok(Message::Halt) => {
                    log::debug!("{}({}) received an halt message and is hanging on", Self::COMPONENT_NAME, self.alu.get_provider_name());
                        return;
                }
                Ok(Message::SystemStatusChanged(status)) => {
                    match status {
                        SystemStatus::Ready => self.status.stand_by(),
                        _ => self.status.power_off()
                    }
                }
                Ok(message) => {
                    match self.status {
                        AluComponentStatus::StandBy => self.process_message(message),
                        AluComponentStatus::WaitingInput => self.process_message(message),
                        _ => log::debug!("{}({}) is off. Discarding message {:?}", Self::COMPONENT_NAME, self.alu.get_provider_name(), &message)
                    }
                }
                Err(e) => {
                    log::debug!("{}({}) received an error while reading message and is hanging on: {:?}", Self::COMPONENT_NAME, self.alu.get_provider_name(), e);
                    return
                }
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::alu::{TestAluBuilder, FuncName};
    use std::thread;
    use std::time::Duration;

    mod add;
    mod clear_entry;
    mod clear_m;
    mod subtract;
    mod multiply;
    mod divide;
    mod copy_m;
    mod copy_to_a;
    mod swap_a;
    mod print;
    mod sqr;
    mod start_stop;
    mod reset;

    #[test]
    pub fn when_the_alu_component_receives_an_halt_message_then_it_stops() { 
        // Arrange

        let (sender, _receiver) = mpsc::channel();
        let alu = TestAluBuilder::new(sender.clone())
            .build();
        let mut component = AluComponent::new(alu, sender.clone());
        let component_sender = component.link.component_sender.clone();

        // Act
        
        let handle = thread::spawn(move || component.run());
        component_sender.send(Message::Halt).unwrap();

        // Assert

        let _ = handle.join();
        assert!(true, "the component should halt");
    }

    #[test]
    pub fn when_the_alu_component_receives_a_text_read_message_then_it_updates_the_memory() {
        // Arrange
        
        let fibonacci = "AV\nA*\nEV\n+\nA⋄\n↕\nDV".to_string();
        let (sender, receiver) = mpsc::channel();
        let alu = TestAluBuilder::new(sender.clone()).build();
        let mut component = AluComponent::new(alu, sender.clone());
        let component_sender = component.link.component_sender.clone();

        // Act
        
        let handle = thread::spawn(move || {
            component.run();
            return component.alu.mem;
        });
        component_sender.send(Message::SystemStatusChanged(SystemStatus::Ready)).unwrap();
        component_sender.send(Message::TextRead(fibonacci)).unwrap();
        component_sender.send(Message::Halt).unwrap();
        let _msg = receiver.recv_timeout(std::time::Duration::from_secs(1));

        // Assert

        let mem = handle.join().expect("The component should halt");

        println!("{:?}", mem);

        assert_eq!(Some(Instruction::Label(JumpDestination::AV)), mem[0]);
        assert_eq!(Some(Instruction::Reset(Register::A)), mem[1]);
        assert_eq!(Some(Instruction::Label(JumpDestination::EV)), mem[2]);
        assert_eq!(Some(Instruction::Add(Register::M)), mem[3]);
        assert_eq!(Some(Instruction::Print(Register::A)), mem[4]);
        assert_eq!(Some(Instruction::SwapA(Register::M)), mem[5]);
        assert_eq!(Some(Instruction::Jump(Origin::DV)), mem[6]);
    }

    #[test]
    pub fn when_the_alu_component_receives_a_card_inserted_message_then_post_a_store_message() {
        // Arrange

        let fibonacci = "AV # Label AV\nA* # Clear register A\nEV # Label EV\n+ # Add register M to the accumulator\nA⋄ # Print register A value\n↕ # Exchange accumulator value with register M\nDV # Jump to DV\n".to_string();
        let (sender, receiver) = mpsc::channel();
        let alu = TestAluBuilder::new(sender.clone())
            .instr(0, Instruction::Label(JumpDestination::AV))
            .instr(1, Instruction::Reset(Register::A))
            .instr(2, Instruction::Label(JumpDestination::EV))
            .instr(3, Instruction::Add(Register::M))
            .instr(4, Instruction::Print(Register::A))
            .instr(5, Instruction::SwapA(Register::M))
            .instr(6, Instruction::Jump(Origin::DV))
            .build();
        let mut component = AluComponent::new(alu, sender.clone());
        let component_sender = component.link.component_sender.clone();

        // Act

        let handle = thread::spawn(move || {
            component.run();
        });

        let card = Card::new_memory_card("");

        component_sender.send(Message::SystemStatusChanged(SystemStatus::Ready)).unwrap();
        component_sender.send(Message::CardInserted(card)).unwrap();
        component_sender.send(Message::Halt).unwrap();

        let msg = receiver.recv_timeout(std::time::Duration::from_secs(1));

        // Assert

        handle.join().expect("The component should halt");

        assert!(msg.is_ok());
        assert_eq!(Ok(Message::StoreTextOnCard(fibonacci)), msg);
    }

    #[test]
    pub fn when_a_program_ends_then_post_a_program_ended_message() {
        // Arrange
        
        let (sender, receiver) = mpsc::channel();
        let alu = TestAluBuilder::new(sender.clone())
            .instr(0, Instruction::Label(JumpDestination::AV))
            .instr(1, Instruction::Reset(Register::A))
            .instr(2, Instruction::Reset(Register::A))
            .instr(3, Instruction::Reset(Register::A))
            .build();
        let mut component = AluComponent::new(alu, sender.clone());
        component.status = AluComponentStatus::StandBy;

        // Act
        
        component.start(Origin::V);
        
        let mut messages = Vec::new();

        while let Ok(message) = receiver.recv_timeout(Duration::from_secs(1)) {
            messages.push(message);
        }

        // Assert

        assert_eq!(4, component.alu.ip);
        assert_eq!(2, messages.len());
        assert_eq!(Message::ProgramStarted, messages[0], "Expecting a Message::ProgramStarted");
        assert_eq!(Message::ProgramEnded, messages[1], "Expecting a Message::ProgramEnded");
    }

    #[test]
    pub fn when_the_start_label_is_not_found_then_post_a_error_message() {
        // Arrange
        
        let (sender, receiver) = mpsc::channel();

        let alu = TestAluBuilder::new(sender.clone())
            .ip(3)
            .cb(FuncName::Jump, |_alu| {
                Err("not found".to_string())
            })
            .build();

        let mut component = AluComponent::new(alu, sender.clone());

        // Act
        component.start(Origin::V);
        let error = receiver.recv_timeout(Duration::from_secs(1));

        // Assert

        assert_eq!(3, component.alu.read_ip(), "IP does not change");
        assert_eq!(Ok(Message::Error), error, "The component should post an error message on the system bus");
    }

    #[test]
    pub fn when_a_program_is_run_some_messages_are_posted() {

        // Arrange
        
        let value = rust_decimal::Decimal::new(123, 2);
        let print_msg = Message::Print(value, Register::A, Operator::Print);

        let (sender, receiver) = mpsc::channel();

        let alu = TestAluBuilder::new(sender.clone())
            .instr(0, Instruction::Label(JumpDestination::AV))
            .instr(1, Instruction::Print(Register::A))
            .ip(0)
            .cb(FuncName::Jump, |alu| {
                alu.ip = 0;
                Ok(())
            })
            .cb(FuncName::Print, |alu| {
                let my_value = rust_decimal::Decimal::new(123, 2);
                let msg = Message::Print(my_value, Register::A, Operator::Print);
                // Emitting a Print event simulates the emulator behavior
                // but it is not strictly required to test the ALU component.
                alu.system.send(msg).unwrap();
                Ok(())
            })
            .build();

        let mut component = AluComponent::new(alu, sender.clone());
        component.status = AluComponentStatus::StandBy;

        // Act
        
        component.start(Origin::V);

        let mut messages = Vec::new();

        while let Ok(message) = receiver.recv_timeout(Duration::from_secs(1)) {
            messages.push(message);
        }

        // Assert

        assert_eq!(2, component.alu.ip, "IP should have been incremented");
        assert_eq!(3, messages.len());
        assert_eq!(Message::ProgramStarted, messages[0], "Expecting a Message::ProgramStarted");
        assert_eq!(print_msg, messages[1], "Expecting a Message::Print");
        assert_eq!(Message::ProgramEnded, messages[2], "Expecting a Message::ProgramEnded");
    }
}
