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

import pygtk
pygtk.require("2.0")
import gtk
import gobject
import sys
import time
import pickle
from emulators.emulators import S_A, S_X, S_Y, S_SP, S_PC

def unpack_unsigned(value):
    return value

def to_signed_byte(value):
    return value if value < 0x80 else -(256 - value)

class StatusDialog(gtk.Dialog):
    def __init__(self, *args, **kwargs):
        gtk.Dialog.__init__(self, *args, **kwargs)
        self.size_group = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)
        self.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE)
        self.controls = {}
        for ID in [S_A, S_X, S_Y, S_SP, S_PC]:
            self.add_line(ID)

    def add_line(self, ID):
        box = gtk.HBox()
        label = gtk.Label(chr(ID))
        self.size_group.add_widget(label)
        control = gtk.Label()
        box.pack_start(label, False, False)
        box.pack_start(control, True, True)
        self.vbox.pack_start(box, False, False)
        self.controls[ID] = control
        return control

    def set_value(self, ID, value):
        v = unpack_unsigned(value)
        text = "$%04X=%r=%r" % (v, v, to_signed_byte(value) if ID != S_PC else value)
        self.controls[ID].set_text(text)

class Controls(gtk.VBox):
    alternatives = { # C64_name: GDK_name
            #"grave": "numbersign", # German
            "grave": "F4", # English
            "*": "F2",
            "LeftArrow": "Escape",
            "pound": "F9",
            "/": "F8", # actually overwritten below :P
            "=": "F7", # eep.
            ";": "F6", # eep.
            #"=": "'",
    }
    def __init__(self, c64):
        gtk.VBox.__init__(self)
        self.t0 = 0
        self.screen_count = 0
        self.C64 = c64
        self.status_dialog = None
        keyboard_matrix = self.C64.CIA1.peripheral.get_keyboard_matrix()
        self.hardware_keycodes = {} # keyval_name -> keycode
        self.keycode_names = {}
        self.keymap = gtk.gdk.keymap_get_default()
        self.C64.CPU.B_disasm = False
        for row in keyboard_matrix:
            for cell in row:
                hardware_keycode = self.get_hardware_keycode_for_keyval(cell)
                self.hardware_keycodes[cell] = hardware_keycode
                self.keycode_names[hardware_keycode] = cell # for the C64, that is.
        for cell in ["Left", "Up"]:
            hardware_keycode = self.get_hardware_keycode_for_keyval(cell)
            self.hardware_keycodes[cell] = hardware_keycode
            self.keycode_names[hardware_keycode] = cell # for the C64, that is.
        self.hardware_keycodes["/"] = 20 # FIXME remove this.
        self.keycode_names[20] = "/" # FIXME remove this.
        status_button = gtk.Button("_Status")
        status_button.connect("clicked", self.show_status)
        pause_button = gtk.Button("_Pause")
        pause_button.connect("clicked", self.pause_CPU)
        read_memory_button = gtk.Button("_Read Memory...")
        read_memory_button.connect("clicked", self.dump_memory)
        toggle_disassembly_button = gtk.Button("_Toggle Disassembly")
        toggle_disassembly_button.connect("clicked", self.toggle_disassembly)
        dump_button = gtk.Button("Dump State")
        dump_button.connect("clicked", self.dump_state)
        restore_button = gtk.Button("Restore State")
        restore_button.connect("clicked", self.restore_state)
        self.pack_start(status_button, False)
        self.pack_start(pause_button, False)
        self.pack_start(read_memory_button, False)
        self.pack_start(toggle_disassembly_button, False)
        self.pack_start(dump_button, False)
        self.pack_start(restore_button, False)
        self.show_all()
    def get_hardware_keycode_for_keyval(self, cell):
        entries = self.keymap.get_entries_for_keyval(gtk.gdk.keyval_from_name(self.__class__.alternatives.get(cell) or cell) or ord(cell))
        if entries is None:
            entries = self.keymap.get_entries_for_keyval(gtk.gdk.keyval_from_name(self.__class__.alternatives[cell]))
            #print(cell)
        assert(entries)
        hardware_keycode = entries[0][0]
        return(hardware_keycode)
    def set_timer(self):
        self.timer = gobject.timeout_add(20, self.fire_timer)
        #self.timer = gobject.timeout_add(90, self.fire_timer)
    def unset_timer(self):
        gobject.source_remove(self.timer)
        self.timer = 0
    def is_timer_running(self):
        return(self.timer != 0)
    def fire_timer(self):
        if self.screen_count % 50 == 0:
            print "FPS: %.2f" % (50*(1/(time.time()-self.t0)))
            self.t0 = time.time()
        self.C64.fire_timer()
        self.screen_count += 1
        return True
    def show_status(self, *args, **kwargs):
        toplevel_widget = self.get_toplevel()
        if self.status_dialog is None:
            self.status_dialog = StatusDialog(parent = toplevel_widget)
            def unset_status_dialog(*args, **kwargs):
                self.status_dialog = None
            self.status_dialog.set_transient_for(toplevel_widget)
            self.status_dialog.connect("delete-event", unset_status_dialog)
            self.status_dialog.show_all()
            gobject.timeout_add(50, self.update_status) # FIXME don't do that too often.

        self.update_status()

    def pause_CPU(self, widget, *args, **kwargs):
        # FIXME abstract that properly.
        C64 = self.C64
        if self.is_timer_running():
            self.unset_timer()
            widget.set_label("_Continue")
        else:
            self.set_timer()
            widget.set_label("_Pause")

    def toggle_disassembly(self, *args, **kwargs):
        self.C64.CPU.B_disasm = not self.C64.CPU.B_disasm
        
    def dump_state(self, *args, **kwargs):
        configurator = self.C64.configurator
        CPU = self.C64.CPU
        MMU = CPU.MMU
        VIC = self.C64.VIC
        CIA1 = self.C64.CIA1
        CIA2 = self.C64.CIA2
        port_value = MMU.read_memory(1)
        MMU.write_memory(1, 0, 1) # make all RAM visible
        RAM_content = [MMU.read_memory(address) for address in range(65536)]
        # TODO IEC
        registers = {S_PC: CPU.read_register(S_PC),
                     S_A: CPU.read_register(S_A),
                     S_X: CPU.read_register(S_X),
                     S_Y: CPU.read_register(S_Y),
                     S_SP: CPU.read_register(S_SP)}
        VIC_content = [VIC.read_memory(address, 1) for address in range(47)]
        state = {
          "version": 1,
          "port": port_value,
          "RAM": RAM_content,
          "registers": registers,
          "VIC_props": dict([(name, getattr(VIC.props, name)) for name in dir(VIC.props) if not name.startswith("_") and name != "unprepare"]), # problem
          "VIC": VIC_content,
          "CIA1": CIA1.dump_state(),
          "CIA2": CIA2.dump_state(),
          "MMU": MMU.dump_state(),
          "configurator": configurator.dump_state(),
        }
        try:
            with open("state.dump", "wb") as f:
                pickle.dump(state, f)
        finally:
            MMU.write_memory(1, port_value, 1)
    def restore_state(self, *args, **kwargs):
        configurator = self.C64.configurator
        CPU = self.C64.CPU
        MMU = CPU.MMU
        # TODO CIAs and IEC
        VIC = self.C64.VIC
        CIA1 = self.C64.CIA1
        CIA2 = self.C64.CIA2
        with open("state.dump", "rb") as f:
           data = pickle.load(f)
        port_value = data["port"]
        MMU.write_memory(1, 0, 1) # make all RAM visible
        RAM_content = data["RAM"]
        for address in range(65536):
            MMU.write_memory(address, RAM_content[address], 1)
        VIC_content = data["VIC"]
        for address in range(47):
            VIC.write_memory(address, VIC_content[address], 1)
        VIC_props = data["VIC_props"]
        for name, value in VIC_props.items():
            setattr(VIC.props, name, value)
        registers = data["registers"]
        for key, value in registers.items():
            CPU.write_register(key, value)
        MMU.write_memory(1, port_value, 1)
        CIA1.restore_state(data["CIA1"])
        CIA2.restore_state(data["CIA2"])
        MMU.restore_state(data["MMU"])
        configurator.restore_state(data["configurator"])
    def dump_memory(self, *args, **kwargs):
        MMU = self.C64.CPU.MMU
        address = 0xF3 # 300
        sys.stdout.write("(%04X) " % address)
        for i in range(16):
            v = MMU.read_memory(address + i, 1)
            sys.stdout.write("%02X " % v)
        sys.stdout.write("\n")

    def update_status(self):
        if self.status_dialog is None:
            return False
        C64 = self.C64
        for register in [S_A, S_X, S_Y, S_SP, S_PC]:
            self.status_dialog.set_value(register, C64.CPU.read_register(register))
        return True

    def handle_key_press(self, keycode):
        n = self.keycode_names.get(keycode)
        if n:
            return self.C64.CIA1.peripheral.handle_key_press(n)

    def handle_key_release(self, keycode):
        n = self.keycode_names.get(keycode)
        if n:
            return self.C64.CIA1.peripheral.handle_key_release(n)

    def handle_mouse_motion(self, x, y, state):
        x = x // 2
        y = y // 2
        return self.C64.CIA1.peripheral.handle_mouse_motion(x, y, state)
