pub use self::stack::Stack;
pub use self::word::Word;

use memory;
use memory::MemoryMap;
use self::opcode::{Opcode, PartialAddr, IWord};

mod alu;
mod opcode;
mod stack;
mod word;

pub struct CPU {
    p: Word,
    i: IWord,

    a: Word,
    b: Word,

    dat: Stack,
    ret: Stack,

    mem: MemoryMap
}

impl CPU {
    fn jump(&mut self, address: PartialAddr) {
        let PartialAddr(partial, bits) = address;
        let bits_word = Word(bits as i32);
        self.p = (self.p >> bits_word << bits_word) | partial;
    }

    fn exec(&mut self, opcode: Opcode) {
        use self::opcode::Opcode::*;

        match opcode {
            Unary(op)  => alu::unary(op, &mut self.dat),
            Binary(op) => alu::binary(op, &mut self.dat, &mut self.ret),
        }
    }

    fn exec_register(&mut self, op: u8, increment: bool) {
        let (reg, reg_op) = match (op, increment) {
            (3, true)  => (&mut self.p, 1),      // @p
            (7, true)  => (&mut self.p, 2),      // !p
            (0...3, _) => (&mut self.a, op & 3), // a @ ! a!
            (4...7, _) => (&mut self.b, op & 3), // b @b !b b!

            (n, f) => panic!("register op out of range: {} {}", n, f)
        };

        match reg_op {
            0 => self.dat.push(*reg),                  // a b
            1 => self.dat.push(self.mem.read(*reg)),   // @ @b @p
            2 => self.mem.write(*reg, self.dat.pop()), // ! !b !p
            3 => { *reg = self.dat.pop() },            // a! b!

            _ => unreachable!()
        }

        if increment {
            *reg = memory::inc_address(*reg);
        }
    }

    fn exec_control(&mut self, operation: u8, address: PartialAddr) {
        let t = self.dat.peek();

        match operation {
            0 => self.jump(address),                        // (jump)
            1 => {                                          // (call)
                self.ret.push(self.p);
                self.jump(address);
            }
            2 => if t == Word(0)    { self.jump(address) }, // if
            3 => if t >= Word(0)    { self.jump(address) }, // -if
            4 => if self.ret.next() { self.jump(address) }, // next
            5 => if self.ret.next() { self.i.restart() },   // unext
            6 => {                                          // ex
                let temp = self.p;
                self.p = self.ret.pop();
                self.ret.push(temp);
            },
            7 => { self.p = self.ret.pop(); },              // ;

            n => panic!("control flow op out of range: {}", n)
        }
    }
}
            Stack(op)  => alu::stack(op, &mut self.dat, &mut self.ret),

            Register(op, inc) => self.exec_register(op, inc),
            Control(op, addr) => self.exec_control(op, addr)