
use moa_core::Error;

use crate::instructions::{Instruction, Target, LoadTarget, RegisterPair};

pub enum Z80InstructionCycles {
    Single(u16),
    Branch {
        taken: u16,
        not_taken: u16
    },
    Repeating {
        repeating: u16,
        terminating: u16,
    },
}

impl Z80InstructionCycles {
    pub fn calculate_cycles(&self, took_branch: bool) -> u16 {
        match self {
            Z80InstructionCycles::Single(cycles) => *cycles,

            Z80InstructionCycles::Branch { taken, not_taken } => if took_branch { *taken } else { *not_taken },

            Z80InstructionCycles::Repeating { repeating, terminating } => if took_branch { *repeating } else { *terminating },
        }
    }

    pub fn from_instruction(instruction: &Instruction, extra: u16) -> Result<Z80InstructionCycles, Error> {
        let cycles = match instruction {
            Instruction::ADCa(target) |
            Instruction::ADDa(target) |
            Instruction::AND(target) |
            Instruction::CP(target) |
            Instruction::SBCa(target) |
            Instruction::SUB(target) |
            Instruction::OR(target) |
            Instruction::XOR(target) => {
                match target {
                    Target::DirectReg(_) |
                    Target::DirectRegHalf(_) => 4,
                    Target::IndirectReg(_) => 7,
                    Target::Immediate(_) => 7,
                    Target::IndirectOffset(_, _) => 19,
                }
            },

            Instruction::ADC16(_, _) |
            Instruction::SBC16(_, _) => 15,

            Instruction::ADD16(dest_pair, _) => {
                if !dest_pair.is_index_reg() {
                    11
                } else {
                    15
                }
            },

            Instruction::BIT(_, target) => {
                match target {
                    Target::DirectReg(_) => 8,
                    Target::IndirectReg(_) => 12,
                    Target::IndirectOffset(_, _) => 20,
                    _ => return Err(Error::new(format!("unexpected instruction: {:?}", instruction))),
                }
            },

            Instruction::CALL(_) => 17,

            Instruction::CALLcc(_, _) => {
                return Ok(Z80InstructionCycles::Branch {
                    taken: 17 + extra,
                    not_taken: 10 + extra,
                });
            },

            Instruction::CCF => 4,

            Instruction::CPD |
            Instruction::CPI |
            Instruction::IND |
            Instruction::INI |
            Instruction::LDD |
            Instruction::LDI |
            Instruction::OUTD |
            Instruction::OUTI => 16,

            Instruction::CPDR |
            Instruction::CPIR |
            Instruction::INDR |
            Instruction::INIR |
            Instruction::LDDR |
            Instruction::LDIR |
            Instruction::OTDR |
            Instruction::OTIR => {
                return Ok(Z80InstructionCycles::Repeating {
                    repeating: 21 + extra,
                    terminating: 16 + extra,
                })
            },

            Instruction::CPL => 4,
            Instruction::DAA => 4,

            Instruction::DEC8(target) |
            Instruction::INC8(target) => {
                match target {
                    Target::DirectReg(_) |
                    Target::DirectRegHalf(_) => 4,
                    Target::IndirectReg(_) => 11,
                    Target::IndirectOffset(_, _) => 23,
                    _ => return Err(Error::new(format!("unexpected instruction: {:?}", instruction))),
                }
            },

            Instruction::DEC16(regpair) |
            Instruction::INC16(regpair) => {
                if !regpair.is_index_reg() {
                    6
                } else {
                    10
                }
            },

            Instruction::DI |
            Instruction::EI => 4,

            Instruction::DJNZ(_) => {
                return Ok(Z80InstructionCycles::Branch {
                    taken: 13 + extra,
                    not_taken: 8 + extra,
                });
            },

            Instruction::EXX => 4,
            Instruction::EXafaf => 4,
            Instruction::EXhlde => 4,
            Instruction::EXsp(regpair) => {
                if !regpair.is_index_reg() {
                    19
                } else {
                    23
                }
            },

            Instruction::HALT => 4,
            Instruction::IM(_) => 8,

            Instruction::INic(_) |
            Instruction::INicz |
            Instruction::OUTic(_) |
            Instruction::OUTicz => 12,

            Instruction::INx(_) |
            Instruction::OUTx(_) => 11,

            Instruction::JP(_) => 10,
            Instruction::JR(_) => 12,

            Instruction::JPIndirect(regpair) => {
                if !regpair.is_index_reg() {
                    4
                } else {
                    8
                }
            },

            Instruction::JPcc(_, _) => 10,

            Instruction::JRcc(_, _) => {
                return Ok(Z80InstructionCycles::Branch {
                    taken: 12 + extra,
                    not_taken: 7 + extra,
                });
            },

            Instruction::LD(dest, src) => {
                match (dest, src) {
                    // 8-Bit Operations

                    (LoadTarget::DirectRegByte(_), LoadTarget::DirectRegByte(_)) => 4,

                    (LoadTarget::DirectRegHalfByte(_), LoadTarget::DirectRegByte(_)) |
                    (LoadTarget::DirectRegByte(_), LoadTarget::DirectRegHalfByte(_)) |
                    (LoadTarget::DirectRegHalfByte(_), LoadTarget::DirectRegHalfByte(_)) => 8,

                    (LoadTarget::DirectRegByte(_) | LoadTarget::DirectRegHalfByte(_), LoadTarget::ImmediateByte(_)) => 7,
                    (LoadTarget::IndirectRegByte(_), LoadTarget::ImmediateByte(_)) => 10,

                    (LoadTarget::IndirectOffsetByte(_, _), _) |
                    (_, LoadTarget::IndirectOffsetByte(_, _)) => 19,

                    (_, LoadTarget::IndirectRegByte(_)) |
                    (LoadTarget::IndirectRegByte(_), _) => 7,

                    (_, LoadTarget::IndirectByte(_)) |
                    (LoadTarget::IndirectByte(_), _) => 13,

                    // 16-Bit Operations

                    (LoadTarget::DirectRegWord(regpair), LoadTarget::ImmediateWord(_)) |
                    (LoadTarget::ImmediateWord(_), LoadTarget::DirectRegWord(regpair)) => {
                        if !regpair.is_index_reg() {
                            10
                        } else {
                            14
                        }
                    },

                    (LoadTarget::DirectRegWord(_), LoadTarget::DirectRegWord(regpair)) => {
                        if !regpair.is_index_reg() {
                            6
                        } else {
                            10
                        }
                    },

                    (LoadTarget::IndirectWord(_), LoadTarget::DirectRegWord(RegisterPair::HL)) |
                    (LoadTarget::DirectRegWord(RegisterPair::HL), LoadTarget::IndirectWord(_)) => 16,

                    (LoadTarget::IndirectWord(_), _) |
                    (_, LoadTarget::IndirectWord(_)) => 20,

                    _ => return Err(Error::new(format!("unexpected instruction: {:?}", instruction))),
                }
            },

            Instruction::LDsr(_, _) => 9,

            Instruction::NEG => 8,
            Instruction::NOP => 4,


            Instruction::POP(regpair) => {
                if !regpair.is_index_reg() {
                    10
                } else {
                    14
                }
            },
            Instruction::PUSH(regpair) => {
                if !regpair.is_index_reg() {
                    11
                } else {
                    15
                }
            },

            Instruction::RES(_, target, _) |
            Instruction::SET(_, target, _) => {
                match target {
                    Target::DirectReg(_) => 8,
                    Target::IndirectReg(_) => 15,
                    Target::IndirectOffset(_, _) => 23,
                    _ => return Err(Error::new(format!("unexpected instruction: {:?}", instruction))),
                }
            },

            Instruction::RET => 10,
            Instruction::RETI => 14,
            Instruction::RETN => 14,

            Instruction::RETcc(_) => {
                return Ok(Z80InstructionCycles::Branch {
                    taken: 11 + extra,
                    not_taken: 5 + extra,
                });
            },

            Instruction::RL(target, _) |
            Instruction::RLC(target, _) |
            Instruction::RR(target, _) |
            Instruction::RRC(target, _) |
            Instruction::SLA(target, _) |
            Instruction::SLL(target, _) |
            Instruction::SRA(target, _) |
            Instruction::SRL(target, _) => {
                match target {
                    Target::DirectReg(_) => 8,
                    Target::IndirectReg(_) => 15,
                    Target::IndirectOffset(_, _) => 23,
                    _ => return Err(Error::new(format!("unexpected instruction: {:?}", instruction))),
                }
            },

            Instruction::RLA |
            Instruction::RLCA |
            Instruction::RRA |
            Instruction::RRCA => 4,

            Instruction::RLD => 18,
            Instruction::RRD => 18,

            Instruction::RST(_) => 11,

            Instruction::SCF => 4,
        };
        Ok(Z80InstructionCycles::Single(cycles + extra))
    }
}

