"""The Ophis program lister

    When displaying an assembled binary for human inspection, it is
    traditional to mix binary with reconstructed instructions that
    have all arguments precomputed. This class manages that."""

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

import sys


class Listing(object):
    """Encapsulates the program listing. Accepts fully formatted
    instruction strings, or batches of data bytes. Batches of data
    bytes are assumed to be contiguous unless a divider is explicitly
    requested."""

    def __init__(self, fname):
        self.listing = [(0, [])]
        self.filename = fname

    def listInstruction(self, inst):
        "Add a preformatted instruction list to the listing."
        self.listing.append(inst)

    def listDivider(self, newpc):
        "Indicate that the next data block will begin at the given PC."
        self.listing.append((newpc, []))

    def listData(self, vals, pc):
        """Add a batch of data to the listing. If this starts a new
        batch of data, begin that batch at the listed PC."""
        if type(self.listing[-1]) is not tuple:
            self.listing.append((pc, []))
        self.listing[-1][1].extend(vals)

    def dump(self):
        if self.filename == "-":
            out = sys.stdout
        else:
            out = open(self.filename, "wt")
        for x in self.listing:
            if type(x) is str:
                print(x, file=out)
            elif type(x) is tuple:
                i = 0
                pc = x[0]
                while True:
                    row = x[1][i:i + 16]
                    if row == []:
                        break
                    dataline = " %04X " % (pc + i)
                    dataline += (" %02X" * len(row)) % tuple(row)
                    charline = ""
                    for c in row:
                        if c < 32 or c > 126:
                            charline += "."
                        else:
                            charline += chr(c)
                    print("%-54s  |%-16s|" % (dataline, charline), file=out)
                    i += 16
        if self.filename != "-":
            out.close()


class NullLister(object):
    "A dummy Lister that actually does nothing."
    def listInstruction(self, inst):
        pass

    def listDivider(self, newpc):
        pass

    def listData(self, vals, pc):
        pass

    def dump(self):
        pass


class LabelMapper(object):
    """Encapsulates the label map. Accepts label names, string
    representations of program points, and the location."""
    def __init__(self, fname):
        self.labeldata = []
        self.filename = fname

    def mapLabel(self, label, ppt, location):
        if label.startswith("_"):
            try:
                macroarg = int(label[1:], 10)
                # If that didn't throw, this is a macro argument
                # and we don't want to track it.
                return
            except ValueError:
                pass
            if label.startswith("_*"):
                # This is the caller side of the macro arguments,
                # and we don't want to track that either.
                return
        if label.startswith("*"):
            # Unprocess anonymous labels
            label = "*"
        shortlocs = []
        # Filenames tend to become absolute paths for better
        # error processing, but that's a disaster in these
        # charts. We split out the leafs here and then re-join
        # the macro application arrows.
        for loc in ppt.split('->'):
            shortloc = loc.split('/')[-1]
            shortloc = shortloc.split('\\')[-1]
            shortlocs.append(shortloc)
        self.labeldata.append((location, label, '->'.join(shortlocs)))

    def dump(self):
        if self.filename == "-":
            out = sys.stdout
        else:
            out = open(self.filename, "wt")
        maxlabellen = 0
        self.labeldata.sort()
        for (loc, label, srcloc) in self.labeldata:
            if len(label) > maxlabellen:
                maxlabellen = len(label)
        formatstr = "$%%04X | %%-%ds | %%s\n" % (maxlabellen)
        for l in self.labeldata:
            out.write(formatstr % l)
        if self.filename != "-":
            out.close()


class NullLabelMapper(object):
    "A dummy LabelMapper that actually does nothing."
    def mapLabel(self, label, ppt, location):
        pass

    def dump(self):
        pass
