#pragma once

// Sunsoft FME-7 mapper

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

#include "mappers/mapper.h"
#include "audio/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 */

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
};
static_assert( sizeof (fme7_state_t) == 18 + sizeof (fme7_apu_state_t) );

// Fme7

class Mapper069 : public Nes_Mapper, fme7_state_t {
public:
	Mapper069()
	{
		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 );
		Nes_Mapper::save_state( out );
	}
	
	virtual void read_state( mapper_state_t const& in )
	{
		Nes_Mapper::read_state( in );
		sound.load_state( sound_state );
	}
	
	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 );
	}
	
	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;
		}
	}


	void 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 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();
		}
	}

	nes_time_t last_time;
	Nes_Fme7_Apu sound;
};


