// Game_Music_Emu 0.6-pre. http://www.slack.net/~ant/

#include "Nsf_Core.h"

#include "blargg_endian.h"

#if !NSF_EMU_APU_ONLY
	#include "Nes_Namco_Apu.h"
	#include "Nes_Vrc6_Apu.h"
	#include "Nes_Fme7_Apu.h"
	#include "Nes_Fds_Apu.h"
	#include "Nes_Mmc5_Apu.h"
	#include "Nes_Vrc7_Apu.h"
#endif

/* Copyright (C) 2003-2008 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */

#include "blargg_source.h"

Nsf_Core::Nsf_Core()
{
	fds   = NULL;
	fme7  = NULL;
	mmc5  = NULL;
	namco = NULL;
	vrc6  = NULL;
	vrc7  = NULL;
}

Nsf_Core::~Nsf_Core()
{
	unload();
}

void Nsf_Core::unload()
{
#if !NSF_EMU_APU_ONLY
	delete fds;
	fds = NULL;
	
	delete fme7;
	fme7 = NULL;
	
	delete namco;
	namco = NULL;
	
	delete mmc5;
	mmc5 = NULL;
	
	delete vrc6;
	vrc6 = NULL;
	
	delete vrc7;
	vrc7 = NULL;
#endif

	Nsf_Impl::unload();
}

void Nsf_Core::set_tempo( double t )
{
	set_play_period( (int) (header().play_period() / t) );
	nes_apu()->set_tempo( t );
#if !NSF_EMU_APU_ONLY
	if ( fds )
		fds->set_tempo( t );
#endif
}

blargg_err_t Nsf_Core::post_load()
{
	int chip_flags = header().chip_flags;
	
	#if !NSF_EMU_APU_ONLY
		if ( chip_flags & header_t::fds_mask )
			CHECK_ALLOC( fds = BLARGG_NEW Nes_Fds_Apu );
		
		if ( chip_flags & header_t::fme7_mask )
			CHECK_ALLOC( fme7 = BLARGG_NEW Nes_Fme7_Apu );
		
		if ( chip_flags & header_t::mmc5_mask )
			CHECK_ALLOC( mmc5 = BLARGG_NEW Nes_Mmc5_Apu );
		
		if ( chip_flags & header_t::namco_mask )
			CHECK_ALLOC( namco = BLARGG_NEW Nes_Namco_Apu );
		
		if ( chip_flags & header_t::vrc6_mask )
			CHECK_ALLOC( vrc6 = BLARGG_NEW Nes_Vrc6_Apu );
		
		if ( chip_flags & header_t::vrc7_mask )
		{
			#if NSF_EMU_NO_VRC7
				chip_flags = ~chips_mask; // give warning rather than error
			#else
					CHECK_ALLOC( vrc7 = BLARGG_NEW Nes_Vrc7_Apu );
					RETURN_ERR( vrc7->init() );
			#endif
		}
	#endif

	set_tempo( 1.0 );
	
	if ( chip_flags & ~chips_mask )
		set_warning( "Uses unsupported audio expansion hardware" );

	return Nsf_Impl::post_load();
}

int Nsf_Core::cpu_read( addr_t addr )
{
	#if !NSF_EMU_APU_ONLY
	{
		if ( addr == Nes_Namco_Apu::data_reg_addr && namco )
			return namco->read_data();
		
		if ( (unsigned) (addr - Nes_Fds_Apu::io_addr) < Nes_Fds_Apu::io_size && fds )
			return fds->read( time(), addr );
		
		int i = addr - 0x5C00;
		if ( (unsigned) i < mmc5->exram_size && mmc5 )
			return mmc5->exram [i];
		
		int m = addr - 0x5205;
		if ( (unsigned) m < 2 && mmc5 )
			return (mmc5_mul [0] * mmc5_mul [1]) >> (m * 8) & 0xFF;
	}
	#endif
	
	return Nsf_Impl::cpu_read( addr );
}

int Nsf_Core::unmapped_read( addr_t addr )
{
	switch ( addr )
	{
	case 0x2002:
	case 0x4016:
	case 0x4017:
		return addr >> 8;
	}

	return Nsf_Impl::unmapped_read( addr );
}

void Nsf_Core::cpu_write( addr_t addr, int data )
{
	#if !NSF_EMU_APU_ONLY
	{
		if ( (unsigned) (addr - fds->io_addr) < fds->io_size && fds )
		{
			fds->write( time(), addr, data );
			return;
		}
		
		if ( namco )
		{
			if ( addr == namco->addr_reg_addr )
			{
				namco->write_addr( data );
				return;
			}
			
			if ( addr == namco->data_reg_addr )
			{
				namco->write_data( time(), data );
				return;
			}
		}
		
		if ( vrc6 )
		{
			int reg = addr & (vrc6->addr_step - 1);
			int osc = (unsigned) (addr - vrc6->base_addr) / vrc6->addr_step;
			if ( (unsigned) osc < vrc6->osc_count && (unsigned) reg < vrc6->reg_count )
			{
				vrc6->write_osc( time(), osc, reg, data );
				return;
			}
		}
		
		if ( addr >= fme7->latch_addr && fme7 )
		{
			switch ( addr & fme7->addr_mask )
			{
			case Nes_Fme7_Apu::latch_addr:
				fme7->write_latch( data );
				return;
			
			case Nes_Fme7_Apu::data_addr:
				fme7->write_data( time(), data );
				return;
			}
		}
		
		if ( mmc5 )
		{
			if ( (unsigned) (addr - mmc5->regs_addr) < mmc5->regs_size )
			{
				mmc5->write_register( time(), addr, data );
				return;
			}
			
			int m = addr - 0x5205;
			if ( (unsigned) m < 2 )
			{
				mmc5_mul [m] = data;
				return;
			}
			
			int i = addr - 0x5C00;
			if ( (unsigned) i < mmc5->exram_size )
			{
				mmc5->exram [i] = data;
				return;
			}
		}
		
		if ( vrc7 )
		{
			if ( addr == 0x9010 )
			{
				vrc7->write_reg( data );
				return;
			}
			
			if ( (unsigned) (addr - 0x9028) <= 0x08 )
			{
				vrc7->write_data( time(), data );
				return;
			}
		}
	}
	#endif
	
	return Nsf_Impl::cpu_write( addr, data );
}

void Nsf_Core::unmapped_write( addr_t addr, int data )
{
	switch ( addr )
	{
	case 0x8000: // some write to $8000 and $8001 repeatedly
	case 0x8001:
	case 0x4800: // probably namco sound mistakenly turned on in MCK
	case 0xF800:
	case 0xFFF8: // memory mapper?
		return;
	}
	
	if ( mmc5 && addr == 0x5115 ) return;
	
	// FDS memory
	if ( fds && (unsigned) (addr - 0x8000) < 0x6000 ) return;

	Nsf_Impl::unmapped_write( addr, data );
}

blargg_err_t Nsf_Core::start_track( int track )
{
	#if !NSF_EMU_APU_ONLY
		if ( mmc5 )
		{
			mmc5_mul [0] = 0;
			mmc5_mul [1] = 0;
			memset( mmc5->exram, 0, mmc5->exram_size );
		}
	#endif
	
	#if !NSF_EMU_APU_ONLY
		if ( fds   ) fds  ->reset();
		if ( fme7  ) fme7 ->reset();
		if ( mmc5  ) mmc5 ->reset();
		if ( namco ) namco->reset();
		if ( vrc6  ) vrc6 ->reset();
		if ( vrc7  ) vrc7 ->reset();
	#endif
	
	return Nsf_Impl::start_track( track );
}

void Nsf_Core::end_frame( time_t end )
{
	Nsf_Impl::end_frame( end );
	
	#if !NSF_EMU_APU_ONLY
		if ( fds   ) fds  ->end_frame( end );
		if ( fme7  ) fme7 ->end_frame( end );
		if ( mmc5  ) mmc5 ->end_frame( end );
		if ( namco ) namco->end_frame( end );
		if ( vrc6  ) vrc6 ->end_frame( end );
		if ( vrc7  ) vrc7 ->end_frame( end );
	#endif
}
