use il::{Instruction, Operand};

/// Returns the register number written to by an instruction, if any.
pub fn get_destination_reg(instr: &Instruction) -> Option<u8> {
    match instr {
        Instruction::Move(Operand::Register(r), _)
        | Instruction::Add(Operand::Register(r), _, _)
        | Instruction::Sub(Operand::Register(r), _, _)
        | Instruction::Mul(Operand::Register(r), _, _)
        | Instruction::Div(Operand::Register(r), _, _)
        | Instruction::Mod(Operand::Register(r), _, _)
        | Instruction::Pow(Operand::Register(r), _, _)
        | Instruction::Load(Operand::Register(r), _, _)
        | Instruction::LoadSlot(Operand::Register(r), _, _, _)
        | Instruction::LoadBatch(Operand::Register(r), _, _, _)
        | Instruction::LoadBatchNamed(Operand::Register(r), _, _, _, _)
        | Instruction::SetEq(Operand::Register(r), _, _)
        | Instruction::SetNe(Operand::Register(r), _, _)
        | Instruction::SetGt(Operand::Register(r), _, _)
        | Instruction::SetLt(Operand::Register(r), _, _)
        | Instruction::SetGe(Operand::Register(r), _, _)
        | Instruction::SetLe(Operand::Register(r), _, _)
        | Instruction::And(Operand::Register(r), _, _)
        | Instruction::Or(Operand::Register(r), _, _)
        | Instruction::Xor(Operand::Register(r), _, _)
        | Instruction::Peek(Operand::Register(r))
        | Instruction::Get(Operand::Register(r), _, _)
        | Instruction::Select(Operand::Register(r), _, _, _)
        | Instruction::Rand(Operand::Register(r))
        | Instruction::Acos(Operand::Register(r), _)
        | Instruction::Asin(Operand::Register(r), _)
        | Instruction::Atan(Operand::Register(r), _)
        | Instruction::Atan2(Operand::Register(r), _, _)
        | Instruction::Abs(Operand::Register(r), _)
        | Instruction::Ceil(Operand::Register(r), _)
        | Instruction::Cos(Operand::Register(r), _)
        | Instruction::Floor(Operand::Register(r), _)
        | Instruction::Log(Operand::Register(r), _)
        | Instruction::Max(Operand::Register(r), _, _)
        | Instruction::Min(Operand::Register(r), _, _)
        | Instruction::Sin(Operand::Register(r), _)
        | Instruction::Sqrt(Operand::Register(r), _)
        | Instruction::Tan(Operand::Register(r), _)
        | Instruction::Trunc(Operand::Register(r), _)
        | Instruction::LoadReagent(Operand::Register(r), _, _, _)
        | Instruction::Rmap(Operand::Register(r), _, _)
        | Instruction::Pop(Operand::Register(r)) => Some(*r),
        _ => None,
    }
}

/// Creates a new instruction with the destination register changed.
pub fn set_destination_reg<'a>(instr: &Instruction<'a>, new_reg: u8) -> Option<Instruction<'a>> {
    let r = Operand::Register(new_reg);
    match instr {
        Instruction::Move(_, b) => Some(Instruction::Move(r, b.clone())),
        Instruction::Add(_, a, b) => Some(Instruction::Add(r, a.clone(), b.clone())),
        Instruction::Sub(_, a, b) => Some(Instruction::Sub(r, a.clone(), b.clone())),
        Instruction::Mul(_, a, b) => Some(Instruction::Mul(r, a.clone(), b.clone())),
        Instruction::Div(_, a, b) => Some(Instruction::Div(r, a.clone(), b.clone())),
        Instruction::Mod(_, a, b) => Some(Instruction::Mod(r, a.clone(), b.clone())),
        Instruction::Pow(_, a, b) => Some(Instruction::Pow(r, a.clone(), b.clone())),
        Instruction::Load(_, a, b) => Some(Instruction::Load(r, a.clone(), b.clone())),
        Instruction::LoadSlot(_, a, b, c) => {
            Some(Instruction::LoadSlot(r, a.clone(), b.clone(), c.clone()))
        }
        Instruction::LoadBatch(_, a, b, c) => {
            Some(Instruction::LoadBatch(r, a.clone(), b.clone(), c.clone()))
        }
        Instruction::LoadBatchNamed(_, a, b, c, d) => Some(Instruction::LoadBatchNamed(
            r,
            a.clone(),
            b.clone(),
            c.clone(),
            d.clone(),
        )),
        Instruction::LoadReagent(_, b, c, d) => {
            Some(Instruction::LoadReagent(r, b.clone(), c.clone(), d.clone()))
        }
        Instruction::SetEq(_, a, b) => Some(Instruction::SetEq(r, a.clone(), b.clone())),
        Instruction::SetNe(_, a, b) => Some(Instruction::SetNe(r, a.clone(), b.clone())),
        Instruction::SetGt(_, a, b) => Some(Instruction::SetGt(r, a.clone(), b.clone())),
        Instruction::SetLt(_, a, b) => Some(Instruction::SetLt(r, a.clone(), b.clone())),
        Instruction::SetGe(_, a, b) => Some(Instruction::SetGe(r, a.clone(), b.clone())),
        Instruction::SetLe(_, a, b) => Some(Instruction::SetLe(r, a.clone(), b.clone())),
        Instruction::And(_, a, b) => Some(Instruction::And(r, a.clone(), b.clone())),
        Instruction::Or(_, a, b) => Some(Instruction::Or(r, a.clone(), b.clone())),
        Instruction::Xor(_, a, b) => Some(Instruction::Xor(r, a.clone(), b.clone())),
        Instruction::Peek(_) => Some(Instruction::Peek(r)),
        Instruction::Get(_, a, b) => Some(Instruction::Get(r, a.clone(), b.clone())),
        Instruction::Select(_, a, b, c) => {
            Some(Instruction::Select(r, a.clone(), b.clone(), c.clone()))
        }
        Instruction::Rand(_) => Some(Instruction::Rand(r)),
        Instruction::Pop(_) => Some(Instruction::Pop(r)),
        Instruction::Acos(_, a) => Some(Instruction::Acos(r, a.clone())),
        Instruction::Asin(_, a) => Some(Instruction::Asin(r, a.clone())),
        Instruction::Atan(_, a) => Some(Instruction::Atan(r, a.clone())),
        Instruction::Atan2(_, a, b) => Some(Instruction::Atan2(r, a.clone(), b.clone())),
        Instruction::Abs(_, a) => Some(Instruction::Abs(r, a.clone())),
        Instruction::Ceil(_, a) => Some(Instruction::Ceil(r, a.clone())),
        Instruction::Cos(_, a) => Some(Instruction::Cos(r, a.clone())),
        Instruction::Floor(_, a) => Some(Instruction::Floor(r, a.clone())),
        Instruction::Log(_, a) => Some(Instruction::Log(r, a.clone())),
        Instruction::Max(_, a, b) => Some(Instruction::Max(r, a.clone(), b.clone())),
        Instruction::Min(_, a, b) => Some(Instruction::Min(r, a.clone(), b.clone())),
        Instruction::Sin(_, a) => Some(Instruction::Sin(r, a.clone())),
        Instruction::Sqrt(_, a) => Some(Instruction::Sqrt(r, a.clone())),
        Instruction::Tan(_, a) => Some(Instruction::Tan(r, a.clone())),
        Instruction::Trunc(_, a) => Some(Instruction::Trunc(r, a.clone())),
        Instruction::Rmap(_, a, b) => Some(Instruction::Rmap(r, a.clone(), b.clone())),
        _ => None,
    }
}

/// Checks if a register is read by an instruction.
pub fn reg_is_read(instr: &Instruction, reg: u8) -> bool {
    let check = |op: &Operand| matches!(op, Operand::Register(r) if *r == reg);

    match instr {
        Instruction::Move(_, a) => check(a),
        Instruction::Add(_, a, b)
        | Instruction::Sub(_, a, b)
        | Instruction::Mul(_, a, b)
        | Instruction::Div(_, a, b)
        | Instruction::Mod(_, a, b)
        | Instruction::Pow(_, a, b) => check(a) || check(b),
        Instruction::Load(_, a, _) => check(a),
        Instruction::Store(a, _, b) => check(a) || check(b),
        Instruction::StoreBatch(a, _, b) => check(a) || check(b),
        Instruction::StoreBatchNamed(a, b, _, c) => check(a) || check(b) || check(c),
        Instruction::StoreSlot(a, b, _, c) => check(a) || check(b) || check(c),
        Instruction::BranchEq(a, b, _)
        | Instruction::BranchNe(a, b, _)
        | Instruction::BranchGt(a, b, _)
        | Instruction::BranchLt(a, b, _)
        | Instruction::BranchGe(a, b, _)
        | Instruction::BranchLe(a, b, _) => check(a) || check(b),
        Instruction::BranchEqZero(a, _) | Instruction::BranchNeZero(a, _) => check(a),
        Instruction::LoadReagent(_, device, _, item_hash) => check(device) || check(item_hash),
        Instruction::Rmap(_, device, reagent_hash) => check(device) || check(reagent_hash),
        Instruction::LoadSlot(_, dev, slot, _) => check(dev) || check(slot),
        Instruction::LoadBatch(_, dev, _, mode) => check(dev) || check(mode),
        Instruction::LoadBatchNamed(_, d_hash, n_hash, _, mode) => {
            check(d_hash) || check(n_hash) || check(mode)
        }
        Instruction::SetEq(_, a, b)
        | Instruction::SetNe(_, a, b)
        | Instruction::SetGt(_, a, b)
        | Instruction::SetLt(_, a, b)
        | Instruction::SetGe(_, a, b)
        | Instruction::SetLe(_, a, b)
        | Instruction::And(_, a, b)
        | Instruction::Or(_, a, b)
        | Instruction::Xor(_, a, b) => check(a) || check(b),
        Instruction::Push(a) => check(a),
        Instruction::Get(_, a, b) => check(a) || check(b),
        Instruction::Put(a, b, c) => check(a) || check(b) || check(c),
        Instruction::Select(_, a, b, c) => check(a) || check(b) || check(c),
        Instruction::Sleep(a) => check(a),
        Instruction::Acos(_, a)
        | Instruction::Asin(_, a)
        | Instruction::Atan(_, a)
        | Instruction::Abs(_, a)
        | Instruction::Ceil(_, a)
        | Instruction::Cos(_, a)
        | Instruction::Floor(_, a)
        | Instruction::Log(_, a)
        | Instruction::Sin(_, a)
        | Instruction::Sqrt(_, a)
        | Instruction::Tan(_, a)
        | Instruction::Trunc(_, a) => check(a),
        Instruction::Atan2(_, a, b) | Instruction::Max(_, a, b) | Instruction::Min(_, a, b) => {
            check(a) || check(b)
        }
        Instruction::JumpRelative(a) => check(a),
        Instruction::Alias(_, a) => check(a),
        _ => false,
    }
}
