#! /usr/bin/python3

import json
from machine import PDP1170
from mmio import MMIO
from pdptraps import PDPTrap, PDPTraps
import random
import sys


ignore_traps = True

class MMIO_wrapper(MMIO):
    def register(self, iofunc, offsetaddr, nwords, *, byte_writes=False, reset=False):
        pass

    def register_simpleattr(self, obj, attrname, addr, reset=False):
        pass

    def devicereset_register(self, func):
        pass

class PDP1170_wrapper(PDP1170):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.logger.info('')

        self.reset_mem_transactions_dict()

    def reset_mem_transactions_dict(self):
        self.mem_transactions = dict()
        self.before = dict()

    def get_mem_before(self):
        return self.before

    def get_mem_transactions_dict(self):
        return self.mem_transactions

    def put(self, physaddr, value):
        self.before[physaddr] = value
        super().physRW(physaddr, value)

    def physRW(self, physaddr, value=None):
        if value == None:  # read
            self.logger.info(f'Read from {physaddr:08o} (phys)')
            if not physaddr in self.mem_transactions and not physaddr in self.before:
                self.before[physaddr] = random.randint(0, 65536)
            return super().physRW(physaddr, self.before[physaddr])

        self.logger.info(f'Write to {physaddr:08o}: {value:06o} (phys)')
        self.mem_transactions[physaddr] = value
        return super().physRW(physaddr, value)

    def physRW_N(self, physaddr, nwords, words=None):
        temp_addr = physaddr
        if words == None:
            self.logger.info(f'Read {nwords} words from {physaddr:08o} (phys)')
            for i in range(nwords):
                self.physRW(temp_addr, random.randint(0, 65536))
                temp_addr += 2
            return super().physRW_N(physaddr, nwords)

        self.logger.info(f'Write {nwords} ({len(words)}) words to {physaddr:08o} (phys)')
        for w in words:
            self.mem_transactions[temp_addr] = w
            temp_addr += 2

        return super().physRW_N(physaddr, nwords, words=words)

class test_generator:
    def _invoke_bp(self, a, i):
        return True

    def put_registers(self, p, target, tag):
        target[tag] = dict()
        target[tag][0] = dict()
        target[tag][1] = dict()
        for set_ in range(0, 2):
            for reg_ in range(0, 6):
                target[tag][set_][reg_] = p.registerfiles[set_][reg_]

        target[tag]['sp'] = p.stackpointers
        target[tag]['pc'] = p.r[p.PC]

    def create_test(self):
        out = { }

        p = PDP1170_wrapper(loglevel='DEBUG')
        p.ub.mmio = MMIO_wrapper(p)

        # TODO what is the maximum size of an instruction?
        # non-mmu thus shall be below device range
        addr = random.randint(0, 0o160000 - 8) & ~3
        mem_kv = []
        while True:
            instr = random.randint(0, 65536 - 8)
            if instr != 1:  # TODO ignore 'WAIT' instruction
                break
        p.logger.info(f'emulating {instr:06o}')
        mem_kv.append((addr + 0, instr))
        mem_kv.append((addr + 2, random.randint(0, 65536 - 8)))
        mem_kv.append((addr + 4, random.randint(0, 65536 - 8)))
        mem_kv.append((addr + 6, random.randint(0, 65536 - 8)))
        out['memory-before'] = dict()
        for a, v in mem_kv:
            p.put(a, v)

        try:
            # generate & set PSW
            while True:
                try:
                    p.psw = random.randint(0, 65536)
                    break
                except PDPTraps.ReservedInstruction as ri:
                    pass

            # generate other registers
            reg_kv = []
            for i in range(7):
                reg_kv.append((i, random.randint(0, 65536)))
            reg_kv.append((7, addr))

            # set registers 
            set_ = (p.psw >> 11) & 1
            for r, v in reg_kv:
                p.registerfiles[set_][r] = v
                p.registerfiles[1 - set_][r] = (~v) & 65535  # make sure it triggers errors
                assert p.registerfiles[set_][r] == p.r[r]
            p.r[6] = p.registerfiles[set_][6]
            p.r[p.PC] = addr
            p._syncregs()

            self.put_registers(p, out, 'registers-before')
            out['registers-before']['psw'] = p.psw

            # run instruction
            p.run_steps(pc=addr, steps=1)

            if p.straps and ignore_traps:
                return None

            self.put_registers(p, out, 'registers-after')
            out['registers-after']['psw'] = p.psw

            mb = p.get_mem_before()
            for a in mb:
                out['memory-before'][a] = mb[a]

            out['memory-after'] = dict()
            mem_transactions = p.get_mem_transactions_dict()
            for a in mem_transactions:
                out['memory-after'][a] = mem_transactions[a]
            # TODO originele geheugeninhouden checken

            return out

        except PDPTraps.ReservedInstruction as pri:
            return None

        except Exception as e:
            # handle PDP11 traps; store them
            print(f'test failed {e}, line number: {e.__traceback__.tb_lineno}')
            return None

fh = open(sys.argv[1], 'w')

t = test_generator()

tests = []
try:
    for i in range(32768):
        if (i & 63) == 0:
            print(f'{i}\r', end='')
        test = t.create_test()
        if test != None:
            tests.append(test)

except KeyboardInterrupt as ki:
    pass

fh.write(json.dumps(tests, indent=4))
fh.close()