##
## This file is part of the libsigrokdecode project.
##
## Copyright (C) 2014 Daniel Elstner <daniel.kitta@gmail.com>
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 3 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program; if not, see <http://www.gnu.org/licenses/>.
##

import sigrokdecode as srd
from functools import reduce
from .tables import instr_table_by_prefix
import string

class Ann:
    ADDR, MEMRD, MEMWR, IORD, IOWR, INSTR, ROP, WOP, WARN = range(9)
class Row:
    ADDRBUS, DATABUS, INSTRUCTIONS, OPERANDS, WARNINGS = range(5)
class Pin:
    D0, D7 = 0, 7
    M1, RD, WR, MREQ, IORQ = range(8, 13)
    A0, A15 = 13, 28
class Cycle:
    NONE, MEMRD, MEMWR, IORD, IOWR, FETCH, INTACK = range(7)

# Provide custom format type 'H' for hexadecimal output
# with leading decimal digit (assembler syntax).
class AsmFormatter(string.Formatter):
    def format_field(self, value, format_spec):
        if format_spec.endswith('H'):
            result = format(value, format_spec[:-1] + 'X')
            return result if result[0] in string.digits else '0' + result
        else:
            return format(value, format_spec)

formatter = AsmFormatter()

ann_data_cycle_map = {
    Cycle.MEMRD:  Ann.MEMRD,
    Cycle.MEMWR:  Ann.MEMWR,
    Cycle.IORD:   Ann.IORD,
    Cycle.IOWR:   Ann.IOWR,
    Cycle.FETCH:  Ann.MEMRD,
    Cycle.INTACK: Ann.IORD,
}

def reduce_bus(bus):
    if 0xFF in bus:
        return None # unassigned bus channels
    else:
        return reduce(lambda a, b: (a << 1) | b, reversed(bus))

def signed_byte(byte):
    return byte if byte < 128 else byte - 256

class Decoder(srd.Decoder):
    api_version = 2
    id       = 'z80'
    name     = 'Z80'
    longname = 'Zilog Z80 CPU'
    desc     = 'Zilog Z80 microprocessor disassembly.'
    license  = 'gplv3+'
    inputs   = ['logic']
    outputs  = ['z80']
    channels = tuple({
            'id': 'd%d' % i,
            'name': 'D%d' % i,
            'desc': 'Data bus line %d' % i
            } for i in range(8)
    ) + (
        {'id': 'm1', 'name': '/M1', 'desc': 'Machine cycle 1'},
        {'id': 'rd', 'name': '/RD', 'desc': 'Memory or I/O read'},
        {'id': 'wr', 'name': '/WR', 'desc': 'Memory or I/O write'},
    )
    optional_channels = (
        {'id': 'mreq', 'name': '/MREQ', 'desc': 'Memory request'},
        {'id': 'iorq', 'name': '/IORQ', 'desc': 'I/O request'},
    ) + tuple({
        'id': 'a%d' % i,
        'name': 'A%d' % i,
        'desc': 'Address bus line %d' % i
        } for i in range(16)
    )
    annotations = (
        ('addr',  'Memory or I/O address'),
        ('memrd', 'Byte read from memory'),
        ('memwr', 'Byte written to memory'),
        ('iord',  'Byte read from I/O port'),
        ('iowr',  'Byte written to I/O port'),
        ('instr', 'Z80 CPU instruction'),
        ('rop',   'Value of input operand'),
        ('wop',   'Value of output operand'),
        ('warn',  'Warning message'),
    )
    annotation_rows = (
        ('addrbus', 'Address bus', (Ann.ADDR,)),
        ('databus', 'Data bus', (Ann.MEMRD, Ann.MEMWR, Ann.IORD, Ann.IOWR)),
        ('instructions', 'Instructions', (Ann.INSTR,)),
        ('operands', 'Operands', (Ann.ROP, Ann.WOP)),
        ('warnings', 'Warnings', (Ann.WARN,))
    )

    def __init__(self):
        self.prev_cycle = Cycle.NONE
        self.op_state   = self.state_IDLE

    def start(self):
        self.out_ann    = self.register(srd.OUTPUT_ANN)
        self.bus_data   = None
        self.samplenum  = None
        self.addr_start = None
        self.data_start = None
        self.dasm_start = None
        self.pend_addr  = None
        self.pend_data  = None
        self.ann_data   = None
        self.ann_dasm   = None
        self.prev_cycle = Cycle.NONE
        self.op_state   = self.state_IDLE
        self.instr_len  = 0

    def decode(self, ss, es, data):
        for (self.samplenum, pins) in data:
            data.itercnt += 1
            cycle = Cycle.NONE
            if pins[Pin.MREQ] != 1: # default to asserted
                if pins[Pin.RD] == 0:
                    cycle = Cycle.FETCH if pins[Pin.M1] == 0 else Cycle.MEMRD
                elif pins[Pin.WR] == 0:
                    cycle = Cycle.MEMWR
            elif pins[Pin.IORQ] == 0: # default to not asserted
                if pins[Pin.M1] == 0:
                    cycle = Cycle.INTACK
                elif pins[Pin.RD] == 0:
                    cycle = Cycle.IORD
                elif pins[Pin.WR] == 0:
                    cycle = Cycle.IOWR

            if cycle != Cycle.NONE:
                self.bus_data = reduce_bus(pins[Pin.D0:Pin.D7+1])
            if cycle != self.prev_cycle:
                if self.prev_cycle == Cycle.NONE:
                    self.on_cycle_begin(reduce_bus(pins[Pin.A0:Pin.A15+1]))
                elif cycle == Cycle.NONE:
                    self.on_cycle_end()
                else:
                    self.on_cycle_trans()
            self.prev_cycle = cycle


    def on_cycle_begin(self, bus_addr):
        if self.pend_addr is not None:
            self.put_text(self.addr_start, Ann.ADDR,
                          '{:04X}'.format(self.pend_addr))
        self.addr_start = self.samplenum
        self.pend_addr  = bus_addr

    def on_cycle_end(self):
        self.instr_len += 1
        self.op_state = self.op_state()
        if self.ann_dasm is not None:
            self.put_disasm()
        if self.op_state == self.state_RESTART:
            self.op_state = self.state_IDLE()

        if self.ann_data is not None:
            self.put_text(self.data_start, self.ann_data,
                          '{:02X}'.format(self.pend_data))
        self.data_start = self.samplenum
        self.pend_data  = self.bus_data
        self.ann_data   = ann_data_cycle_map[self.prev_cycle]

    def on_cycle_trans(self):
        self.put_text(self.samplenum - 1, Ann.WARN,
                      'Illegal transition between control states')
        self.pend_addr = None
        self.ann_data  = None
        self.ann_dasm  = None

    def put_disasm(self):
        text = formatter.format(self.mnemonic, r=self.arg_reg, d=self.arg_dis,
                                j=self.arg_dis+self.instr_len, i=self.arg_imm,
                                ro=self.arg_read, wo=self.arg_write)
        self.put_text(self.dasm_start, self.ann_dasm, text)
        self.ann_dasm   = None
        self.dasm_start = self.samplenum

    def put_text(self, ss, ann_idx, ann_text):
        self.put(ss, self.samplenum, self.out_ann, [ann_idx, [ann_text]])

    def state_RESTART(self):
        return self.state_IDLE

    def state_IDLE(self):
        if self.prev_cycle != Cycle.FETCH:
            return self.state_IDLE
        self.want_dis   = 0
        self.want_imm   = 0
        self.want_read  = 0
        self.want_write = 0
        self.want_wr_be = False
        self.op_repeat  = False
        self.arg_dis    = 0
        self.arg_imm    = 0
        self.arg_read   = 0
        self.arg_write  = 0
        self.arg_reg    = ''
        self.mnemonic   = ''
        self.instr_pend = False
        self.read_pend  = False
        self.write_pend = False
        self.dasm_start = self.samplenum
        self.op_prefix  = 0
        self.instr_len  = 0
        if self.bus_data in (0xCB, 0xED, 0xDD, 0xFD):
            return self.state_PRE1
        else:
            return self.state_OPCODE

    def state_PRE1(self):
        if self.prev_cycle != Cycle.FETCH:
            self.mnemonic = 'Prefix not followed by fetch'
            self.ann_dasm = Ann.WARN
            return self.state_RESTART
        self.op_prefix = self.pend_data
        if self.op_prefix in (0xDD, 0xFD):
            if self.bus_data == 0xCB:
                return self.state_PRE2
            if self.bus_data in (0xDD, 0xED, 0xFD):
                return self.state_PRE1
        return self.state_OPCODE

    def state_PRE2(self):
        if self.prev_cycle != Cycle.MEMRD:
            self.mnemonic = 'Missing displacement'
            self.ann_dasm = Ann.WARN
            return self.state_RESTART
        self.op_prefix = (self.op_prefix << 8) | self.pend_data
        return self.state_PREDIS

    def state_PREDIS(self):
        if self.prev_cycle != Cycle.MEMRD:
            self.mnemonic = 'Missing opcode'
            self.ann_dasm = Ann.WARN
            return self.state_RESTART
        self.arg_dis = signed_byte(self.pend_data)
        return self.state_OPCODE

    def state_OPCODE(self):
        (table, self.arg_reg) = instr_table_by_prefix[self.op_prefix]
        self.op_prefix = 0
        instruction = table.get(self.pend_data, None)
        if instruction is None:
            self.mnemonic = 'Invalid instruction'
            self.ann_dasm = Ann.WARN
            return self.state_RESTART
        (self.want_dis, self.want_imm, self.want_read, want_write,
                self.op_repeat, self.mnemonic) = instruction
        self.want_write = abs(want_write)
        self.want_wr_be = (want_write < 0)
        if self.want_dis > 0:
            return self.state_POSTDIS
        if self.want_imm > 0:
            return self.state_IMM1
        self.ann_dasm = Ann.INSTR
        if self.want_read > 0 and self.prev_cycle in (Cycle.MEMRD, Cycle.IORD):
            return self.state_ROP1
        if self.want_write > 0 and self.prev_cycle in (Cycle.MEMWR, Cycle.IOWR):
            return self.state_WOP1
        return self.state_RESTART

    def state_POSTDIS(self):
        self.arg_dis = signed_byte(self.pend_data)
        if self.want_imm > 0:
            return self.state_IMM1
        self.ann_dasm = Ann.INSTR
        if self.want_read > 0 and self.prev_cycle in (Cycle.MEMRD, Cycle.IORD):
            return self.state_ROP1
        if self.want_write > 0 and self.prev_cycle in (Cycle.MEMWR, Cycle.IOWR):
            return self.state_WOP1
        return self.state_RESTART

    def state_IMM1(self):
        self.arg_imm = self.pend_data
        if self.want_imm > 1:
            return self.state_IMM2
        self.ann_dasm = Ann.INSTR
        if self.want_read > 0 and self.prev_cycle in (Cycle.MEMRD, Cycle.IORD):
            return self.state_ROP1
        if self.want_write > 0 and self.prev_cycle in (Cycle.MEMWR, Cycle.IOWR):
            return self.state_WOP1
        return self.state_RESTART

    def state_IMM2(self):
        self.arg_imm |= self.pend_data << 8
        self.ann_dasm = Ann.INSTR
        if self.want_read > 0 and self.prev_cycle in (Cycle.MEMRD, Cycle.IORD):
            return self.state_ROP1
        if self.want_write > 0 and self.prev_cycle in (Cycle.MEMWR, Cycle.IOWR):
            return self.state_WOP1
        return self.state_RESTART

    def state_ROP1(self):
        self.arg_read = self.pend_data
        if self.want_read < 2:
            self.mnemonic = '{ro:02X}'
            self.ann_dasm = Ann.ROP
        if self.want_write > 0:
            return self.state_WOP1
        if self.want_read > 1:
            return self.state_ROP2
        if self.op_repeat and self.prev_cycle in (Cycle.MEMRD, Cycle.IORD):
            return self.state_ROP1
        return self.state_RESTART

    def state_ROP2(self):
        self.arg_read |= self.pend_data << 8
        self.mnemonic = '{ro:04X}'
        self.ann_dasm = Ann.ROP
        if self.want_write > 0 and self.prev_cycle in (Cycle.MEMWR, Cycle.IOWR):
            return self.state_WOP1
        return self.state_RESTART

    def state_WOP1(self):
        self.arg_write = self.pend_data
        if self.want_read > 1:
            return self.state_ROP2
        if self.want_write > 1:
            return self.state_WOP2
        self.mnemonic = '{wo:02X}'
        self.ann_dasm = Ann.WOP
        if self.want_read > 0 and self.op_repeat and \
                self.prev_cycle in (Cycle.MEMRD, Cycle.IORD):
            return self.state_ROP1
        return self.state_RESTART

    def state_WOP2(self):
        if self.want_wr_be:
            self.arg_write = (self.arg_write << 8) | self.pend_data
        else:
            self.arg_write |= self.pend_data << 8
        self.mnemonic = '{wo:04X}'
        self.ann_dasm = Ann.WOP
        return self.state_RESTART
