/*****************************************************************************
** $Source: /cygdrive/d/Private/_SVNROOT/bluemsx/blueMSX/Src/Board/SVI.c,v $
**
** $Revision: 1.64 $
**
** $Date: 2009-07-01 21:13:04 $
**
** More info: http://www.bluemsx.com
**
** Copyright (C) 2003-2006 Daniel Vik, Tomas Karlsson
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
** 
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
**
******************************************************************************
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "SVI.h"

#include "R800.h"
#include "R800Dasm.h"
#include "R800SaveState.h"
#include "R800Debug.h"

#include "AY8910.h"
#include "SviPPI.h"
#include "DeviceManager.h"
#include "SaveState.h"
#include "Led.h"
#include "KeyClick.h"
#include "Casette.h"
#include "Disk.h"
#include "IoPort.h"
#include "MegaromCartridge.h"
#include "SlotManager.h"
#include "RomLoader.h"
#include "romMapperSvi80Col.h"
#include "SviJoyIo.h"

/* Hardware */
static AY8910*         ay8910;
static AudioKeyClick*  keyClick;
static R800*           r800;
static SviJoyIo*       joyIO;
static UInt8           KeyboardMap[16];
static UInt32          sviRamSize;
static UInt32          sviRamStart;
static UInt8*          sviRam;
static UInt8           psgAYReg15;
static int             svi328Col80Enabled;
static UInt8           lastJoystickValue;

extern void PatchZ80(void* ref, CpuRegs* cpu);

static void sviMemWrite(void* ref, UInt16 address, UInt8 value)
{
    if ((svi328Col80Enabled && svi328Col80MemBankCtrlStatus()) && (address & 0xf800) == 0xf000)
    {
        svi328Col80MemWrite(address & 0xfff, value);
    }
    else
        slotWrite(ref, address, value);
}

static UInt8 sviMemRead(void* ref, UInt16 address)
{
    if ((svi328Col80Enabled && svi328Col80MemBankCtrlStatus()) && (address & 0xf800) == 0xf000) 
        return svi328Col80MemRead(address & 0xfff);
    else
        return slotRead(ref, address);
}

/*
       SLOT 0    SLOT 1    SLOT 2    SLOT 3
FFFF +---------+---------+---------+---------+
     | BANK 02 | BANK 12 | BANK 22 | BANK 32 | PAGE 3
     |   RAM   |ROM CART |   RAM   |   RAM   |
8000 |00000000 |01010000 |10100000 |11110000 | PAGE 2
     +---------+---------+---------+---------+
7FFF | BANK 01 | BANK 11 | BANK 21 | BANK 31 | PAGE 1
     |ROM BASIC|ROM CART |   RAM   |   RAM   |
     |00000000 |00000101 |00001010 |00001111 | PAGE 0
0000 +---------+---------+---------+---------+
*/

static void sviMemSetBank(UInt8 value)
{
    UInt8 psreg;
    int i;
    psgAYReg15 = value;

    // Default to bank 1 and 2
    psreg = 0;

    switch (~value & 0x14) {
    case 4: // bk22
        psreg = 0xa0;
        break;
    case 16: // bk32
        psreg = 0xf0;
        break;
    }

    switch (~value & 0x0B) {
    case 1: // bk12 (cart)?
        if ((~value & 0x80) || (~value & 0x40)) {
            psreg = 0x50;
        }
        // bk11 (cart)
        psreg |= 0x05;
        break;
    case 2: // bk21
        psreg |= 0x0a;
        break;
    case 8: // bk31
        psreg |= 0x0f;
        break;
    }

    /* Map the SVI-328 bank to slot and page */
    for (i = 0; i < 4; i++) {
        slotSetRamSlot(i, psreg & 3);
        psreg >>= 2;
    }
}

/*
PSG Port A Input
Bit Name   Description
 1  FWD1   Joystick 1, Forward
 2  BACK1  Joystick 1, Back
 3  LEFT1  Joystick 1, Left
 4  RIGHT1 Joystick 1, Right
 5  FWD2   Joystick 2, Forward
 6  BACK2  Joystick 2, Back
 7  LEFT2  Joystick 2, Left
 8  RIGHT2 Joystick 2, Right

PSG Port B Output
Bit Name    Description
1   /CART   Memory bank 11, ROM 0000-7FFF (Cartridge /CCS1, /CCS2)
2   /BK21   Memory bank 21, RAM 0000-7FFF
3   /BK22   Memory bank 22, RAM 8000-FFFF
4   /BK31   Memory bank 31, RAM 0000-7FFF
5   /BK32   Memory bank 32, RAM 8000-7FFF
6   CAPS    Caps-Lock diod
7   /ROMEN0 Memory bank 12, ROM 8000-BFFF* (Cartridge /CCS3)
8   /ROMEN1 Memory bank 12, ROM C000-FFFF* (Cartridge /CCS4)

* The /CART signal must be active for any effect,
  then all banks of RAM are disabled. */

static UInt8 sviPsgReadHandler(void* arg, UInt16 address) 
{
    UInt8 value = 0xff;

    switch (address) {
        case 0:
            value = boardCaptureUInt8(17, sviJoyIoRead(joyIO));
            lastJoystickValue = value;
            break;
        case 1:
            value = psgAYReg15;
            break;
    }
    return value;
}

static UInt8 sviPsgPollHandler(void* arg, UInt16 address)
{
    UInt8 value = 0xff;

    switch (address) {
        case 0:
            value = lastJoystickValue;
            break;
        case 1:
            value = psgAYReg15;
            break;
    }
    return value;
}

static void sviPsgWriteHandler(void* arg, UInt16 address, UInt8 value) 
{
    switch (address) {
    case 0:
        break;
    case 1:
        ledSetCapslock(value & 0x20 ? 1 : 0);
        if ((psgAYReg15 & 0xDF) != (value & 0xDF))
            sviMemSetBank(value);
        break;
    }
}

static int sviLoad80Col(Machine* machine, VdpSyncMode vdpSyncMode)
{
    // The 80 column cart has a memory mapping that doesn't fit
    // the blueMSX memory management yet, so it needs special
    // handling. When supported this should be moved to Machine.c
    
    int success = 1;
    int i;

    for (i = 0; i < machine->slotInfoCount; i++) {
        if (!machine->slotInfo[i].error) {
            int size;
            UInt8* buf = romLoad(machine->slotInfo[i].name, machine->slotInfo[i].inZipName, &size);

            if (buf != NULL) {
                if (machine->slotInfo[i].romType == ROM_SVI328COL80) {
                    int frameRate = (vdpSyncMode == VDP_SYNC_60HZ) ? 60 : 50;
                    svi328Col80Enabled = romMapperSvi328Col80Create(frameRate, buf, size);
                    success &= svi328Col80Enabled;
                }
                free(buf);
            }
        }
    }

    return success;
}

static void reset()
{
    UInt32 systemTime = boardSystemTime();

    slotManagerReset();

    if (r800 != NULL) {
        r800Reset(r800, systemTime);
    }
    if (ay8910 != NULL) {
        ay8910Reset(ay8910);
    }

    sviMemSetBank(0xDF);

    ledSetCapslock(0);

    deviceManagerReset();
}

static void destroy()
{
    boardRemoveExternalDevices();
    ay8910SetIoPort(ay8910, NULL, NULL, NULL, NULL);
    ay8910Destroy(ay8910);
    ay8910 = NULL;
    audioKeyClickDestroy(keyClick);
    r800DebugDestroy();
    slotManagerDestroy();
    deviceManagerDestroy();
    r800Destroy(r800);
}

static int getRefreshRate()
{
    return vdpGetRefreshRate();
}

static UInt32 getTimeTrace(int offset) {
    return r800GetTimeTrace(r800, offset);
}

static void saveState()
{
    SaveState* state = saveStateOpenForWrite("svi");

    saveStateSet(state, "svi328Col80Enabled", svi328Col80Enabled);
    saveStateSet(state, "psgAYReg15", psgAYReg15);

    saveStateClose(state);

    r800SaveState(r800);
    deviceManagerSaveState();
    slotSaveState();
    ay8910SaveState(ay8910);
}

static void loadState()
{
    SaveState* state = saveStateOpenForRead("svi");

    svi328Col80Enabled = saveStateGet(state, "svi328Col80Enabled", 0);
    psgAYReg15      = (UInt8)saveStateGet(state, "psgAYReg15", 0);

    saveStateClose(state);
    
    r800LoadState(r800);
    boardInit(&r800->systemTime);
    deviceManagerLoadState();
    slotLoadState();
    ay8910LoadState(ay8910);
}

int sviCreate(Machine* machine, 
              VdpSyncMode vdpSyncMode,
              BoardInfo* boardInfo)
{
    int success;
    int i;

    r800 = r800Create(CPU_ENABLE_M1, sviMemRead, sviMemWrite, ioPortRead, ioPortWrite, PatchZ80, boardTimerCheckTimeout, NULL, NULL, NULL, NULL, NULL, NULL);

    boardInfo->cartridgeCount   = 1;
    boardInfo->diskdriveCount   = 2;
    boardInfo->casetteCount     = 1;
    boardInfo->cpuRef           = r800;

    boardInfo->destroy          = destroy;
    boardInfo->softReset        = reset;
    boardInfo->loadState        = loadState;
    boardInfo->saveState        = saveState;
    boardInfo->getRefreshRate   = getRefreshRate;
    boardInfo->getRamPage       = NULL;

    boardInfo->run              = r800Execute;
    boardInfo->stop             = r800StopExecution;
    boardInfo->setInt           = r800SetInt;
    boardInfo->clearInt         = r800ClearInt;
    boardInfo->setCpuTimeout    = r800SetTimeoutAt;
    boardInfo->setBreakpoint    = r800SetBreakpoint;
    boardInfo->clearBreakpoint  = r800ClearBreakpoint;
    boardInfo->setDataBus       = r800SetDataBus;
    
    boardInfo->getTimeTrace     = getTimeTrace;

    deviceManagerCreate();
    boardInit(&r800->systemTime);
    ioPortReset();
    r800Reset(r800, 0);
    mixerReset(boardGetMixer());

    r800DebugCreate(r800);

    ay8910 = ay8910Create(boardGetMixer(), AY8910_SVI, PSGTYPE_AY8910, 0, machine->audio.psgpan);
    ay8910SetIoPort(ay8910, sviPsgReadHandler, sviPsgPollHandler, sviPsgWriteHandler, NULL);

    keyClick  = audioKeyClickCreate(boardGetMixer());

    joyIO = sviJoyIoCreate();
    
    sviPPICreate(joyIO);
    slotManagerCreate();

    svi328Col80Enabled = 0;

    /* Initialize VDP */
    vdpCreate(VDP_SVI, machine->video.vdpVersion, vdpSyncMode, machine->video.vramSize / 0x4000);

    /* Initialize memory */
    for (i = 0; i < 4; i++) {
        slotSetSubslotted(i, 0);
    }
    for (i = 0; i < 2; i++) {
        cartridgeSetSlotInfo(i, machine->cart[i].slot, 0);
    }

    success = machineInitialize(machine, &sviRam, &sviRamSize, &sviRamStart);
    success &= sviLoad80Col(machine, vdpSyncMode);

    for (i = 0; i < 8; i++) {
        slotMapRamPage(0, 0, i);
    }

    sviMemSetBank(0xDF);
    ledSetCapslock(0);

    if (success) {
        success = boardInsertExternalDevices();
    }

    memset(KeyboardMap, 0xff, 16);

    r800SetFrequency(r800, CPU_Z80,  machine->cpu.freqZ80);
    r800SetFrequency(r800, CPU_R800, machine->cpu.freqR800);

    diskEnable(0, machine->fdc.count > 0);
    diskEnable(1, machine->fdc.count > 1);

    if (!success) {
        destroy();
    }

    return success;
}
