import struct

class MPWObject:
    def __init__(self):
        self._list = []
        self._dict = []
        self._backdict = {}
        self._dict_idx = 200

    def __bytes__(self):
        dest = bytearray()

        for chunk in self._list:
            dest.extend(chunk)
            if len(dest) & 1: dest.append(0)

        return bytes(dest)

    def _ensurename(self, name):
        # get the ID of this name from the dict
        # If nonexistent, then add it
        # i.e. idempotent

        try:
            return self._backdict[name]
        except KeyError:
            self.putdict([name])
            return self._backdict[name]

    def _quickappend(self, *bytelist):
        self._list.append(bytes(bytelist))

    def putfirst(self):
        self._quickappend(1, 1, 0, 2)

    def putlast(self):
        self._quickappend(2, 0)

    def putdict(self, items):
        dest = bytearray()

        dest.extend([4, 0, 99, 99])
        dest.extend(struct.pack('>H', self._dict_idx))

        flag = False

        for item in items:
            flag = True
            dest.append(len(item))
            dest.extend(item.encode('ascii'))

            self._backdict[item] = self._dict_idx

            self._dict_idx += 1 # ID of the *next* thing

        if not flag: return

        struct.pack_into('>H', dest, 2, len(dest))
        
        self._list.append(dest)

    def putmod(self, name='#0001', segname='Main', flags=(1<<7)+(1<<3)):
        modid = self._ensurename(name)
        segid = self._ensurename(segname)
        self._last_mod_id = modid

        self._list.append(struct.pack('>BBHH', 5, flags, modid, segid))

    def putentry(self, offset, name):
        entid = self._ensurename(name)

        self._list.append(struct.pack('>BBHL', 6, 1<<3, entid, offset))

    def putsize(self, size):
        self._list.append(struct.pack('>BBL', 7, 0, size))

    def putcontents(self, data): # in multiple chunks please!
        done = 0

        while done < len(data):
            this_time = data[done:done+30000]

            header = struct.pack('>BBHL', 8, 1<<3, 8 + len(this_time), done)

            self._list.append(header + this_time)

            done += len(this_time)

    def putcomment(self, cmt):
        cmt = cmt.replace('\n','\r').encode('mac_roman')
        if len(cmt) & 1: cmt += b' '

        dest = bytearray()
        dest.extend([3, 0])
        dest.extend(struct.pack('>H', len(cmt) + 4))
        dest.extend(cmt)

        self._list.append(dest)

    def putsimpleref(self, targname, width, *offsets):
        offsets = list(offsets)

        if width == 2: # of the operand field, in bytes
            flags = 1 << 4
        elif width == 4:
            flags = 0

        flags |= 1<<3 # longwords in the offset list!

        targid = self._ensurename(targname)

        dest = struct.pack('>BBHH', 9, flags, 6 + 4 * len(offsets), targid)
        dest += b''.join(struct.pack('>L', o) for o in offsets)

        self._list.append(dest)

    def putweirdref(self, targname, width, *offsets):
        # Assumes that you've already put -offset at offset
        offsets = list(offsets)

        if width == 1:
            flags = 2 << 4
        elif width == 2: # of the operand field, in bytes
            flags = 1 << 4
        elif width == 4:
            flags = 0 << 4

        flags |= 1<<7 # difference calculation
        # flags |= 1<<3 # longwords in the offset list!

        targid = self._ensurename(targname)

        dest = struct.pack('>BBHHH', 10, flags, 8 + 2 * len(offsets), targid, self._last_mod_id)
        dest += b''.join(struct.pack('>H', o) for o in offsets)

        self._list.append(dest)

