
// Sunsoft FME-7 mapper

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

#include "Nes_Mapper.h"

#include "blargg_endian.h"
#include "Nes_Fme7_Apu.h"

/* Copyright (C) 2005 Chris Moeller */
/* Copyright (C) 2005-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 fme7_state_t
{
	// first 16 bytes in register order
	uint8_t regs [13];
	uint8_t irq_mode;
	uint16_t irq_count;
	
	uint8_t command;
	uint8_t irq_pending;
	fme7_apu_state_t sound_state; // only used when saving/restoring state
	
	void swap();
};
BOOST_STATIC_ASSERT( sizeof (fme7_state_t) == 18 + sizeof (fme7_apu_state_t) );

void fme7_state_t::swap()
{
	set_le16( &irq_count, irq_count );
	for ( unsigned i = 0; i < sizeof sound_state.delays / sizeof sound_state.delays [0]; i++ )
		set_le16( &sound_state.delays [i], sound_state.delays [i] );
}

class Mapper_Fme7 : public Nes_Mapper, fme7_state_t {
public:
	Mapper_Fme7()
	{
		fme7_state_t* state = this;
		register_state( state, sizeof *state );
	}
	
	virtual int channel_count() const { return sound.osc_count; }

	virtual void set_channel_buf( int i, Blip_Buffer* b ) { sound.osc_output( i, b ); }

	virtual void set_treble( blip_eq_t const& eq ) { sound.treble_eq( eq ); }
	
	virtual void reset_state()
	{
		regs [8] = 0x40; // wram disabled
		irq_count = 0xFFFF;
		sound.reset();
	}
	
	virtual void save_state( mapper_state_t& out )
	{
		sound.save_state( &sound_state );
		fme7_state_t::swap();
		Nes_Mapper::save_state( out );
		fme7_state_t::swap(); // to do: kind of hacky to swap in place
	}
	
	virtual void read_state( mapper_state_t const& in )
	{
		Nes_Mapper::read_state( in );
		fme7_state_t::swap();
		sound.load_state( sound_state );
	}
	
	void write_register( int index, int data );
	
	virtual void apply_mapping()
	{
		last_time = 0;
		for ( int i = 0; i < (int) sizeof regs; i++ )
			write_register( i, regs [i] );
	}

	virtual void run_until( nes_time_t end_time )
	{
		int new_count = irq_count - (end_time - last_time);
		last_time = end_time;
		
		if ( new_count <= 0 && (irq_mode & 0x81) == 0x81 )
			irq_pending = true;
		
		if ( irq_mode & 0x01 )
			irq_count = new_count & 0xFFFF;
	}
	
	virtual nes_time_t next_irq( nes_time_t )
	{
		if ( irq_pending )
			return 0;
		
		if ( (irq_mode & 0x81) == 0x81 )
			return last_time + irq_count + 1;
		
		return no_irq;
	}
	
	virtual void end_frame( nes_time_t end_time )
	{
		if ( end_time > last_time )
			run_until( end_time );
		
		last_time -= end_time;
		
		sound.end_frame( end_time );
	}
	
	void write_irq( nes_time_t, int index, int data );
	
	virtual void write( nes_time_t time, nes_addr_t addr, int data )
	{
		switch ( addr & 0xE000 )
		{
		case 0x8000:
			command = data & 0x0F;
			break;
		
		case 0xA000:
			if ( command < 0x0D )
				write_register( command, data );
			else
				write_irq( time, command, data );
			break;
		
		case 0xC000:
			sound.write_latch( data );
			break;
		
		case 0xE000:
			sound.write_data( time, data );
			break;
		}
	}

	nes_time_t last_time;
	Nes_Fme7_Apu sound;
};

void Mapper_Fme7::write_irq( nes_time_t time, int index, int data )
{
	run_until( time );
	switch ( index )
	{
	case 0x0D:
		irq_mode = data;
		irq_pending = false;
		irq_changed();
		break;

	case 0x0E:
		irq_count = (irq_count & 0xFF00) | data;
		break;

	case 0x0F:
		irq_count = data << 8 | (irq_count & 0xFF);
		break;
	}
}

void Mapper_Fme7::write_register( int index, int data )
{
	regs [index] = data;
	int prg_bank = index - 0x09;
	if ( (unsigned) prg_bank < 3 ) // most common
	{
		set_prg_bank( 0x8000 | (prg_bank << bank_8k), bank_8k, data );
	}
	else if ( index == 0x08 )
	{
		enable_sram( (data & 0xC0) == 0xC0 );
		if ( !(data & 0xC0) )
			set_prg_bank( 0x6000, bank_8k, data & 0x3F );
	}
	else if ( index < 0x08 )
	{
		set_chr_bank( index * 0x400, bank_1k, data );
	}
	else
	{
		if ( data & 2 )
			mirror_single( data & 1 );
		else if ( data & 1 )
			mirror_horiz();
		else
			mirror_vert();
	}
}

void register_fme7_mapper();
void register_fme7_mapper()
{
	register_mapper<Mapper_Fme7>( 69 );
}
