#!/usr/bin/env python
# I, Danny Milosavljevic, hereby place this file into the public domain.
import memory
#class Configurator(memory.Memory)...
import rom

minimal_overlay_address = 0x2000

"""
--------------------------------------- $10000
 Upper Bank of Cartridge ROM
--------------------------------------- $C000
 Lower Bank of Cartridge ROM
--------------------------------------- $8000
 Cartridge RAM (may be battery-backed)
--------------------------------------- $6000
 Expansion Modules
--------------------------------------- $5000
 Input/Output
--------------------------------------- $2000
 2kB Internal RAM, mirrored 4 times
--------------------------------------- $0000
"""

def hardware_overlay_P(address):
    return(False)
class MMU(memory.Memory):
    def __init__(self):
        self.port = 0
        self.IO_pages = 256*[None]
        self.CARTRIDGE_pages = 256*[None]
        self.known_addresses = {}
        self.lifeline_controller = None
        self.memory = 256*256*[0]
        self.IO_overlay = 0xDE00*[0xFF]
        hook_addresses = []
        self.refresh_cache()
    def set_known_addresses(self, value):
        self.known_addresses = value
    def add_real_page(self, prefix, page_number, controller):
        if prefix == "RAM":
            pass
        elif prefix == "IO":
            self.IO_pages[page_number] = controller
        elif prefix == "CARTRIDGE":
            self.CARTRIDGE_pages[page_number] = controller
        elif prefix == "cpu":
            if page_number == 255: # C128
                self.lifeline_controller = controller
    def read_memory(self, address, size = 1):
        page_number = address >> 8
        offset = address & 0xFF
        if ((address + size - 1) >> 8) > address >> 8: # crossing the page boundary
            assert(size == 2)
            return self.read_memory(address, 1) | (self.read_memory(address + 1, 1) << 8)
        controller = self.real_pages[page_number]
        if offset < 5 and page_number == 255 and self.lifeline_controller is not None:
            controller = self.lifeline_controller
        if controller is None:
            if size == 1:
                result = self.memory[address & 2047]
            else: # TODO make sure to handle boundary!
                result = memory.one_big_value(self.memory[address & 2047: (address & 2047) + size])
        else:
            result = controller.read_memory(offset, size)
        if address >= 0x2000 and address < 0x5000 and self.get_B_IO(): # IO
            IO_addr = address & 0xFFF
            if hardware_overlay_P(IO_addr):
                #dir_addr = hardware_direction_address_of(IO_addr)|0xD000
                # assume also visible
                #d = self.IO_pages[dir_addr >> 8].read_memory(dir_addr & 0xFF, size) # that is, self.read_memory(dir_addr | 0xD000)
                vo = self.IO_overlay[IO_addr] if size == 1 else memory.one_big_value(self.IO_overlay[IO_addr : IO_addr + size]) # you really don't want the latter.
                result = vo # (result&d) | (vo&~d)
        return(result)
    def write_memory(self, address, value, size):
        page_number = address >> 8
        offset = address & 0xFF 
        if ((address + size - 1) >> 8) > address >> 8: # crossing the page boundary
            assert(size == 2)
            self.write_memory(address, value & 0xFF, 1)
            self.write_memory(address + 1, value >> 8, 1)
            return
        controller = self.real_pages[page_number]
        if offset < 5 and page_number == 255 and self.lifeline_controller is not None:
            controller = self.lifeline_controller
        if controller is None or not controller.B_can_write: # RAM (can't write to ROM)
            # NES actually has just 2 KB RAM and I think is supposed to wrap around. However, some have additional RAM so I wrap around at 64K
            if size == 1:
                self.memory[address & 0xFFFF] = value
            else:
                for i in range(size):
                    self.memory[(address & 0xFFFF) + i] = value & 0xFF
                    value >>= 8
        else:
            controller.write_memory(offset, value, size)
    def read_RAM(self, address, size = 1): # VIC special case
        if size == 1:
            return self.memory[address]
        else:
            return memory.one_big_value(self.memory[address:address + size])
    def read_chargen(self, address, size = 1): # VIC special case
        pass # FIXME remove
    def get_B_IO(self):
        return(False)
    def calc_cache_map(self, value, wvalue, page0_page, page1_page):
        result = 256*[None]
        for i in range(0x8000//256, 0xC000//256):
            result[i] = self.CARTRIDGE_pages[i]
        for i in range(0xC000//256, 0x10000//256):
            result[i] = self.CARTRIDGE_pages[i]
        for i in range(0x2000//256, 0x2100//256):
            result[i] = self.IO_pages[i]
        return(result)
    def refresh_cache(self):
        cache_map = self.update_cache_map(self.port, 0, 0, 1) # FIXME values...
        self.cache_map = cache_map
    def update_cache_map(self, value, wvalue, page0_page, page1_page):
        self.real_pages = self.calc_cache_map(value, wvalue, page0_page, page1_page)
        return(self.real_pages)
    # name is more like type.
    def map_ROM(self, name, address, value, B_active): # this doesn't actually MAP anything, but it's to keep compability with the old interface.
        assert address >= minimal_overlay_address, "MMU.map_ROM address >= minimal_overlay_address" # ??  or range_1[1] == 2
        assert((address & 0xFF) == 0)
        r = value
        for page in range(address >> 8, (address + len(value) + 255) >> 8):
            ROM_1 = rom.ROM(r[:256], B_active) # B_active is ignored
            r = r[256:]
            self.add_real_page(name, page, ROM_1)
        self.refresh_cache()
        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"
        assert((range_1[0] & 0xFF) == 0)
        assert((range_1[1] & 0xFF) == 0)
        if (range_1[0] >> 8 < (range_1[1] - 1) >> 8): # more than one page total
            off = 0
            #for page in range(range_1[0] >> 8, range_1[1] >> 8):
            #    self.add_real_page("IO" if name != "cpu" else name, page, Window1(IO, off))
            #    off += 256
            assert(False)
        else:
            assert(range_1[0] >> 8 == (range_1[1] - 1) >> 8)
            for page in range(range_1[0] >> 8, range_1[1] >> 8):
                self.add_real_page("IO" if name != "cpu" else name, page, IO)
    def set_overlay_active(self, name, value):
        pass # unused
    def IO_overlay_write_byte(self, IO_address, value):
        IO_address = IO_address & 0xFFF
        self.IO_overlay[IO_address] = value
    def dump_state(self):
        result = self.IO_overlay
        # note that actual configuration is handled by configurator
        return(result)
    def restore_state(self, state):
        if len(state) > 0:
            IO_overlay = state
            for i in range(len(self.IO_overlay)):
                self.IO_overlay[i] = IO_overlay[i]
        # note that actual configuration is handled by configurator

class MMC1(MMU):
    pass

def create_MMU(kind):
    assert(kind == 1)
    return(MMC1())
    
"""
Mapper#  Name                  Examples
---------------------------------------------------------------------------
0        No mapper             All 32kB ROM + 8kB VROM games
1        Nintendo MMC1         Megaman2, Bomberman2, etc.
2        CNROM switch          Castlevania, LifeForce, etc.
3        UNROM switch          QBert, PipeDream, Cybernoid, many Japanese games
4        Nintendo MMC3         SilverSurfer, SuperContra, Immortal, etc.
5        Nintendo MMC5         Castlevania3
6        FFE F4xxx             F4xxx games off FFE CDROM
7        AOROM switch          WizardsAndWarriors, Solstice, etc.
8        FFE F3xxx             F3xxx games off FFE CDROM
9        Nintendo MMC2         Punchout
10       Nintendo MMC4         Punchout2
11       ColorDreams chip      CrystalMines, TaginDragon, etc.
12     - FFE F6xxx             F6xxx games off FFE CDROM
13       CPROM switch
15       100-in-1 switch       100-in-1 cartridge
16       Bandai chip           Japanese DragonBallZ series, etc.
17       FFE F8xxx             F8xxx games off FFE CDROM
18       Jaleco SS8806 chip    Japanese Baseball3, etc.
19       Namcot 106 chip       Japanese GhostHouse2, Baseball90, etc.
20       Nintendo DiskSystem   Reserved. Don't use this mapper!
21       Konami VRC4a          Japanese WaiWaiWorld2, etc.
22       Konami VRC2a          Japanese TwinBee3
23       Konami VRC2a          Japanese WaiWaiWorld, MoonWindLegend, etc.
24     - Konami VRC6           
25       Konami VRC4b          
32       Irem G-101 chip       Japanese ImageFight, etc.
33       Taito TC0190/TC0350   Japanese PowerBlazer
34       Nina-1 board          ImpossibleMission2 and DeadlyTowers
64       Tengen RAMBO-1 chip
65       Irem H-3001 chip
66       GNROM switch
67       SunSoft3 chip
68       SunSoft4 chip
69       SunSoft5 FME-7 chip
71       Camerica chip
78       Irem 74HC161/32-based
79       AVE Nina-3 board      KrazyKreatures, DoubleStrike, etc.
81       AVE Nina-6 board      Deathbots, MermaidsOfAtlantis, etc.
91       Pirate HK-SF3 chip
---------------------------------------------------------------------------
"""
