/* Copyright notice for this file:
 *  Copyright (C) 2004-2006 Shay Green
 *  Copyright (C) 2007 CaH4e3
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 * 
 * General code is from FCEUX https://sourceforge.net/p/fceultra/code/HEAD/tree/fceu/trunk/src/boards/vrc2and4.cpp
 * IRQ portion is from existing VRC6/VRC7 by Shay Green
 * This mapper was ported by retrowertz for Libretro port of QuickNES.
 * 3-19-2018
 *
 * VRC-2/VRC-4 Konami
 */

#include "Nes_Mapper.h"

struct vrc2_state_t
{
	uint8_t prg_banks [ 2 ];
	uint8_t chr_banks [ 8 ];
	uint8_t mirroring;
	uint8_t prg_swap;
	uint8_t irq_latch;
	uint8_t irq_control;

	// internal state
	uint16_t next_time;
	uint8_t irq_pending;
};

BOOST_STATIC_ASSERT( sizeof ( vrc2_state_t ) == 18 );

template <bool type_a, bool type_b>
class Mapper_VRC2_4 : public Nes_Mapper, vrc2_state_t {
public:
	Mapper_VRC2_4()
	{
		if (type_a && type_b)			// mapper 21
		{
			is22 = 0;
			reg1mask = 0x42;
			reg2mask = 0x84;
		}
		else if (!type_a && type_b)		// mapper 22
		{
			is22 = 1;
			reg1mask = 2;
			reg2mask = 1;
		}
		else if (!type_a && !type_b)	// mapper 23
		{
			is22 = 0;
			reg1mask = 0x15;
			reg2mask = 0x2a;
		}
		else if (type_a && !type_b)		// mapper 25
		{
			is22 = 0;
			reg1mask = 0xa;
			reg2mask = 0x5;
		}
		vrc2_state_t * state = this;
		register_state( state, sizeof * state );
	}

	void reset_state()
	{
	}

	void apply_mapping()
	{
		if ( !is22 ) enable_sram();
		update_prg();
		update_chr();
		set_mirroring();
	}

	void reset_timer( nes_time_t present )
	{
		next_time = present + unsigned ( ( 0x100 - irq_latch ) * timer_period ) / 4;
	}

	virtual void run_until( nes_time_t end_time )
	{
		if ( irq_control & 2 )
		{
			while ( next_time < end_time )
			{
				// printf( "%d timer expired\n", next_time );
				irq_pending = true;
				reset_timer( next_time );
			}
		}
	}

	virtual void end_frame( nes_time_t end_time )
	{
		run_until( end_time );

		// to do: next_time might go negative if IRQ is disabled
		next_time -= end_time;
	}

	virtual nes_time_t next_irq( nes_time_t present )
	{
		if ( irq_pending )
			return present;

		if ( irq_control & 2 )
			return next_time + 1;

		return no_irq;
	}

	void write_irq( nes_time_t time, nes_addr_t addr, int data );

	void write( nes_time_t time, nes_addr_t addr, int data )
	{
		addr = ( addr & 0xF000 ) | !!( addr & reg2mask ) << 1 | !!( addr & reg1mask );

		if( addr >= 0xB000 && addr <= 0xE003)
		{
			unsigned banknumber = ( ( addr >> 1 ) & 1 ) | ( ( addr - 0xB000 ) >> 11 );
			unsigned offset = ( addr & 1 ) << 2;

			chr_banks [ banknumber ] &= ( 0xF0 ) >> offset;
			chr_banks [ banknumber ] |= ( data & 0xF ) << offset;
			chr_banks [ banknumber ] |= ( offset ? ( ( data & 0x10 ) << 4 ) : 0 );
			update_chr();
		}
		else
		{
			switch ( addr & 0xF003 )
			{
				case 0x8000:
				case 0x8001:
				case 0x8002:
				case 0x8003:
					prg_banks [ 0 ] = data & 0x1F;
					update_prg();
					break;
				case 0xA000:
				case 0xA001:
				case 0xA002:
				case 0xA003:
					prg_banks [ 1 ] = data & 0x1F;
					update_prg();
					break;
				case 0x9000:
				case 0x9001:
					mirroring = data;
					set_mirroring();
					break;
				case 0x9002:
				case 0x9003:
					prg_swap = data;
					update_prg();
					break;
				case 0xF000:
				case 0xF001:
				case 0xF002:
				case 0xF003:
					write_irq( time, addr, data );
					break;
			}
		}
	}

	unsigned is22, reg1mask, reg2mask;
	enum { timer_period = 113 * 4 + 3 };

private:
	void set_mirroring()
	{
		switch ( mirroring & 3 )
		{
		case 0: mirror_vert(); break;
		case 1: mirror_horiz(); break;
		case 2:
		case 3: mirror_single( mirroring & 1 ); break;
		}
	}

	void update_prg()
	{
		if ( prg_swap & 2 )
		{
			set_prg_bank( 0x8000, bank_8k, ( 0xFE ) );
			set_prg_bank( 0xC000, bank_8k, prg_banks [ 0 ] );
		}
		else
		{
			set_prg_bank( 0x8000, bank_8k, prg_banks [ 0 ] );
			set_prg_bank( 0xC000, bank_8k, ( 0xFE ) );
		}

		set_prg_bank( 0xA000, bank_8k, prg_banks [ 1 ] );
		set_prg_bank( 0xE000, bank_8k, ( 0xFF ) );
	}

	void update_chr()
	{
		for ( int i = 0; i < (int) sizeof chr_banks; i++ )
			set_chr_bank( i * 0x400, bank_1k, chr_banks [ i ] >> is22 );
	}
};

template <bool type_a, bool type_b>
void Mapper_VRC2_4<type_a, type_b>::write_irq( nes_time_t time,
	nes_addr_t addr, int data )
{
	// IRQ
	run_until( time );
	//printf("%6d VRC2_4 [%d] A:%04x V:%02x\n", time, addr & 3, addr, data);
	switch ( addr & 3 )
	{
		case 0:
			irq_latch = ( irq_latch & 0xF0 ) | ( data & 0xF );
			break;
		case 1:
			irq_latch = ( irq_latch & 0x0F ) | ( ( data & 0xF ) << 4 );
			break;
		case 2:
			irq_pending = false;
			irq_control = data & 3;
			if ( data & 2 ) reset_timer( time );
			break;
		case 3:
			irq_pending = false;
			irq_control = ( irq_control & ~2 ) | ( ( irq_control << 1 ) & 2 );
			break;
	}
	irq_changed();
}

void register_vrc2_4_mapper();
void register_vrc2_4_mapper()
{
	register_mapper< Mapper_VRC2_4<true,  true>  > ( 21 );
	register_mapper< Mapper_VRC2_4<false, true>  > ( 22 );
	register_mapper< Mapper_VRC2_4<false, false> > ( 23 );
	register_mapper< Mapper_VRC2_4<true,  false> > ( 25 );
}
