
// NES MMC5 mapper, currently only tailored for Castlevania 3 (U)

// Nes_Emu 0.7.0. http://www.slack.net/~ant/

#include "Nes_Mapper.h"

#include "Nes_Core.h"
#include <string.h>

/* Copyright (C) 2004-2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module 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 Lesser General Public License for
more details. You should have received a copy of the GNU Lesser General
Public License along with this module; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */

#include "blargg_source.h"

struct mmc5_state_t
{
	enum { reg_count = 0x30 };
	uint8_t regs [0x30];
	uint8_t irq_enabled;
};
// to do: finalize state format
BOOST_STATIC_ASSERT( sizeof (mmc5_state_t) == 0x31 );

class Mapper_Mmc5 : public Nes_Mapper, mmc5_state_t {
public:
	Mapper_Mmc5()
	{
		mmc5_state_t* state = this;
		register_state( state, sizeof *state );
	}
	
	virtual void reset_state()
	{
		irq_time = no_irq;
		regs [0x00] = 2;
		regs [0x01] = 3;
		regs [0x14] = 0x7f;
		regs [0x15] = 0x7f;
		regs [0x16] = 0x7f;
		regs [0x17] = 0x7f;
	}
	
	virtual void read_state( mapper_state_t const& in )
	{
		Nes_Mapper::read_state( in );
		irq_time = no_irq;
	}
	
	enum { regs_addr = 0x5100 };
	
	virtual void apply_mapping();
	
	virtual nes_time_t next_irq( nes_time_t )
	{
		if ( irq_enabled & 0x80 )
			return irq_time;
		
		return no_irq;
	}
	
	virtual bool write_intercepted( nes_time_t time, nes_addr_t addr, int data )
	{
		int reg = addr - regs_addr;
		if ( (unsigned) reg < reg_count )
		{
			regs [reg] = data;
			switch ( reg )
			{
			case 0x05:
				mirror_manual( data & 3, data >> 2 & 3,
						data >> 4 & 3, data >> 6 & 3 );
				break;
			
			case 0x15:
				set_prg_bank( 0x8000, bank_16k, data >> 1 & 0x3f );
				break;
			
			case 0x16:
				set_prg_bank( 0xC000, bank_8k, data & 0x7f );
				break;
			
			case 0x17:
				set_prg_bank( 0xE000, bank_8k, data & 0x7f );
				break;
			
			case 0x20:
			case 0x21:
			case 0x22:
			case 0x23:
			case 0x28:
			case 0x29:
			case 0x2a:
			case 0x2b:
				set_chr_bank( ((reg >> 1 & 4) + (reg & 3)) * 0x400, bank_1k, data );
				break;
			}
		}
		else if ( addr == 0x5203 )
		{
			irq_time = no_irq;
			if ( data && data < 240 )
			{
				irq_time = (341 * 21 + 128 + (data * 341)) / 3;
				if ( irq_time < time )
					irq_time = no_irq;
			}
			irq_changed();
		}
		else if ( addr == 0x5204 )
		{
			irq_enabled = data;
			irq_changed();
		}
		else
		{
			return false;
		}
		
		return true;
	}
	
	virtual void write( nes_time_t, nes_addr_t, int ) { }
	nes_time_t irq_time;
};

void Mapper_Mmc5::apply_mapping()
{
	static unsigned char list [] = {
		0x05, 0x15, 0x16, 0x17,
		0x20, 0x21, 0x22, 0x23,
		0x28, 0x29, 0x2a, 0x2b
	};
	
	for ( int i = 0; i < (int) sizeof list; i++ )
		write_intercepted( 0, regs_addr + list [i], regs [list [i]] );
	intercept_writes( 0x5100, 0x200 );
}

void register_mmc5_mapper();
void register_mmc5_mapper()
{
	register_mapper<Mapper_Mmc5>( 5 );
}
