use std::io;

use crate::bit::BitIndex;
use crate::byteorder::{LittleEndian, ReadBytesExt};
use crate::num::FromPrimitive;

use super::arm::{ArmCond, ArmOpCode, ArmShiftType};
use super::{Addr, InstructionDecoder, InstructionDecoderError};

pub mod display;
pub mod exec;

#[derive(Debug, PartialEq)]
pub enum ThumbDecodeErrorKind {
    UnknownInstructionFormat,
    IoError(io::ErrorKind),
}
use ThumbDecodeErrorKind::*;

#[derive(Debug, PartialEq)]
pub struct ThumbDecodeError {
    pub kind: ThumbDecodeErrorKind,
    pub insn: u16,
    pub addr: Addr,
}

impl ThumbDecodeError {
    fn new(kind: ThumbDecodeErrorKind, insn: u16, addr: Addr) -> ThumbDecodeError {
        ThumbDecodeError {
            kind: kind,
            insn: insn,
            addr: addr,
        }
    }
}

#[derive(Debug, Copy, Clone, PartialEq)]
pub enum ThumbFormat {
    /// Format 1
    MoveShiftedReg,
    /// Format 2
    AddSub,
    /// Format 3
    DataProcessImm,
    /// Format 4
    AluOps,
    /// Format 5
    HiRegOpOrBranchExchange,
    /// Format 6
    LdrPc,
    /// Format 7
    LdrStrRegOffset,
    /// Format 8
    LdrStrSHB,
    /// Format 9
    LdrStrImmOffset,
    /// Format 10
    LdrStrHalfWord,
    /// Format 11
    LdrStrSp,
    /// Format 12
    LdrAddress,
    /// Format 13
    AddSp,
    /// Format 14
    PushPop,
    /// Format 15
    LdmStm,
    /// Format 16
    BranchConditional,
    /// Format 17
    Swi,
    /// Format 18
    Branch,
    /// Format 19
    BranchLongWithLink,
}

#[derive(Debug, Copy, Clone, PartialEq)]
pub struct ThumbInstruction {
    pub fmt: ThumbFormat,
    pub raw: u16,
    pub pc: Addr,
}

impl InstructionDecoder for ThumbInstruction {
    type IntType = u16;

    fn decode(raw: u16, addr: Addr) -> Result<Self, InstructionDecoderError> {
        use self::ThumbFormat::*;

        let fmt = if raw & 0xf800 == 0x1800 {
            Ok(AddSub)
        } else if raw & 0xe000 == 0x0000 {
            Ok(MoveShiftedReg)
        } else if raw & 0xe000 == 0x2000 {
            Ok(DataProcessImm)
        } else if raw & 0xfc00 == 0x4000 {
            Ok(AluOps)
        } else if raw & 0xfc00 == 0x4400 {
            Ok(HiRegOpOrBranchExchange)
        } else if raw & 0xf800 == 0x4800 {
            Ok(LdrPc)
        } else if raw & 0xf200 == 0x5000 {
            Ok(LdrStrRegOffset)
        } else if raw & 0xf200 == 0x5200 {
            Ok(LdrStrSHB)
        } else if raw & 0xe000 == 0x6000 {
            Ok(LdrStrImmOffset)
        } else if raw & 0xf000 == 0x8000 {
            Ok(LdrStrHalfWord)
        } else if raw & 0xf000 == 0x9000 {
            Ok(LdrStrSp)
        } else if raw & 0xf000 == 0xa000 {
            Ok(LdrAddress)
        } else if raw & 0xff00 == 0xb000 {
            Ok(AddSp)
        } else if raw & 0xf600 == 0xb400 {
            Ok(PushPop)
        } else if raw & 0xf000 == 0xc000 {
            Ok(LdmStm)
        } else if raw & 0xf000 == 0xd000 {
            Ok(BranchConditional)
        } else if raw & 0xff00 == 0xdf00 {
            Ok(Swi)
        } else if raw & 0xf800 == 0xe000 {
            Ok(Branch)
        } else if raw & 0xf000 == 0xf000 {
            Ok(BranchLongWithLink)
        } else {
            Err(ThumbDecodeError::new(UnknownInstructionFormat, raw, addr))
        }?;

        Ok(ThumbInstruction {
            fmt: fmt,
            raw: raw,
            pc: addr,
        })
    }

    fn decode_from_bytes(bytes: &[u8], addr: Addr) -> Result<Self, InstructionDecoderError> {
        let mut rdr = std::io::Cursor::new(bytes);
        let raw = rdr
            .read_u16::<LittleEndian>()
            .map_err(|e| InstructionDecoderError::IoError(e.kind()))?;
        Self::decode(raw, addr)
    }

    fn get_raw(&self) -> u16 {
        self.raw
    }
}

#[derive(Debug, Primitive, PartialEq)]
pub enum OpFormat3 {
    MOV = 0,
    CMP = 1,
    ADD = 2,
    SUB = 3,
}

impl From<OpFormat3> for ArmOpCode {
    fn from(op: OpFormat3) -> ArmOpCode {
        match op {
            OpFormat3::MOV => ArmOpCode::MOV,
            OpFormat3::CMP => ArmOpCode::CMP,
            OpFormat3::ADD => ArmOpCode::ADD,
            OpFormat3::SUB => ArmOpCode::SUB,
        }
    }
}

#[derive(Debug, Primitive, PartialEq)]
pub enum OpFormat5 {
    ADD = 0,
    CMP = 1,
    MOV = 2,
    BX = 3,
}

impl From<OpFormat5> for ArmOpCode {
    fn from(op: OpFormat5) -> ArmOpCode {
        match op {
            OpFormat5::ADD => ArmOpCode::ADD,
            OpFormat5::CMP => ArmOpCode::CMP,
            OpFormat5::MOV => ArmOpCode::MOV,
            _ => unreachable!(), // this should not be called if op = BX
        }
    }
}

impl ThumbInstruction {
    const FLAG_H1: usize = 7;
    const FLAG_H2: usize = 6;
    const FLAG_R: usize = 8;
    const FLAG_S: usize = 7;

    pub fn rd(&self) -> usize {
        match self.fmt {
            ThumbFormat::DataProcessImm
            | ThumbFormat::LdrPc
            | ThumbFormat::LdrStrSp
            | ThumbFormat::LdrAddress => self.raw.bit_range(8..11) as usize,
            _ => (self.raw & 0b111) as usize,
        }
    }

    pub fn rs(&self) -> usize {
        self.raw.bit_range(3..6) as usize
    }

    pub fn rb(&self) -> usize {
        self.raw.bit_range(3..6) as usize
    }

    pub fn ro(&self) -> usize {
        self.raw.bit_range(6..9) as usize
    }

    pub fn rn(&self) -> usize {
        self.raw.bit_range(6..9) as usize
    }

    pub fn format1_op(&self) -> ArmShiftType {
        ArmShiftType::from_u8(self.raw.bit_range(11..13) as u8).unwrap()
    }

    pub fn format3_op(&self) -> OpFormat3 {
        OpFormat3::from_u8(self.raw.bit_range(11..13) as u8).unwrap()
    }

    pub fn format5_op(&self) -> OpFormat5 {
        OpFormat5::from_u8(self.raw.bit_range(8..10) as u8).unwrap()
    }

    pub fn offset5(&self) -> i8 {
        self.raw.bit_range(6..11) as i8
    }

    pub fn offset8(&self) -> i8 {
        self.raw.bit_range(0..8) as i8
    }

    pub fn word8(&self) -> u16 {
        self.raw.bit_range(0..8) << 2
    }

    pub fn is_transfering_bytes(&self) -> bool {
        self.raw.bit(10)
    }

    pub fn is_load(&self) -> bool {
        self.raw.bit(11)
    }

    pub fn is_subtract(&self) -> bool {
        self.raw.bit(9)
    }

    pub fn is_immediate_operand(&self) -> bool {
        self.raw.bit(10)
    }

    pub fn cond(&self) -> ArmCond {
        ArmCond::from_u8(self.raw.bit_range(8..12) as u8).unwrap()
    }

    pub fn flag(&self, bit: usize) -> bool {
        self.raw.bit(bit)
    }

    pub fn register_list(&self) -> Vec<usize> {
        let list_bits = self.raw & 0xff;
        let mut list = Vec::with_capacity(8);
        for i in 0..=7 {
            if (list_bits & (1 << i)) != 0 {
                list.push(i)
            }
        }
        list
    }

    pub fn sword7(&self) -> i32 {
        let imm7 = self.raw & 0x7f;
        if self.flag(ThumbInstruction::FLAG_S) {
            -((imm7 << 2) as i32)
        } else {
            (imm7 << 2) as i32
        }
    }
}

#[cfg(test)]
/// All instructions constants were generated using an ARM assembler.
mod tests {
    use super::*;

    #[test]
    fn mov_low_reg() {
        use crate::arm7tdmi::cpu::{Core, CpuPipelineAction};
        use crate::sysbus::BoxedMemory;

        let bytes = vec![];
        let mut mem = BoxedMemory::new(bytes.into_boxed_slice());
        let mut core = Core::new();
        core.set_reg(0, 0);

        // movs r0, #0x27
        let insn = ThumbInstruction::decode(0x2027, 0).unwrap();

        assert_eq!(format!("{}", insn), "mov\tr0, #0x27");
        assert_eq!(
            core.exec_thumb(&mut mem, insn),
            Ok(CpuPipelineAction::IncPC)
        );
        assert_eq!(core.get_reg(0), 0x27);
    }

    #[test]
    fn ldr_pc() {
        use crate::arm7tdmi::cpu::{Core, CpuPipelineAction};
        use crate::sysbus::BoxedMemory;

        // ldr r0, [pc, #4]
        let insn = ThumbInstruction::decode(0x4801, 0x6).unwrap();

        let bytes = vec![
            /* 0: */        0x00, 0x00,
            /* 2: */        0x00, 0x00,
            /* 4: */        0x00, 0x00,
            /* 6: <pc> */   0x00, 0x00,
            /* 8: */        0x00, 0x00, 0x00, 0x00,
            /* c: */        0x78, 0x56, 0x34, 0x12,
        ];
        let mut mem = BoxedMemory::new(bytes.into_boxed_slice());
        let mut core = Core::new();
        core.set_reg(0, 0);

        assert_eq!(format!("{}", insn), "ldr\tr0, [pc, #0x4] ; = #0xc");
        assert_eq!(
            core.exec_thumb(&mut mem, insn),
            Ok(CpuPipelineAction::IncPC)
        );
        assert_eq!(core.get_reg(0), 0x12345678);
    }

    #[test]
    fn ldr_str_reg_offset() {
        use crate::arm7tdmi::{
            cpu::{Core, CpuPipelineAction},
            Bus,
        };
        use crate::sysbus::BoxedMemory;

        // str	r0, [r4, r1]
        let str_insn = ThumbInstruction::decode(0x5060, 0x6).unwrap();
        // ldrb r2, [r4, r1]
        let ldr_insn = ThumbInstruction::decode(0x5c62, 0x6).unwrap();

        let mut core = Core::new();
        core.set_reg(0, 0x12345678);
        core.set_reg(2, 0);
        core.set_reg(1, 0x4);
        core.set_reg(4, 0xc);

        let bytes = vec![
            /*  0: */ 0xaa, 0xbb, 0xcc, 0xdd, /*  4: */ 0xaa, 0xbb, 0xcc, 0xdd,
            /*  8: */ 0xaa, 0xbb, 0xcc, 0xdd, /*  c: */ 0xaa, 0xbb, 0xcc, 0xdd,
            /* 10: */ 0xaa, 0xbb, 0xcc, 0xdd,
        ];
        let mut mem = BoxedMemory::new(bytes.into_boxed_slice());

        assert_eq!(format!("{}", str_insn), "str\tr0, [r4, r1]");
        assert_eq!(format!("{}", ldr_insn), "ldrb\tr2, [r4, r1]");
        assert_eq!(
            core.exec_thumb(&mut mem, str_insn),
            Ok(CpuPipelineAction::IncPC)
        );
        assert_eq!(mem.read_32(0x10), 0x12345678);
        assert_eq!(
            core.exec_thumb(&mut mem, ldr_insn),
            Ok(CpuPipelineAction::IncPC)
        );
        assert_eq!(core.get_reg(2), 0x78);
    }
}