#!/usr/bin/python
# license:BSD-3-Clause
# copyright-holders:Olivier Galibert

from __future__ import print_function

USAGE = """
Usage:
%s prefix {opc.lst|-} disp.lst device.inc deviced.inc
"""
import sys
import logging

MAX_STATES = 0

def load_opcodes(fname):
    """Load opcodes from .lst file"""
    opcodes = []
    logging.info("load_opcodes: %s", fname)
    try:
        f = open(fname, "rU")
    except Exception:
        err = sys.exc_info()[1]
        logging.error("cannot read opcodes file %s [%s]", fname, err)
        sys.exit(1)

    for line in f:
        if line.startswith("#"): continue
        line = line.rstrip()
        if not line: continue
        if line.startswith(" ") or line.startswith("\t"):
            # append instruction to last opcode
            opcodes[-1][1].append(line)
        else:
            # add new opcode
            opcodes.append((line, []))
    return opcodes


def load_disp(fname):
    logging.info("load_disp: %s", fname)
    states = []
    try:
        f = open(fname, "rU")
    except Exception:
        err = sys.exc_info()[1]
        logging.error("cannot read display file %s [%s]", fname, err)
        sys.exit(1)
    for line in f:
        if line.startswith("#"): continue
        line = line.strip()
        if not line: continue
        tokens = line.split()
        states += tokens
    return states

def emit(f, text):
    """write string to file"""
    print(text, file=f)

FULL_PROLOG="""\
void %(device)s_device::%(opcode)s_full()
{
"""

FULL_EPILOG="""\
}
"""

FULL_EAT_ALL="""\
\ticount=0; inst_substate = %(substate)s; return;
"""

FULL_MEMORY="""\
\tif(icount == 0) { inst_substate = %(substate)s; return; }
%(ins)s
\ticount--;
"""

FULL_NONE="""\
%(ins)s
"""

PARTIAL_PROLOG="""\
void %(device)s_device::%(opcode)s_partial()
{
switch(inst_substate) {
case 0:
"""

PARTIAL_EPILOG="""\
}
\tinst_substate = 0;
}

"""

PARTIAL_EAT_ALL="""\
\ticount=0; inst_substate = %(substate)s; return;
case %(substate)s:;
"""

PARTIAL_MEMORY="""\
\tif(icount == 0) { inst_substate = %(substate)s; return; }
case %(substate)s:
%(ins)s
\ticount--;
"""

PARTIAL_NONE="""\
%(ins)s
"""
def identify_line_type(ins):
    if "eat-all-cycles" in ins: return "EAT"
    for s in ["read", "write", "prefetch(", "prefetch_noirq("]:
        if s in ins:
            return "MEMORY"
    return "NONE"


def save_opcodes(f, device, opcodes):
    for name, instructions in opcodes:
        d = { "device": device,
              "opcode": name,
              }

        emit(f, FULL_PROLOG % d)
        substate = 1
        for ins in instructions:
            d["substate"] = str(substate)
            d["ins"] =  ins
            line_type = identify_line_type(ins)
            if line_type == "EAT":
                emit(f, FULL_EAT_ALL % d)
                substate += 1
            elif line_type == "MEMORY":
                emit(f, FULL_MEMORY % d)
                substate += 1
            else:
                emit(f, FULL_NONE %d)
        emit(f, FULL_EPILOG % d)

        emit(f, PARTIAL_PROLOG % d)
        substate = 1
        for ins in instructions:
            d["substate"] = str(substate)
            d["ins"] =  ins
            line_type = identify_line_type(ins)
            if line_type == "EAT":
                emit(f, PARTIAL_EAT_ALL % d)
                substate += 1
            elif line_type == "MEMORY":
                emit(f, PARTIAL_MEMORY % d)
                substate += 1
            else:
                emit(f, PARTIAL_NONE %d)
        emit(f, PARTIAL_EPILOG % d)


DO_EXEC_FULL_PROLOG="""\
void %(device)s_device::do_exec_full()
{
\tswitch(inst_state) {
"""

DO_EXEC_FULL_EPILOG="""\
\t}
}
"""

DO_EXEC_PARTIAL_PROLOG="""\
void %(device)s_device::do_exec_partial()
{
\tswitch(inst_state) {
"""

DO_EXEC_PARTIAL_EPILOG="""\
\t}
}
"""

DISASM_PROLOG="""\
const %(device)s_disassembler::disasm_entry %(device)s_disassembler::disasm_entries[0x%(disasm_count)x] = {
"""

DISASM_EPILOG="""\
};
"""

def save_tables(f, device, states):
    total_states = len(states)

    d = { "device": device,
          "disasm_count": total_states-1
          }
    
    emit(f, DO_EXEC_FULL_PROLOG % d)
    for n, state in enumerate(states):
        if state == ".": continue
        if n < total_states - 1:
            emit(f, "\tcase 0x%02x: %s_full(); break;" % (n, state))
        else:
            emit(f, "\tcase %s: %s_full(); break;" % ("STATE_RESET", state))
    emit(f, DO_EXEC_FULL_EPILOG % d)

    emit(f, DO_EXEC_PARTIAL_PROLOG % d)
    for n, state in enumerate(states):
        if state == ".": continue
        if n < total_states - 1:
            emit(f, "\tcase 0x%02x: %s_partial(); break;" % (n, state))
        else:
            emit(f, "\tcase %s: %s_partial(); break;" % ("STATE_RESET", state))
    emit(f, DO_EXEC_PARTIAL_EPILOG % d)

def save_dasm(f, device, states):
    total_states = len(states)

    d = { "device": device,
          "disasm_count": total_states-1
          }
    
    emit(f, DISASM_PROLOG % d )
    for n, state in enumerate(states):
        if state == ".": continue
        if n == total_states - 1: break
        tokens = state.split("_")
        opc = tokens[0]
        mode = tokens[-1]
        extra = "0"
        if opc in ["jsr", "bsr"]:
            extra =  "STEP_OVER"
        elif opc in ["rts", "rti", "rtn"]:
            extra = "STEP_OUT"
        emit(f, '\t{ "%s", DASM_%s, %s },' % (opc, mode, extra))
    emit(f, DISASM_EPILOG % d)

def saves(fname, device, opcodes, states):
    logging.info("saving: %s", fname)
    try:
        f = open(fname, "w")
    except Exception:
        err = sys.exc_info()[1]
        logging.error("cannot write file %s [%s]", fname, err)
        sys.exit(1)
    save_opcodes(f, device, opcodes)
    emit(f, "\n")
    save_tables(f, device, states)
    f.close()


def saved(fname, device, opcodes, states):
    logging.info("saving: %s", fname)
    try:
        f = open(fname, "w")
    except Exception:
        err = sys.exc_info()[1]
        logging.error("cannot write file %s [%s]", fname, err)
        sys.exit(1)
    save_dasm(f, device, states)
    f.close()


def main(argv):
    debug = False
    logformat=("%(levelname)s:"
               "%(module)s:"
               "%(lineno)d:"
               "%(threadName)s:"
               "%(message)s")
    if debug:
        logging.basicConfig(level=logging.INFO, format=logformat)
    else:
        logging.basicConfig(level=logging.WARNING, format=logformat)


    if len(argv) != 6:
        print(USAGE % argv[0])
        return 1

    mode = argv[1]
    device_name = argv[2]

    opcodes = []
    if argv[3] !=  "-":
        opcodes = load_opcodes(argv[3])
        logging.info("found %d opcodes", len(opcodes))
    else:
        logging.info("skipping opcode reading")


    states = load_disp(argv[4])
    logging.info("loaded %s states", len(states))

    assert (len(states) & 0xff) == 1
    if mode == 's':
        saves(argv[5], device_name, opcodes, states)
    else:
        saved(argv[5], device_name, opcodes, states)


# ======================================================================
if __name__ == "__main__":
    sys.exit(main(sys.argv))

