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

#include "Nes_Core.h"
#include <cstdio>
#include <cstring>
#include "Nes_Mapper.h"
#include "Nes_State.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"

/*
  New mapping distribution by Sergio Martin (eien86)
  https://github.com/SergioMartin86/jaffarPlus
*/
#include "mappers/mapper000.hpp"
#include "mappers/mapper001.hpp"
#include "mappers/mapper002.hpp"
#include "mappers/mapper003.hpp"
#include "mappers/mapper004.hpp"
#include "mappers/mapper005.hpp"
#include "mappers/mapper007.hpp"
#include "mappers/mapper009.hpp"
#include "mappers/mapper010.hpp"
#include "mappers/mapper011.hpp"
#include "mappers/mapper015.hpp"
#include "mappers/mapper019.hpp"
#include "mappers/mapper021.hpp"
#include "mappers/mapper022.hpp"
#include "mappers/mapper023.hpp"
#include "mappers/mapper024.hpp"
#include "mappers/mapper025.hpp"
#include "mappers/mapper026.hpp"
#include "mappers/mapper030.hpp"
#include "mappers/mapper032.hpp"
#include "mappers/mapper033.hpp"
#include "mappers/mapper034.hpp"
#include "mappers/mapper060.hpp"
#include "mappers/mapper066.hpp"
#include "mappers/mapper069.hpp"
#include "mappers/mapper070.hpp"
#include "mappers/mapper071.hpp"
#include "mappers/mapper073.hpp"
#include "mappers/mapper075.hpp"
#include "mappers/mapper078.hpp"
#include "mappers/mapper079.hpp"
#include "mappers/mapper085.hpp"
#include "mappers/mapper086.hpp"
#include "mappers/mapper087.hpp"
#include "mappers/mapper088.hpp"
#include "mappers/mapper089.hpp"
#include "mappers/mapper093.hpp"
#include "mappers/mapper094.hpp"
#include "mappers/mapper097.hpp"
#include "mappers/mapper113.hpp"
#include "mappers/mapper140.hpp"
#include "mappers/mapper152.hpp"
#include "mappers/mapper154.hpp"
#include "mappers/mapper156.hpp"
#include "mappers/mapper180.hpp"
#include "mappers/mapper184.hpp"
#include "mappers/mapper190.hpp"
#include "mappers/mapper193.hpp"
#include "mappers/mapper206.hpp"
#include "mappers/mapper207.hpp"
#include "mappers/mapper232.hpp"
#include "mappers/mapper240.hpp"
#include "mappers/mapper241.hpp"
#include "mappers/mapper244.hpp"
#include "mappers/mapper246.hpp"

extern const char unsupported_mapper [] = "Unsupported mapper";

bool const wait_states_enabled = true;
bool const single_instruction_mode = false; // for debugging irq/nmi timing issues

const int unmapped_fill = Nes_Cpu::page_wrap_opcode;

unsigned const low_ram_size = 0x800;
unsigned const low_ram_end  = 0x2000;
unsigned const sram_end     = 0x8000;

Nes_Core::Nes_Core() : ppu( this )
{
	cart = NULL;
	impl = NULL;
	mapper = NULL;
	memset( &nes, 0, sizeof nes );
	memset( &joypad, 0, sizeof joypad );
}

const char * Nes_Core::init()
{
	if ( !impl )
	{
		CHECK_ALLOC( impl = new impl_t );
		impl->apu.dmc_reader( read_dmc, this );
		impl->apu.irq_notifier( apu_irq_changed, this );
	}
	
	return 0;
}

void Nes_Core::close()
{
	cart = NULL;
	delete mapper;
	mapper = NULL;
	
	ppu.close_chr();
	
	disable_rendering();
}

const char * Nes_Core::open( Nes_Cart const* new_cart )
{
	close();
	RETURN_ERR( init() );

  // Getting cartdrige mapper code
  auto mapperCode = new_cart->mapper_code();

  // Now checking if the detected mapper code is supported
  if (mapperCode ==   0) mapper = new Mapper000();
  if (mapperCode ==   1) mapper = new Mapper001();
  if (mapperCode ==   2) mapper = new Mapper002();
  if (mapperCode ==   3) mapper = new Mapper003();
  if (mapperCode ==   4) mapper = new Mapper004();
  if (mapperCode ==   5) mapper = new Mapper005();
  if (mapperCode ==   7) mapper = new Mapper007();
  if (mapperCode ==   9) mapper = new Mapper009();
  if (mapperCode ==  10) mapper = new Mapper010();
  if (mapperCode ==  11) mapper = new Mapper011();
  if (mapperCode ==  15) mapper = new Mapper015();
  if (mapperCode ==  19) mapper = new Mapper019();
  if (mapperCode ==  21) mapper = new Mapper021();
  if (mapperCode ==  22) mapper = new Mapper022();
  if (mapperCode ==  23) mapper = new Mapper023();
  if (mapperCode ==  24) mapper = new Mapper024();
  if (mapperCode ==  25) mapper = new Mapper025();
  if (mapperCode ==  26) mapper = new Mapper026();
  if (mapperCode ==  30) mapper = new Mapper030();
  if (mapperCode ==  32) mapper = new Mapper032();
  if (mapperCode ==  33) mapper = new Mapper033();
  if (mapperCode ==  34) mapper = new Mapper034();
  if (mapperCode ==  60) mapper = new Mapper060();
  if (mapperCode ==  66) mapper = new Mapper066();
  if (mapperCode ==  69) mapper = new Mapper069();
  if (mapperCode ==  70) mapper = new Mapper070();
  if (mapperCode ==  71) mapper = new Mapper071();
  if (mapperCode ==  73) mapper = new Mapper073();
  if (mapperCode ==  75) mapper = new Mapper075();
  if (mapperCode ==  78) mapper = new Mapper078();
  if (mapperCode ==  79) mapper = new Mapper079();
  if (mapperCode ==  85) mapper = new Mapper085();
  if (mapperCode ==  86) mapper = new Mapper086();
  if (mapperCode ==  87) mapper = new Mapper087();
  if (mapperCode ==  88) mapper = new Mapper088();
  if (mapperCode ==  89) mapper = new Mapper089();
  if (mapperCode ==  93) mapper = new Mapper093();
  if (mapperCode ==  94) mapper = new Mapper094();
  if (mapperCode ==  97) mapper = new Mapper097();
  if (mapperCode == 113) mapper = new Mapper113();
  if (mapperCode == 140) mapper = new Mapper140();
  if (mapperCode == 152) mapper = new Mapper152();
  if (mapperCode == 154) mapper = new Mapper154();
  if (mapperCode == 156) mapper = new Mapper156();
  if (mapperCode == 180) mapper = new Mapper180();
  if (mapperCode == 184) mapper = new Mapper184();
  if (mapperCode == 190) mapper = new Mapper190();
  if (mapperCode == 193) mapper = new Mapper193();
  if (mapperCode == 206) mapper = new Mapper206();
  if (mapperCode == 207) mapper = new Mapper207();
  if (mapperCode == 232) mapper = new Mapper232();
  if (mapperCode == 240) mapper = new Mapper240();
  if (mapperCode == 241) mapper = new Mapper241();
  if (mapperCode == 244) mapper = new Mapper244();
  if (mapperCode == 246) mapper = new Mapper246();

  // If no mapper was found, return null (error) now 
  if (mapper == NULL)
  {
	  fprintf(stderr, "Could not find mapper for code: %u\n", mapperCode);
    return unsupported_mapper;
  } 

  // Assigning backwards pointers to cartdrige and emulator now
  mapper->cart_ = new_cart;
  mapper->emu_ = this;

	RETURN_ERR( ppu.open_chr( new_cart->chr(), new_cart->chr_size() ) );
	
	cart = new_cart;
	memset( impl->unmapped_page, unmapped_fill, sizeof impl->unmapped_page );
	reset( true, true );

	return 0;
}

Nes_Core::~Nes_Core()
{
	close();
	delete impl;
}

void Nes_Core::save_state( Nes_State_* out ) const
{
	out->clear();
	
	out->nes = nes;
	out->nes_valid = true;
	
	*out->cpu = cpu::r;
	out->cpu_valid = true;
	
	*out->joypad = joypad;
	out->joypad_valid = true;
	
	impl->apu.save_state( out->apu );
	out->apu_valid = true;
	
	ppu.save_state( out );
	
	memcpy( out->ram, cpu::low_mem, out->ram_size );
	out->ram_valid = true;
	
	out->sram_size = 0;
	if ( sram_present )
	{
		out->sram_size = sizeof impl->sram;
		memcpy( out->sram, impl->sram, out->sram_size );
	}
	
	out->mapper->size = 0;
	mapper->save_state( *out->mapper );
	out->mapper_valid = true;
}

void Nes_Core::save_state( Nes_State* out ) const
{
	save_state( reinterpret_cast<Nes_State_*>(out) );
}

void Nes_Core::load_state( Nes_State_ const& in )
{
	disable_rendering();
	error_count = 0;
	
	if ( in.nes_valid )
		nes = in.nes;
	
	// always use frame count
	ppu.burst_phase = 0; // avoids shimmer when seeking to same time over and over
	nes.frame_count = in.nes.frame_count;
	if ( (frame_count_t) nes.frame_count == invalid_frame_count )
		nes.frame_count = 0;
	
	if ( in.cpu_valid )
		cpu::r = *in.cpu;
	
	if ( in.joypad_valid )
		joypad = *in.joypad;
	
	if ( in.apu_valid )
	{
		impl->apu.load_state( *in.apu );
		// prevent apu from running extra at beginning of frame
		impl->apu.end_frame( -(int) nes.timestamp / ppu_overclock );
	}
	else
	{
		impl->apu.reset();
	}
	
	ppu.load_state( in );
	
	if ( in.ram_valid )
		memcpy( cpu::low_mem, in.ram, in.ram_size );
	
	sram_present = false;
	if ( in.sram_size )
	{
		sram_present = true;
		memcpy( impl->sram, in.sram, min( (int) in.sram_size, (int) sizeof impl->sram ) );
		enable_sram( true ); // mapper can override (read-only, unmapped, etc.)
	}
	
	if ( in.mapper_valid ) // restore last since it might reconfigure things
		mapper->load_state( *in.mapper );
}

void Nes_Core::enable_prg_6000()
{
	sram_writable = 0;
	sram_readable = 0;
	lrom_readable = 0x8000;
}

void Nes_Core::enable_sram( bool b, bool read_only )
{
	sram_writable = 0;
	if ( b )
	{
		if ( !sram_present )
		{
			sram_present = true;
			memset( impl->sram, 0xFF, impl->sram_size );
		}
		sram_readable = sram_end;
		if ( !read_only )
			sram_writable = sram_end;
		cpu::map_code( 0x6000, impl->sram_size, impl->sram );
	}
	else
	{
		sram_readable = 0;
		for ( int i = 0; i < impl->sram_size; i += cpu::page_size )
			cpu::map_code( 0x6000 + i, cpu::page_size, impl->unmapped_page );
	}
}

inline void Nes_Core::cpu_adjust_time( int n )
{
	ppu_2002_time   -= n;
	cpu_time_offset += n;
	cpu::reduce_limit( n );
}

// I/O and sound

int Nes_Core::read_dmc( void* data, nes_addr_t addr )
{
	Nes_Core* emu = (Nes_Core*) data;
	int result = *emu->cpu::get_code( addr );
	if ( wait_states_enabled )
		emu->cpu_adjust_time( 4 );
	return result;
}

void Nes_Core::apu_irq_changed( void* emu )
{
	((Nes_Core*) emu)->irq_changed();
}

void Nes_Core::write_io( nes_addr_t addr, int data )
{
	// sprite dma
	if ( addr == 0x4014 )
	{
		ppu.dma_sprites( clock(), cpu::get_code( data * 0x100 ) );
		cpu_adjust_time( 513 );
		return;
	}
	
	// joypad strobe
	if ( addr == 0x4016 )
	{
		// if strobe goes low, latch data
		if ( joypad.w4016 & 1 & ~data )
		{
			joypad.joypad_latches [0] = current_joypad [0];
			joypad.joypad_latches [1] = current_joypad [1];
		}
		joypad.w4016 = data;
		return;
	}
	
	// apu
	if ( unsigned (addr - impl->apu.start_addr) <= impl->apu.end_addr - impl->apu.start_addr )
	{
		impl->apu.write_register( clock(), addr, data );
		if ( wait_states_enabled )
		{
			if ( addr == 0x4010 || (addr == 0x4015 && (data & 0x10)) )
			{
				impl->apu.run_until( clock() + 1 );
				event_changed();
			}
		}
		return;
	}
}

int Nes_Core::read_io( nes_addr_t addr )
{
	if ( (addr & 0xFFFE) == 0x4016 )
	{
		// to do: to aid with recording, doesn't emulate transparent latch,
		// so a game that held strobe at 1 and read $4016 or $4017 would not get
		// the current A status as occurs on a NES
		unsigned long result = joypad.joypad_latches [addr & 1];
		if ( !(joypad.w4016 & 1) )
			joypad.joypad_latches [addr & 1] = (result >> 1) | 0x80000000;
		return result & 1;
	}
	
	if ( addr == Nes_Apu::status_addr )
		return impl->apu.read_status( clock() );
	
	return addr >> 8; // simulate open bus
}

// CPU

const int irq_inhibit_mask = 0x04;

nes_addr_t Nes_Core::read_vector( nes_addr_t addr )
{
	uint8_t const* p = cpu::get_code( addr );
	return p [1] * 0x100 + p [0];
}

void Nes_Core::reset( bool full_reset, bool erase_battery_ram )
{
	if ( full_reset )
	{
		cpu::reset( impl->unmapped_page );
		cpu_time_offset = -1;
		clock_ = 0;
		
		// Low RAM
		memset( cpu::low_mem, 0xFF, low_ram_size );
		cpu::low_mem [8] = 0xf7;
		cpu::low_mem [9] = 0xef;
		cpu::low_mem [10] = 0xdf;
		cpu::low_mem [15] = 0xbf;
		
		// SRAM
		lrom_readable = 0;
		sram_present = true;
		enable_sram( false );
		if ( !cart->has_battery_ram() || erase_battery_ram )
			memset( impl->sram, 0xFF, impl->sram_size );
		
		joypad.joypad_latches [0] = 0;
		joypad.joypad_latches [1] = 0;
		
		nes.frame_count = 0;
	}
	
	// to do: emulate partial reset
	
	ppu.reset( full_reset );
	impl->apu.reset();
	
	mapper->reset();
	
	cpu::r.pc = read_vector( 0xFFFC );
	cpu::r.sp = 0xfd;
	cpu::r.a = 0;
	cpu::r.x = 0;
	cpu::r.y = 0;
	cpu::r.status = irq_inhibit_mask;
	nes.timestamp = 0;
	error_count = 0;
}

void Nes_Core::vector_interrupt( nes_addr_t vector )
{
	cpu::push_byte( cpu::r.pc >> 8 );
	cpu::push_byte( cpu::r.pc & 0xFF );
	cpu::push_byte( cpu::r.status | 0x20 ); // reserved bit is set
	
	cpu_adjust_time( 7 );
	cpu::r.status |= irq_inhibit_mask;
	cpu::r.pc = read_vector( vector );
}

inline nes_time_t Nes_Core::earliest_irq( nes_time_t present )
{
	return min( impl->apu.earliest_irq( present ), mapper->next_irq( present ) );
}

void Nes_Core::irq_changed()
{
	cpu_set_irq_time( earliest_irq( cpu_time() ) );
}

inline nes_time_t Nes_Core::ppu_frame_length( nes_time_t present )
{
	nes_time_t t = ppu.frame_length();
	if ( t > present )
		return t;
	
	ppu.render_bg_until( clock() ); // to do: why this call to clock() rather than using present?
	return ppu.frame_length();
}

inline nes_time_t Nes_Core::earliest_event( nes_time_t present )
{
	// PPU frame
	nes_time_t t = ppu_frame_length( present );
	
	// DMC
	if ( wait_states_enabled )
		t = min( t, impl->apu.next_dmc_read_time() + 1 );
	
	// NMI
	t = min( t, ppu.nmi_time() );
	
	if ( single_instruction_mode )
		t = min( t, present + 1 );
	
	return t;
}

void Nes_Core::event_changed()
{
	cpu_set_end_time( earliest_event( cpu_time() ) );
}

#undef NES_EMU_CPU_HOOK
#ifndef NES_EMU_CPU_HOOK
	#define NES_EMU_CPU_HOOK( cpu, end_time ) cpu::run( end_time )
#endif

nes_time_t Nes_Core::emulate_frame_()
{
	Nes_Cpu::result_t last_result = cpu::result_cycles;
	int extra_instructions = 0;
	while ( true )
	{
		// Add DMC wait-states to CPU time
		if ( wait_states_enabled )
		{
			impl->apu.run_until( cpu_time() );
			clock_ = cpu_time_offset;
		}
		
		nes_time_t present = cpu_time();
		if ( present >= ppu_frame_length( present ) )
		{
			if ( ppu.nmi_time() <= present )
			{
				// NMI will occur next, so delayed CLI and SEI don't need to be handled.
				// If NMI will occur normally ($2000.7 and $2002.7 set), let it occur
				// next frame, otherwise vector it now.
				
				if ( !(ppu.w2000 & 0x80 & ppu.r2002) )
				{
					/* vectored NMI at end of frame */
					vector_interrupt( 0xFFFA );
					present += 7;
				}
				return present;
			}
			
			if ( extra_instructions > 2 )
			{
				return present;
			}
			
			if ( last_result != cpu::result_cli && last_result != cpu::result_sei &&
					(ppu.nmi_time() >= 0x10000 || (ppu.w2000 & 0x80 & ppu.r2002)) )
				return present;
			
			/* Executing extra instructions for frame */
			extra_instructions++; // execute one more instruction
		}
		
		// NMI
		if ( present >= ppu.nmi_time() )
		{
			ppu.acknowledge_nmi();
			vector_interrupt( 0xFFFA );
			last_result = cpu::result_cycles; // most recent sei/cli won't be delayed now
		}
		
		// IRQ
		nes_time_t irq_time = earliest_irq( present );
		cpu_set_irq_time( irq_time );
		if ( present >= irq_time && (!(cpu::r.status & irq_inhibit_mask) ||
				last_result == cpu::result_sei) )
		{
			if ( last_result != cpu::result_cli )
			{
				/* IRQ vectored */
				mapper->run_until( present );
				vector_interrupt( 0xFFFE );
			}
			else
			{
				// CLI delays IRQ
				cpu_set_irq_time( present + 1 );
			}
		}
		
		// CPU
		nes_time_t end_time = earliest_event( present );
		if ( extra_instructions )
			end_time = present + 1;
		unsigned long cpu_error_count = cpu::error_count();
		last_result = NES_EMU_CPU_HOOK( cpu, end_time - cpu_time_offset - 1 );
		cpu_adjust_time( cpu::time() );
		clock_ = cpu_time_offset;
		error_count += cpu::error_count() - cpu_error_count;
	}
}

nes_time_t Nes_Core::emulate_frame(int joypad1, int joypad2)
{
	current_joypad [0] = (joypad1 |= ~0xFF);
	current_joypad [1] = (joypad2 |= ~0xFF);

	cpu_time_offset = ppu.begin_frame( nes.timestamp ) - 1;
	ppu_2002_time = 0;
	clock_ = cpu_time_offset;
	
	// TODO: clean this fucking mess up
	auto t0 = emulate_frame_();
	impl->apu.run_until_( t0 );
	clock_ = cpu_time_offset;
	auto t1 = cpu_time();
	impl->apu.run_until_( t1 );
	
	nes_time_t ppu_frame_length = ppu.frame_length();
	nes_time_t length = cpu_time();
	nes.timestamp = ppu.end_frame( length );
	mapper->end_frame( length );

	impl->apu.end_frame( ppu_frame_length );
	
	disable_rendering();
	nes.frame_count++;
	
	return ppu_frame_length;
}

void Nes_Core::add_mapper_intercept( nes_addr_t addr, unsigned size, bool read, bool write )
{
	int end = (addr + size + (page_size - 1)) >> page_bits;
	for ( int page = addr >> page_bits; page < end; page++ )
	{
		data_reader_mapped [page] |= read;
		data_writer_mapped [page] |= write;
	}
}
