#!/usr/bin/env python3
import sys

verbose = False

prologue = '"""' + """Opcodes file.

    Tables for the assembly of 6502-family instructions, mapping
    opcodes and addressing modes to binary instructions.""" + '"""' + """

# Copyright 2002-2024 Michael C. Martin and additional contributors.
# You may use, modify, and distribute this file under the MIT
# license: See README for details.

# DO NOT EDIT THIS FILE DIRECTLY.
# This file was automatically generated by gensets.py based on the
# the tables in tools/opcodes. Edit those tables, not these.

# Names of addressing modes
modes = ["Implied",
         "Immediate",
         "ImmediateLong",
         "Zero Page",
         "Zero Page, X",
         "Zero Page, Y",
         "Absolute",
         "Absolute, X",
         "Absolute, Y",
         "(Absolute)",
         "(Absolute, X)",
         "(Absolute), Y",
         "(Absolute), Z",
         "(Zero Page)",
         "(Zero Page, X)",
         "(Zero Page), Y",
         "(Zero Page, SP), Y",
         "(Zero Page), Z",
         "Relative",
         "RelativeLong",
         "Zero Page, Relative"]


# Lengths of the argument
lengths = [0, 1, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 2, 2]
"""

# These values should match the ones in the prologue string.
modes = ["Implied",
         "Immediate",
         "Immediate.W",
         "Zero Page",
         "Zero Page, X",
         "Zero Page, Y",
         "Absolute",
         "Absolute, X",
         "Absolute, Y",
         "(Absolute)",
         "(Absolute, X)",
         "(Absolute), Y",
         "(Absolute), Z",
         "(Zero Page)",
         "(Zero Page, X)",
         "(Zero Page), Y",
         "(Zero Page, SP), Y",
         "(Zero Page), Z",
         "Relative",
         "RelativeLong",
         "Zero Page, Relative"]


flatmodes = [x.lower() for x in modes]


# WARNING: This decommenter assumes that # never appears anywhere else
# in a line.
def decomment(l):
    if '#' in l:
        l = l[:l.index('#')]
    return l.strip()


def decomment_readlines(fname):
    result = [decomment(x) for x in open(fname, "rt").readlines()]
    return [x for x in result if len(x) > 0]


def parse_chipset_file(fname):
    result = [None] * 256
    ls = [[x.strip() for x in y]
          for y in [z.split(':', 1) for z in decomment_readlines(fname)]]
    for l in ls:
        if len(l) == 2:
            try:
                op = int(l[0], 16)
                syns = l[1].split(';')
                for s in syns:
                    if s.strip() == "":
                        continue
                    s_p = s.split('-')
                    if len(s_p) == 2:
                        mnem = s_p[0].lower().strip()
                        mode = s_p[1].lower().strip()
                        if mode in flatmodes:
                            if result[op] is None:
                                result[op] = []
                            result[op].append((mnem, flatmodes.index(mode)))
                        else:
                            print(f"Unknown mode \"{s_p[1]}\"", file=sys.stderr)
                    else:
                        print(f"Malformed instruction \"{s}\"", file=sys.stderr)
            except ValueError:
                print(f"Illegal opcode \"{l[0]}\"", file=sys.stderr)
    return result


def collate_chipset_map(cs_list, base):
    result = {}
    for (opcode, insts) in zip(list(range(256)), cs_list):
        if insts is not None:
            for inst in insts:
                (mnem, mode) = inst
                if mnem not in result:
                    result[mnem] = [None] * len(modes)
                if result[mnem][mode] is not None:
                    print(f"Warning: Reassigning {mnem} - {modes[mode]}", file=sys.stderr)
                result[mnem][mode] = opcode
    if base is not None:
        todel = []
        for x in result:
            if x in base:
                if result[x] == base[x]:
                    todel.append(x)
                elif verbose != 0:
                    print(f"# Opcode {x} changed")
            elif verbose != 0:
                print(f"# Opcode {x} added")
        for x in todel:
            del result[x]
    return result


def mapval(x):
    if x is None:
        return "None"
    else:
        return "0x%02X" % x


def dump_map(m, prologue=''):
    mnems = list(m.keys())
    mnems.sort()
    for mnem in mnems:
        codes = [mapval(x) for x in m[mnem]]
        print("%s'%s': [%s,\n%s     %s]," % (prologue, mnem,
                                             ', '.join(codes[:8]),
                                             prologue + " " * len(mnem),
                                             ', '.join(codes[8:])))


if __name__ == '__main__':
    if len(sys.argv) > 1:
        chipsets = sys.argv[1:]
    else:
        chipsets = ['chipsets.txt']
    archs = []
    for x in chipsets:
        try:
            for line in decomment_readlines(x):
                l = [x.strip() for x in line.split(":", 1)]
                if len(l) != 2:
                    print(f"Could not parse the chipset line \"{line}\"" , file=sys.stderr)
                else:
                    archs.append((l[0], l[1]))
        except IOError:
            print(f"Could not read file {x}", file=sys.stderr)
    print(prologue)
    baseset = None
    for (field, fname) in archs:
        chipset_list = parse_chipset_file(fname)
        instruction_map = collate_chipset_map(chipset_list, baseset)
        if baseset is None:
            baseset = instruction_map
        print("%s = {" % field)
        dump_map(instruction_map, ' ' * (len(field) + 4))
        print("%s}" % (' ' * (len(field) + 3)))
