#include "SVIPSG.hh"
#include "AY8910.hh"
#include "LedStatus.hh"
#include "MSXCPUInterface.hh"
#include "MSXMotherBoard.hh"
#include "JoystickPort.hh"
#include "serialize.hh"
#include <memory>

//        Slot 0    Slot 1    Slot 2    Slot 3
// FFFF +---------+---------+---------+---------+
//      | Bank 2  | Bank 12 | Bank 22 | Bank 32 | Page 3
//      |   RAM   |ROM Cart |   RAM   |   RAM   |
// 8000 |00000000 |01010000 |10100000 |11110000 | Page 2
//      +---------+---------+---------+---------+
// 7FFF | Bank 1  | Bank 11 | Bank 21 | Bank 31 | Page 1
//      |ROM BASIC|ROM Cart |   RAM   |   RAM   |
//      |00000000 |00000101 |00001010 |00001111 | Page 0
// 0000 +---------+---------+---------+---------+
//
// PSG Port A Input
// Bit Name   Description
//  0  FWD1   Joystick 1, Forward
//  1  BACK1  Joystick 1, Back
//  2  LEFT1  Joystick 1, Left
//  3  RIGHT1 Joystick 1, Right
//  4  FWD2   Joystick 2, Forward
//  5  BACK2  Joystick 2, Back
//  6  LEFT2  Joystick 2, Left
//  7  RIGHT2 Joystick 2, Right
//
// PSG Port B Output
// Bit Name    Description
// 0   /CART   Memory bank 11, ROM 0000-7FFF (cartridge /CCS1, /CCS2)
// 1   /BK21   Memory bank 21, RAM 0000-7FFF
// 2   /BK22   Memory bank 22, RAM 8000-FFFF
// 3   /BK31   Memory bank 31, RAM 0000-7FFF
// 4   /BK32   Memory bank 32, RAM 8000-7FFF
// 5   CAPS    Caps-Lock diod
// 6   /ROMEN0 Memory bank 12, ROM 8000-BFFF* (cartridge /CCS3)
// 7   /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.

namespace openmsx {

// MSXDevice

SVIPSG::SVIPSG(const DeviceConfig& config)
	: MSXDevice(config)
	, prev(255)
{
	ports[0] = &getMotherBoard().getJoystickPort(0);
	ports[1] = &getMotherBoard().getJoystickPort(1);

	// must come after initialisation of ports
	EmuTime::param time = getCurrentTime();
	ay8910 = std::make_unique<AY8910>("PSG", *this, config, time);
	reset(time);
}

SVIPSG::~SVIPSG()
{
	powerDown(EmuTime::dummy());
}

void SVIPSG::reset(EmuTime::param time)
{
	registerLatch = 0;
	ay8910->reset(time);
}

void SVIPSG::powerDown(EmuTime::param /*time*/)
{
	getMotherBoard().getLedStatus().setLed(LedStatus::CAPS, false);
}

byte SVIPSG::readIO(word /*port*/, EmuTime::param time)
{
	return ay8910->readRegister(registerLatch, time);
}

byte SVIPSG::peekIO(word /*port*/, EmuTime::param time) const
{
	return ay8910->peekRegister(registerLatch, time);
}

void SVIPSG::writeIO(word port, byte value, EmuTime::param time)
{
	switch (port & 0x07) {
	case 0:
		registerLatch = value & 0x0F;
		break;
	case 4:
		ay8910->writeRegister(registerLatch, value, time);
		break;
	}
}

// AY8910Periphery

byte SVIPSG::readA(EmuTime::param time)
{
	return ((ports[1]->read(time) & 0x0F) << 4) |
	       ((ports[0]->read(time) & 0x0F) << 0);
}

void SVIPSG::writeB(byte value, EmuTime::param /*time*/)
{
	getMotherBoard().getLedStatus().setLed(LedStatus::CAPS, (value & 0x20) != 0);

	// Default to bank 1 and 2
	byte psreg = 0;
	switch (~value & 0x14) {
	case 0x04: // bk22
		psreg = 0xa0;
		break;
	case 0x10: // 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;
	}
	getMotherBoard().getCPUInterface().setPrimarySlots(psreg);

	prev = value;
}

template<typename Archive>
void SVIPSG::serialize(Archive& ar, unsigned /*version*/)
{
	ar.template serializeBase<MSXDevice>(*this);
	ar.serialize("ay8910",        *ay8910,
	             "registerLatch", registerLatch);
	byte portB = prev;
	ar.serialize("portB", portB);
	if (ar.isLoader()) {
		writeB(portB, getCurrentTime());
	}
}
INSTANTIATE_SERIALIZE_METHODS(SVIPSG);
REGISTER_MSXDEVICE(SVIPSG, "SVI-328 PSG");

} // namespace openmsx
