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

# shedskin main file
import sys
import os
from s import InterruptibleObject
from chips import cpu6502 #as cpu
from chips import ppu
from chips import nessid
from chips import ram
from chips import ppummu
from nes import screens
import time
#import gmonitor
from chips import memory
#import gdisplay
from chips.symbols import *
import symboltables
import devices
from chips import nesmmu
import nesfile

class Extension(memory.Memory):
    pass
class NES(InterruptibleObject):
    def __init__(self):
        self.D1541 = None
        self.interrupt_clock = 0
        self.PPU_clock = 0
        MMU = self.create_MMU()
        self.MMU = MMU
        self.CPU = cpu6502.CPU(MMU)
        #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/nes/symbols/zeropage.INI")
        KERNAL_known_addresses = symboltables.load_known_addresses("emulators/nes/symbols/KERNAL.INI")
        IO_known_addresses = {}
        IO_known_addresses.update(symboltables.load_known_addresses("emulators/nes/symbols/PPU.INI"))
        known_addresses.update(zeropage_known_addresses)
        known_addresses.update(KERNAL_known_addresses)
        known_addresses.update(IO_known_addresses)
        MMU.set_known_addresses(known_addresses)
        # IO devices start at $2000. Expansion Modules at $5000.
        screen = screens.Screen()
        sid = nessid.SID(MMU, screen) # devices.InputDevices(MMU, 0x2000)
        self.SID = sid
        screen.get_rendered_pixbuf() # ShedSkin
        PPUMMU = ppummu.MMU()
        ppu1 = ppu.PPU(self, PPUMMU, screen)
        screen.set_PPU(ppu1)
        self.PPU = ppu1
        MMU.map_IO("ppu", (0x2000, 0x2100), ppu1) # actually to $2008
        MMU.map_IO("sid", (0x4000, 0x4100), sid)
        ppu1.repaint() # ShedSkin
        ppu1.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"?
        if False:
            self.fire_timer() # ShedSkin
        #self.controls = gmonitor.Controls(self)
        #self.controls = {}

    def create_MMU(self):
        result = nesmmu.MMU()
        if True: # ShedSkin
            result = nesmmu.create_MMU(1) # FIXME configurable type.
        return(result)
    def set_ROM_name(self, name):
        with open(name, "rb") as f:
            self.load_ROM(f)
    def load_ROM(self, f):
        hooks = []
        hook_addresses = []
        for hook in hooks:
            hook_addresses = hook_addresses + hook.get_points()
            self.CPU.add_BRK_handler(hook)
        nesfile.load(f, self.CPU.MMU, self.PPU.MMU)
        assert(self.MMU.read_memory(0xFFFC, 2) >= 0x8000) # sanity check, technically not necessary.
        self.CPU.write_register(S_PC, (self.MMU.read_memory(0xFFFC, 2)))
    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
        CPU.B_unit_testing = True
    def run(self):
        while True: # TODO terminate?
            self.iterate()
    def check_test_output(self): # instruction test ROM.
        final_result = self.MMU.read_memory(0x6000, 1)
        sys.stdout.write("(status=$%X)" % (final_result, ))
        text_out_base = 0x6004
        print("TOB",hex(self.MMU.read_memory(text_out_base - 3, 1)))
        for i in range(1000):
            c = self.MMU.read_memory(text_out_base + i)
            if c == 0:
                break
            sys.stdout.write("%s" % (chr(c) if (c >= 32 and c < 127) or c == 10 else (hex(c) + " "), ))
        sys.stdout.write("\n")
    def fire_timer(self):
        for n in range(19800):
            self.iterate()
            self.interrupt_clock += 1
            self.PPU_clock += 1
            if self.PPU_clock >= 66:
                self.PPU_clock = 0
                self.PPU.increase_raster_position()
        if self.PPU.update_timers() or self.SID.update_timers() or self.interrupt_clock >= 50:
            self.interrupt_clock = 0
            self.cause_interrupt()
            self.check_test_output()
        self.PPU.repaint()
        if self.PPU_clock >= 1: # 6:
            self.PPU_clock = 0
            self.PPU.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

if __name__ == "__main__":
    nes = NES()
    # clear_Z, set_Z, clear_N, set_N, set_V
    # timeout_remove
    for i in range(800000):
        nes.iterate()
    #nes.CPU_clock = timer.timeout_add(5, nes)
    #nes.cause_interrupt() # ShedSkin
    #{
    import gtk
    gtk.main()
    #}
    #nes.run()
    print(nes.D1541.get_reader_fileno())
    print(nes.D1541.read())
    nes.run()
    nes.prepare_unit_tests() # ShedSkin
    nes.configurator.restore_state(nes.configurator.dump_state())
    nes.MMU.write_memory(42, nes.MMU.read_memory(42, 1), 1) # ShedSkin
