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

import sys
import memory

# TODO actually the memory is separated into pages of 256 bytes each on a real C64.

# $0000-$00FF, 0-255 zero page (without usable $0 and $1).
# $0100-$01FF, 256-511 processor stack.
# $0200-$02FF, buffers.
# $0300-$03FF, 768-1023 IRQ vectors.
# $0400-$07FF, 1024-2047 default screen memory.
# $0800-$9FFF, 2048-40959 basic area.
# $A000-$BFFF, 40960-49151 BASIC ROM
# $C000-$CFFF, 49152-53247 upper RAM area.
# $D000-$DFFF, 53248-57343 I/O Area
# $D000-$DFFF, 53248-57343 character ROM
# $D000-$D3FF, 53248-54271 VIC II
# $D400-$D7FF, 54272-55295 SID
# $D800-$DBFF, 55296-56319 Color RAM (only 4 bits per byte)!
# $DC00-$DCFF, 56320-56575 CIA#1 inputs
# $DD00-$DDFF, 56576-56831 CIA#2 serial, NMI
# $DE00-$DEFF, 56832-57087 external device memory maps.
# $DF00-$DFFF, 57088-57343 external device memory maps.
# $E000-$FFFF, 57344-65535 kernal ROM!!!
# $FFFA-$FFFF, 65530-65535 hardware vectors.

class ROM(memory.Memory):
    def __init__(self, value, B_active = True):
        self.B_active = B_active
        self.memory = []
        for i in range(len(value)): # for some reason, in ShedSkin "for c in value: self.memory.append(ord(c))" doesn't work.
            c = value[i]
            v = ord(c)
            self.memory.append(v)

        #self.memory = [ord(c) for c in value]
        self.B_can_write = False # in the instance because of ShedSkin

    def read_memory(self, address, size = 1):
        if size == 1:
            return self.memory[address]
        return one_big_value(self.memory[address : address + size])

    def write_memory(self, address, value, size):
        raise NotImplementedError("cannot write to ROM")

minimal_overlay_address = 0xA000

def one_big_value(part):
    assert len(part) <= 4, "mmu.one_big_value: len(part)<=4"
    f = 0
    v = 0
    for c in part:
        v = v | (c << f)
        f += 8
    return v

class MMU(memory.Memory):
    def __init__(self):
        self.overlays = {} # name: beginning_page_number
        #self.overlay_values = []
        self.known_addresses = {}
        self.potential_overlay_pages = 256 * [None]
        self.active_overlay_pages = 256 * [None]
        self.overlay_starting_addresses = 256 * [0]
        self.overlay_sizes = 256 * [0] # beginning_page_number -> size_pages
        self.memory = 65536 * [0]
        self.chargen = None

    def set_overlay(self, name, overlay):
        overlay_beginning, overlay_end = overlay[0]
        overlay_controller = overlay[1]
        if name == "chargen": # this has overlapping addresses with half the C64 chips, so special-case it:
            self.chargen = overlay_controller
            return
        #print("Overlay name %r controller %r beginning %r end %r" % (name, overlay_controller, overlay_beginning, overlay_end))
        assert((overlay_end & ((1 << 8) - 1)) == 0)
        overlay_end -= 1
        beginning_page_number = overlay_beginning >> 8
        assert((overlay_beginning & ((1 << 8) - 1)) == 0)
        end_page_number = overlay_end >> 8
        self.overlays[name] = beginning_page_number
        self.overlay_sizes[beginning_page_number] = end_page_number + 1 - beginning_page_number
        for page_number in range(beginning_page_number, end_page_number + 1):
            self.potential_overlay_pages[page_number] = overlay_controller
            self.active_overlay_pages[page_number] = overlay_controller
            self.overlay_starting_addresses[page_number] = overlay_beginning
            #print("overlay page %r" % page_number)
        #self.overlay_values = self.overlays.values()
        #print("end1")

    def read_memory(self, address, size = 1):
        page_number = address >> 8
        if address >= minimal_overlay_address or address < 2:
            if address >= 0xD000 and address < 0xE000 and self.chargen.B_active: # this has overlapping addresses with half the C64 chips, so special-case it:
                controller = self.chargen
            else:
                controller = self.active_overlay_pages[page_number]
            if controller:
                    return controller.read_memory(address - self.overlay_starting_addresses[page_number], size)
        if size == 1:
            return(self.memory[address])
        assert size >= 0, "MMU.read_memory: size>=0"
        v = one_big_value(self.memory[address : address + size])
        return v

    #def read_zero_page(self, address, size = 1):
    #    return self.read_memory(address, size)
    def write_memory(self, address, value, size):
        if address >= minimal_overlay_address or address < 2:
            page_number = address >> 8
            controller = self.active_overlay_pages[page_number]
            if controller and controller.B_can_write:
                return controller.write_memory(address - self.overlay_starting_addresses[page_number], value, size)

        assert isinstance(value, int), "MMU.write_memory: value is an integer"
        for i in range(size):
            self.memory[address + i] = value & 0xFF
            value >>= 8

    def map_ROM(self, name, address, value, B_active):
        assert address >= minimal_overlay_address, "MMU.map_ROM address >= minimal_overlay_address" # ??  or range_1[1] == 2
        ROM_1 = ROM(value, B_active)
        self.set_overlay(name, ((address, address + len(value)), ROM_1))
        return ROM_1

    def map_IO(self, name, range_1, IO):
        assert range_1[0] >= minimal_overlay_address or range_1[1] == 0x100, "MMU.map_IO"
        self.set_overlay(name, ((range_1[0], range_1[1]), IO))

    def set_overlay_active(self, name, value):
        #print("setting overlay %r to %r" % (name, value))
        if name == "chargen":
            self.chargen.B_active = value
        else:
            beginning_page_number = self.overlays[name]
            size_pages = self.overlay_sizes[beginning_page_number]
            for i in range(beginning_page_number, beginning_page_number + size_pages):
                #print("page %r to %r" % (i, value))
                self.active_overlay_pages[i] = self.potential_overlay_pages[i] if value else None
            #print("the end")
        # TODO set_known_addresses

    def set_known_addresses(self, value):
        self.known_addresses = value

    def read_RAM(self, address, size = 1): # VIC special case
        if size == 1:
            return(self.memory[address])
        assert size >= 0, "MMU.read_memory: size>=0"
        v = one_big_value(self.memory[address : address + size])
        return v
