1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
use super::opcode::*;
use super::environment::*;
use super::registers::*;

/*
    From "The undocumented Z80 documented" TUZD-4.4:

Officially the Z80 has an 8 bit I/O port address space. When using the I/O ports, the 16 address
lines are used. And in fact, the high 8 bit do actually have some value, so you can use 65536
ports after all. IN r,(C), OUT (C),r, and the Block I/O instructions actually place the entire BC
register on the address bus. Similarly IN A,(n) and OUT (n),A put A × 256 + n on the address
bus.
The INI/INIR/IND/INDR instructions use BC after decrementing B, and the OUTI/OTIR/OUTD/OTDR
instructions before.
*/


pub fn build_out_c_r(r: Reg8) -> Opcode {
    Opcode::new(
        format!("OUT (C), {}", r),
        Box::new(move |env: &mut Environment| {
            let address = env.state.reg.get16(Reg16::BC);
            let value = env.state.reg.get8(r);
            env.port_out(address, value);
        })
    )
}

pub fn build_out_c_0() -> Opcode {
    Opcode::new(
        "OUT (C), 0".to_string(),
        Box::new(move |env: &mut Environment| {
            let address = env.state.reg.get16(Reg16::BC);
            env.port_out(address, 0);
        })
    )
}

pub fn build_out_n_a() -> Opcode {
    Opcode::new(
        "OUT (n), A".to_string(),
        Box::new(move |env: &mut Environment| {
            let a = env.state.reg.a();
            let address = ((a as u16) << 8) + env.advance_pc() as u16;
            env.port_out(address, a);
        })
    )
}

pub fn build_in_r_c(r: Reg8) -> Opcode {
    Opcode::new(
        format!("IN {}, (C)", r),
        Box::new(move |env: &mut Environment| {
            let address = env.state.reg.get16(Reg16::BC);
            let value = env.port_in(address);
            env.state.reg.set8(r, value);

            env.state.reg.update_bits_in_flags(value);
        })
    )
}

pub fn build_in_0_c() -> Opcode {
    Opcode::new(
        "IN (C)".to_string(),
        Box::new(move |env: &mut Environment| {
            let address = env.state.reg.get16(Reg16::BC);
            let value = env.port_in(address);

            env.state.reg.update_bits_in_flags(value);
        })
    )
}

pub fn build_in_a_n() -> Opcode {
    Opcode::new(
        "IN A, (n)".to_string(),
        Box::new(move |env: &mut Environment| {
            let a = env.state.reg.a();
            let address = ((a as u16) << 8) + env.advance_pc() as u16;
            let value = env.port_in(address);
            env.state.reg.set_a(value);
        })
    )
}

/*
, and the OUTI/OTIR/OUTD/OTDR
instructions before.
*/

pub fn build_in_block((inc, repeat, postfix) : (bool, bool, &'static str)) -> Opcode {
    Opcode::new(
        format!("IN{}", postfix),
        Box::new(move |env: &mut Environment| {
            // The INI/INIR/IND/INDR instructions use BC after decrementing B
            let b = env.state.reg.inc_dec8(Reg8::B, false /* decrement */);
            let address = env.state.reg.get16(Reg16::BC);

            let value = env.port_in(address);
            // We won't have IX and IY cases to consider
            env.set_reg(Reg8::_HL, value);
            env.state.reg.inc_dec16(Reg16::HL, inc);

            // TUZD-4.3
            let mut j = env.state.reg.get8(Reg8::C) as u16;
            j = if inc {j+1} else {j-1};
            let k = value as u16 + (j & 0xff);
            env.state.reg.update_block_flags(value, k, b);

            if repeat && b != 0 {
                // Back to redo the instruction
                env.set_branch_taken();
                let pc = env.state.reg.pc().wrapping_sub(2);
                env.state.reg.set_pc(pc);
            }
        })
    )
}

pub fn build_out_block((inc, repeat, postfix) : (bool, bool, &'static str)) -> Opcode {
    let n0 = if repeat {"OT"} else {"OUT"};
    Opcode::new(
        format!("{}{}", n0, postfix),
        Box::new(move |env: &mut Environment| {
            // the OUTI/OTIR/OUTD/OTDR instructions use BC before decrementing B
            let address = env.state.reg.get16(Reg16::BC);
            let b = env.state.reg.inc_dec8(Reg8::B, false /* decrement */);

            // We won't have IX and IY cases to consider
            let value = env.reg8_ext(Reg8::_HL);
            env.port_out(address, value);
            env.state.reg.inc_dec16(Reg16::HL, inc);

            // TUZD-4.3
            let k = value as u16 + env.state.reg.get8(Reg8::L) as u16;
            env.state.reg.update_block_flags(value, k, b);

            if repeat && b != 0 {
                // Back to redo the instruction
                env.set_branch_taken();
                let pc = env.state.reg.pc().wrapping_sub(2);
                env.state.reg.set_pc(pc);
            }
        })
    )
}