#!/usr/bin/env python2
# I, Danny Milosavljevic, hereby place this file into the public domain.

# shedskin main file
import sys
import os
import iec3
from s import InterruptibleObject
from chips import cpu
from chips import vic_ii
from chips import sid
from chips import cia
from chips import vdc
from chips import via
from chips import ram
from c64 import screens
import time
import tape
#import gmonitor
from chips import memory
import d1541
import d1541connector
#import gdisplay
from chips.symbols import *
import symboltables
import devices
from chips.mmu2 import C64Configurator as CPUPort
from chips import mmu2
from chips.mmu2 import C128Configurator
from chips.mmu2 import C128ShadowConfigurator

class Extension(memory.Memory):
    pass
class C64(InterruptibleObject):
    def __init__(self):
        self.D1541 = None
        self.interrupt_clock = 0
        self.VIC_clock = 0
        MMU = self.create_MMU()
        self.MMU = MMU
        for i in range(256):
            MMU.add_real_page("RAM", i, ram.RAM())
        self.CPU = cpu.CPU(MMU)
        configurator = CPUPort(MMU)
        self.configurator = configurator
        self.CPU.MMU.map_IO("cpu", (0x0000, 0x0100), configurator) # actually too much. This will be special-cased in MMU.read_memory
        known_addresses = {}
        zeropage_known_addresses = symboltables.load_known_addresses("emulators/c64/symbols/zeropage.INI")
        KERNAL_known_addresses = symboltables.load_known_addresses("emulators/c64/symbols/KERNAL.INI")
        BASIC_known_addresses = symboltables.load_known_addresses("emulators/c64/symbols/BASIC.INI")
        IO_known_addresses = {}
        IO_known_addresses.update(symboltables.load_known_addresses("emulators/c64/symbols/CIA1.INI"))
        IO_known_addresses.update(symboltables.load_known_addresses("emulators/c64/symbols/CIA2.INI"))
        IO_known_addresses.update(symboltables.load_known_addresses("emulators/c64/symbols/VICII.INI"))
        IO_known_addresses.update(symboltables.load_known_addresses("emulators/c64/symbols/SID.INI"))
        known_addresses.update(zeropage_known_addresses)
        known_addresses.update(KERNAL_known_addresses)
        known_addresses.update(BASIC_known_addresses)
        known_addresses.update(IO_known_addresses)
        MMU.set_known_addresses(known_addresses)
        self.IECBus = iec3.IECBus()
        self.IEC = iec3.ComputerDevice(self.IECBus, 31, self.CPU, 0xDD00)
        self.IECBus.add_device(self.IEC)
        hooks = [tape.Tape(self.CPU), self.IEC]
        hook_addresses = []
        for hook in hooks:
            hook_addresses = hook_addresses + hook.get_points()
            self.CPU.add_BRK_handler(hook)
        self.load_ROMs(hook_addresses)
        SID = sid.SID()
        cia1 = cia.CIA(devices.InputDevices(MMU, 0xDC00, SID))
        self.CIA1 = cia1
        cia2 = cia.CIA(devices.SerialDevices(MMU, 0xDD00, self.IEC))
        self.CIA2 = cia2 # note that CIA2 interrupts cannot be masked by SEI since it's connected by NMI.
        screen = screens.Screen(cia2)
        screen.get_rendered_pixbuf() # ShedSkin
        vic = vic_ii.VIC_II(self, self.CPU.MMU, cia2, screen)
        screen.set_VIC(vic)
        self.VIC = vic
        self.CPU.MMU.map_IO("vic", (0xD000, 0xD400), vic)
        self.CPU.MMU.map_IO("sid", (0xD400, 0xD800), SID)
        self.CPU.MMU.map_color_IO("color", (0xD800, 0xDC00))
        self.CPU.MMU.map_IO("cia1", (0xDC00, 0xDD00), cia1)
        self.CPU.MMU.map_IO("cia2", (0xDD00, 0xDE00), cia2)
        self.CPU.MMU.map_IO("extension", (0xDE00, 0xE000), Extension())
        configurator.set_port(0x34)
        # power-up pattern:
        address = 0
        for i in range(512):
            for b in range(64):
                if i > 0 or b > 1:
                    MMU.write_memory(address, 0, 1)
                address += 1
            for b in range(64):
                MMU.write_memory(address, 0xFF, 1)
                address += 1
        configurator.set_port(0x37)
        MMU.write_memory(0xDC01, 0xFF, 1) # init value
        MMU.write_memory(0xDC02, 0xFF, 1) # out
        MMU.write_memory(0xDC03, 0, 1) # in 
        MMU.write_memory(0xDD02, 63, 1) # most out
        MMU.write_memory(0xDD03, 0, 1) # in
        assert(MMU.read_memory(0xFFFC, 2) == 0xFCE2) # cold reset vector.
        vic.repaint() # ShedSkin
        vic.unprepare() # memory is not initialized yet, so unprepare...
        #MMU.write_memory(0xFFFA, b"\x43\xFE\xE2\xFC\x48\xFF") # FIXME endianness.
        #self.CPU.BRK(0)
        # done automatically on "BRK"?
        self.CPU.write_register(S_PC, (MMU.read_memory(0xFFFC, 2)))
        if False:
            self.fire_timer() # ShedSkin
        #self.controls = gmonitor.Controls(self)
        #self.controls = {}

    def create_MMU(self):
        result = mmu2.MMU()
        if True: # ShedSkin
            result = mmu2.C64MMU()
        return(result)

    def load_ROMs(self, hook_addresses):
        self.ROMs = [
            ("basic",   (0xA000, 0xC000)), 
            # C000..CFFF is FREE (not used by BASIC at all)
            ("chargen", (0xD000, 0xE000)),
            ("kernal",  (0xE000, 0x10000)),
        ]
        for ROM, range_1 in self.ROMs:
            value = open("data/C64/" + ROM, "rb").read()
            size = range_1[1] - range_1[0]
            #print(ROM)
            assert size == len(value), "C64 ROM is not truncated"
            # TODO how to do this in a nicer way?
            if ROM == "kernal":
                # patch ROM so BRK instructions are at strategic places.
                xvalue = [c for c in value]
                for address in hook_addresses:
                    assert address >= 0xE000, "hooks are actually within the KERNAL"
                    xvalue[address - 0xE000] = chr(0) # BRK
                xvalue[0xE4E2 - 0xE000] = chr(0x60) # get rid of tape delay
                xvalue[0xEE1E - 0xE000] = chr(0xEA) # get rid of serial reader CLOCK drop wait.
                xvalue[0xEE1F - 0xE000] = chr(0xEA) # get rid of serial reader CLOCK drop wait.
                xvalue[0xEEB4 - 0xE000] = chr(0) # BRK wait 1ms
                xvalue[0xEEB6 - 0xE000] = chr(0) # BRK wait 1ms # TODO maybe remove this after we got instruction timing right.
                value = b"".join(xvalue)
            self.CPU.MMU.map_ROM(ROM, range_1[0], value, ROM != "chargen")
    def prepare_unit_tests(self):
        CPU = self.CPU
        CPU.clear_N()
        CPU.CLV()
        CPU.clear_B()
        CPU.CLD()
        CPU.SEI()
        CPU.clear_Z()
        CPU.CLC()
        #self.CPU.write_register(S_P, 4) # FIXME
        CPU.write_register(S_SP, 0xFD)
        MMU = self.CPU.MMU
        MMU.write_memory(2, 0, 1)
        MMU.write_memory(0xA002, 0, 1)
        MMU.write_memory(0xA003, 0x80, 1)
        MMU.write_memory(0xFFFE, 0x48, 1)
        MMU.write_memory(0xFFFF, 0xFF, 1)
        MMU.write_memory(0x01FE, 0xFF, 1)
        MMU.write_memory(0x01FF, 0x7F, 1)
        MMU.write_memory(0xFFD2, 0, 1) # BRK instead of print character.
        MMU.write_memory(0xE16F, 0, 1) # BRK instead of LOAD.
        MMU.write_memory(0xFFE4, 0, 1) # BRK instead of scan keyboard.
        MMU.write_memory(0x8000, 0, 1) # BRK
        MMU.write_memory(0xA474, 0, 1) # BRK
        for i, c in enumerate([0x48, 0x8A, 0x48, 0x98, 0x48, 0xBA, 0xBD, 0x04, 0x01, 0x29, 0x10, 0xF0, 0x03, 0x6C, 0x16, 0x03, 0x6C, 0x14, 0x03]):
            MMU.write_memory(0xFF48 + i, c, 1)
        """ IRQ handler:
         FF48  48        PHA
         FF49  8A        TXA
         FF4A  48        PHA
         FF4B  98        TYA
         FF4C  48        PHA
         FF4D  BA        TSX
         FF4E  BD 04 01  LDA    $0104,X
         FF51  29 10     AND    #$10
         FF53  F0 03     BEQ    $FF58
         FF55  6C 16 03  JMP    ($0316)
         FF58  6C 14 03  JMP    ($0314)
        """
        # FIXME
        #header = tape.tape_loader.load_header("")
        #start = header.start_addr
        #data = tape.tape_loader.load_data()
        #for i in range(size):
        #    memory.write_memory(start + i, ord(data[i]), 1)
        CPU.B_unit_testing = True
    def set_tape_image_name(self, name, format):
        tape.set_image_name(name, format)
    def set_1541_image_name(self, address, name, type):
        # FIXME change it when it's already there.
        #device = self.IECBus.get_device(address)
        #if not device:
        device = d1541connector.D1541(self.IECBus, address)
        self.IECBus.add_device(device)
        self.D1541 = device
        self.D1541.set_image_name(name, type)
        return(self.D1541)
    def run(self):
        while True: # TODO terminate?
            self.iterate()
    def fire_timer(self):
        for n in range(19800):
            self.iterate()
            self.interrupt_clock += 1
            self.VIC_clock += 1
            if self.VIC_clock >= 66:
                self.VIC_clock = 0
                self.VIC.increase_raster_position()
        if self.CIA1.update_timers() or self.CIA2.update_timers() or self.interrupt_clock >= 50:
            self.interrupt_clock = 0
            self.cause_interrupt()
        self.VIC.repaint()
        if self.VIC_clock >= 1: # 6:
            self.VIC_clock = 0
            self.VIC.increase_raster_position()
        return
        self.iterate()
        self.interrupt_clock += 1
        if self.interrupt_clock >= 50:
            self.interrupt_clock = 0
            self.cause_interrupt()
        return
    def iterate(self):
        self.CPU.step()
        return True
    def cause_interrupt(self):
        if "I" in self.CPU.flags: # interrupt DISABLE
            #print("not supposed to cause interrupts right now...")
            return True
        #print("at 0x0283: %r" % self.CPU.MMU.read_memory(0x0283, 2))
        #print("at 0x37: %r" % self.CPU.MMU.read_memory(0x37, 2))
        #print("at 0x2B: %r" % self.CPU.MMU.read_memory(0x2B, 2))
        #if not self.CIA1.B_interrupt_pending:
        if not self.CPU.B_in_interrupt:
            #self.CIA1.B_interrupt_pending = True
            self.CPU.cause_interrupt(False)
        return True

# just a souped-up C64.
class C128(C64):
    def __init__(self):
        C64.__init__(self)
        configurator = C128Configurator(self.MMU)
        self.configurator = configurator
        # TODO maybe get rid of C64Configurator ?
        self.CPU.MMU.map_IO("cpu128", (0xFF00, 0x10000), C128ShadowConfigurator(configurator)) # actually too much. This will be special-cased in MMU.read_memory
        self.CPU.MMU.map_IO("mmu", (0xD500, 0xD600), configurator)
        self.CPU.MMU.map_IO("vdc", (0xD600, 0xD602), vdc.VDC())
        self.CPU.MMU.map_color_IO("color", (0xD800, 0xDC00))
    def create_MMU(self):
        result = mmu2.MMU()
        if True: # ShedSkin
            result = mmu2.C128MMU()
        return(result)
    def load_ROMs(self, hook_addresses):
        f = open("data/C128/complete.318023-02.bin", "rb")
        self.ROMs = [
            ("basic",   (0xA000, 0xC000)), # C64 FIXME
            ("kernal",  (0xE000, 0x10000)), # C64
            ("editor", (0xC000, 0xD000)),
            ("z80bios", (0x10000, 0x11000)), # Z80 sees it at 0
            ("c128kernal", (0xE000, 0x10000)),
            #???("chargen", (0xD000, 0xE000)), # missing
        ] # TOTAL: 72 KiB. 
        for ROM, range_1 in self.ROMs:
            size = range_1[1] - range_1[0]
            value = f.read(size)
            #print(ROM)
            assert size == len(value), "C128 ROM is not truncated"
            # TODO how to do this in a nicer way?
            if ROM == "kernal":
                # patch ROM so BRK instructions are at strategic places.
                xvalue = [c for c in value]
                for address in hook_addresses:
                    assert address >= 0xE000, "hooks are actually within the KERNAL"
                    xvalue[address - 0xE000] = chr(0) # BRK
                xvalue[0xE4E2 - 0xE000] = chr(0x60) # get rid of tape delay
                xvalue[0xEE1E - 0xE000] = chr(0xEA) # get rid of serial reader CLOCK drop wait.
                xvalue[0xEE1F - 0xE000] = chr(0xEA) # get rid of serial reader CLOCK drop wait.
                xvalue[0xEEB4 - 0xE000] = chr(0) # BRK wait 1ms
                xvalue[0xEEB6 - 0xE000] = chr(0) # BRK wait 1ms
                value = b"".join(xvalue)
            self.CPU.MMU.map_ROM(ROM, range_1[0], value, ROM != "chargen")
        with open("data/C128/basic-4000.318018-04.bin", "rb") as baslo:
            self.CPU.MMU.map_ROM("BASICLOW", 0x4000, baslo.read(), True)
        with open("data/C128/basic-8000.318019-04.bin", "rb") as bashi:
            self.CPU.MMU.map_ROM("BASICHIGH", 0x8000, bashi.read(), True)
        with open("data/C128/characters.390059-01.bin", "rb") as characters: # C128 mode
            self.CPU.MMU.map_ROM("chargen", 0xD000, characters.read(4096), False)

class D1541(InterruptibleObject):
    def __init__(self):
        self.interrupt_clock = 0
        MMU = mmu2.D1541MMU()
        for i in range(8):
            MMU.add_real_page("RAM", i, ram.RAM())
        self.CPU = cpu.CPU(MMU)
        zeropage_known_addresses = symboltables.load_known_addresses("emulators/d1541/symbols/zeropage.INI")
        controller_known_addresses = symboltables.load_known_addresses("emulators/d1541/symbols/controller.INI")
        DOS_known_addresses = symboltables.load_known_addresses("emulators/d1541/symbols/DOS.INI")
        IO_known_addresses = {}
        IO_known_addresses.update(symboltables.load_known_addresses("emulators/d1541/symbols/VIA1.INI"))
        IO_known_addresses.update(symboltables.load_known_addresses("emulators/d1541/symbols/VIA2.INI"))
        known_addresses = {}
        known_addresses.update(zeropage_known_addresses)
        known_addresses.update(controller_known_addresses)
        known_addresses.update(DOS_known_addresses)
        known_addresses.update(IO_known_addresses)
        MMU.set_known_addresses(known_addresses)
        self.ROMs = [
            ("dos1541", (0xC000, 0x10000)),
        ]
        for ROM, range_1 in self.ROMs:
            value = open("data/C64/" + ROM, "rb").read()
            size = range_1[1] - range_1[0]
            #print(ROM)
            assert size == len(value), "D1541 ROM is not truncated"
            # TODO how to do this in a nicer way?
            if ROM == "dos1541":
                # patch ROM so BRK instructions are at strategic places.
                xvalue = [c for c in value]
                #xvalue[0xFEF3 - 0xC000] = chr(0x60) # get rid of 40 us delay
                value = b"".join(xvalue)
            self.CPU.MMU.map_ROM(ROM, range_1[0], value, ROM != "chargen")
        self.IECBus = iec3.IECBus()
        self.IEC = iec3.D1541ComputerDevice(self.IECBus, 8, self.CPU, 0x1800)
        self.IECBus.add_device(self.IEC)
        #self.computer_device = self.IEC.computer_device
        via1 = via.VIA(devices.D1541SerialDevices(MMU, 0x1800, self.IEC))
        self.VIA1 = via1
        via2 = via.VIA(devices.PhysicalDrive(MMU, 0x1C00))
        self.VIA2 = via2
        self.CPU.MMU.map_IO("via1", (0x1800, 0x1810), via1)
        self.CPU.MMU.map_IO("via2", (0x1C00, 0x1C10), via2)
        address = 0
        # power-up pattern:
        for i in range(512):
            for b in range(64):
                MMU.write_memory(address, 0, 1)
                address += 1
            for b in range(64):
                MMU.write_memory(address, 0xFF, 1)
                address += 1
        assert(MMU.read_memory(0xFFFC, 2) == 0xEAA0)
        self.CPU.write_register(S_PC, (MMU.read_memory(0xFFFC, 2)))
        if False:
            self.fire_timer() # ShedSkin

    def prepare_unit_tests(self):
        CPU = self.CPU
        CPU.clear_N()
        CPU.CLV()
        CPU.clear_B()
        CPU.CLD()
        CPU.SEI()
        CPU.clear_Z()
        CPU.CLC()
        #self.CPU.write_register(S_P, 4) # FIXME
        CPU.write_register(S_SP, 0xFD)
        MMU = self.CPU.MMU
        MMU.write_memory(2, 0, 1)
        MMU.write_memory(0xA002, 0, 1)
        MMU.write_memory(0xA003, 0x80, 1)
        MMU.write_memory(0xFFFE, 0x48, 1)
        MMU.write_memory(0xFFFF, 0xFF, 1)
        MMU.write_memory(0x01FE, 0xFF, 1)
        MMU.write_memory(0x01FF, 0x7F, 1)
        MMU.write_memory(0xFFD2, 0, 1) # BRK instead of print character.
        MMU.write_memory(0xE16F, 0, 1) # BRK instead of LOAD.
        MMU.write_memory(0xFFE4, 0, 1) # BRK instead of scan keyboard.
        MMU.write_memory(0x8000, 0, 1) # BRK
        MMU.write_memory(0xA474, 0, 1) # BRK
        for i, c in enumerate([0x48, 0x8A, 0x48, 0x98, 0x48, 0xBA, 0xBD, 0x04, 0x01, 0x29, 0x10, 0xF0, 0x03, 0x6C, 0x16, 0x03, 0x6C, 0x14, 0x03]):
            MMU.write_memory(0xFF48 + i, c, 1)
        CPU.B_unit_testing = True
        
    def set_image_name(self, name, type):
        #device.set_image_name(name, type)
        #return(device)
        pass
        
    def run(self):
        while True: # TODO terminate?
            self.iterate()

    def fire_timer(self):
        for n in range(19800):
            self.iterate()
            self.interrupt_clock += 1
        if self.VIA1.update_timers() or self.VIA2.update_timers() or self.interrupt_clock >= 50:
            self.interrupt_clock = 0
            self.cause_interrupt()
        return

    def iterate(self):
        self.CPU.step()
        return True

    def cause_interrupt(self):
        if "I" in self.CPU.flags: # interrupt DISABLE
            #print("not supposed to cause interrupts right now...")
            return True
        if not self.CPU.B_in_interrupt:
            self.CPU.cause_interrupt(False)
        return True
if __name__ == "__main__":
    c_128 = C128()
    c_128.CIA1.peripheral.handle_key_press("X")
    c_128.CIA1.peripheral.handle_key_release("X")
    c_128.CIA1.peripheral.handle_mouse_motion(0, 0, 0)
    devices.KeyDevices().get_keyboard_matrix()
    devices.KeyDevices().handle_key_press("X")
    devices.KeyDevices().handle_key_release("X")
    c_128.CIA1.peripheral.get_keyboard_matrix()
    c_128.CIA1.read_memory(0, 1)
    # clear_Z, set_Z, clear_N, set_N, set_V
    # timeout_remove
    for i in range(800000):
        c_128.iterate()
    #c_128.CPU_clock = timer.timeout_add(5, c_128)
    #c_128.cause_interrupt() # ShedSkin
    #{
    import gtk
    gtk.main()
    #}
    #c_128.run()
    c_128.run()
    c_128.set_tape_image_name("stuff", "T64") # ShedSkin
    c_128.set_1541_image_name(8, "stuff", "D64") # ShedSkin
    c_128.prepare_unit_tests() # ShedSkin
    c_128.configurator.restore_state(c_128.configurator.dump_state())
    c_128.MMU.restore_state(c_128.MMU.dump_state())
    c_128.CIA1.restore_state(c_128.CIA1.dump_state())
    c_128.CIA2.restore_state(c_128.CIA2.dump_state())
    c_128.CIA1.timer_A.fire()
    c_128.CIA1.timer_B.fire()
    c_128.CIA2.timer_A.fire()
    c_128.CIA2.timer_B.fire()
if __name__ == "__main__":
    c64 = C64()
    c64.CIA1.peripheral.handle_key_press("X")
    c64.CIA1.peripheral.handle_key_release("X")
    c64.CIA1.peripheral.handle_mouse_motion(0, 0, 0)
    devices.KeyDevices().get_keyboard_matrix()
    devices.KeyDevices().handle_key_press("X")
    devices.KeyDevices().handle_key_release("X")
    devices.KeyDevices().handle_mouse_motion(0, 0, 0)
    c64.CIA1.peripheral.get_keyboard_matrix()
    i = c64.CIA1.read_memory(0, 1)
    i = c64.CIA2.read_memory(0, 1)
    # clear_Z, set_Z, clear_N, set_N, set_V
    # timeout_remove
    for i in range(800000):
        c64.iterate()
    #c64.CPU_clock = timer.timeout_add(5, c64)
    #c64.cause_interrupt() # ShedSkin
    #{
    import gtk
    gtk.main()
    #}
    #c64.run()
    print(c64.D1541.get_reader_fileno())
    print(c64.D1541.read())
    c64.run()
    c64.set_tape_image_name("stuff", "T64") # ShedSkin
    c64.set_1541_image_name(8, "stuff", "D64") # ShedSkin
    c64.prepare_unit_tests() # ShedSkin
    c64.configurator.restore_state(c64.configurator.dump_state())
    c64.MMU.restore_state(c_128.MMU.dump_state())
    c64.CIA1.restore_state(c64.CIA1.dump_state())
    c64.CIA2.restore_state(c64.CIA2.dump_state())
    c64.CIA1.timer_A.fire()
    c64.CIA1.timer_B.fire()
    c64.CIA2.timer_A.fire()
    c64.CIA2.timer_B.fire()
    c64.MMU.write_memory(42, c64.MMU.read_memory(42, 1), 1) # ShedSkin
if __name__ == "__main__":
    d_1541 = D1541()
    d_1541.VIA1.read_memory(0, 1)
    for i in range(800000):
        d_1541.iterate()
    #{
    import gtk
    gtk.main()
    #}
    #c64.run()
    d_1541.run()
    d_1541.set_image_name("stuff", "D64") # ShedSkin
    d_1541.prepare_unit_tests() # ShedSkin
    d_1541.VIA1.restore_state(d_1541.VIA1.dump_state())
    d_1541.VIA2.restore_state(d_1541.VIA2.dump_state())
    d_1541.VIA1.timer_A.fire()
    d_1541.VIA2.timer_B.fire()
