/*
	Highly inefficient and boring interpreter. Nothing special here
*/

#include "types.h"

#include "../sh4_interpreter.h"
#include "../sh4_opcode_list.h"
#include "../sh4_core.h"
#include "hw/aica/aica_if.h"
#include "../sh4_interrupts.h"
#include "hw/sh4/sh4_mem.h"
#include "../dyna/blockmanager.h"
#include "../sh4_sched.h"

#define CPU_RATIO      (8)

static s32 l;

static void ExecuteOpcode(u16 op)
{
	if (sr.FD == 1 && OpDesc[op]->IsFloatingPoint())
		RaiseFPUDisableException();
	OpPtr[op](op);
	l -= CPU_RATIO;
}

void Sh4_int_Run()
{
	sh4_int_bCpuRun=true;

	l = SH4_TIMESLICE;

   do
   {
#if !defined(NO_MMU)
      try {
#endif
         do
         {
            u32 addr = next_pc;
            next_pc += 2;
            u32 op = IReadMem16(addr);

            ExecuteOpcode(op);
         } while (l > 0);
         l += SH4_TIMESLICE;
         UpdateSystem_INTC();
#if !defined(NO_MMU)
      }
      catch (SH4ThrownException& ex) {
         Do_Exception(ex.epc, ex.expEvn, ex.callVect);
         l -= CPU_RATIO * 5;	// an exception requires the instruction pipeline to drain, so approx 5 cycles
      }
#endif
   } while(sh4_int_bCpuRun);
   
   sh4_int_bCpuRun=false;
}

void Sh4_int_Stop()
{
	if (sh4_int_bCpuRun)
		sh4_int_bCpuRun=false;
}

void Sh4_int_Start()
{
	if (!sh4_int_bCpuRun)
		sh4_int_bCpuRun=true;
}


void Sh4_int_Step()
{
	if (sh4_int_bCpuRun)
	{
		WARN_LOG(INTERPRETER, "Sh4 Is running , can't step");
	}
	else
	{
		u32 op=ReadMem16(next_pc);
		next_pc+=2;
		ExecuteOpcode(op);
	}
}

void Sh4_int_Skip()
{
	if (!sh4_int_bCpuRun)
		next_pc+=2;
}

void Sh4_int_Reset(bool Manual)
{
   if (sh4_int_bCpuRun)
      return;

   next_pc = 0xA0000000;

   memset(r,0,sizeof(r));
   memset(r_bank,0,sizeof(r_bank));

   gbr=ssr=spc=sgr=dbr=vbr=0;
   mac.full=pr=fpul=0;

   sh4_sr_SetFull(0x700000F0);
   old_sr.status=sr.status;
   UpdateSR();

   fpscr.full = 0x0004001;
   old_fpscr=fpscr;
   UpdateFPSCR();

   //Any more registers have default value ?
   INFO_LOG(INTERPRETER, "Sh4 Reset");
}

bool Sh4_int_IsCpuRunning()
{
	return sh4_int_bCpuRun;
}

//TODO : Check for valid delayslot instruction
void ExecuteDelayslot()
{
#if !defined(NO_MMU)
   try {
#endif
      u32 addr = next_pc;
      next_pc += 2;
      u32 op = IReadMem16(addr);

      if (op != 0)
            ExecuteOpcode(op);
#if !defined(NO_MMU)
   }
   catch (SH4ThrownException& ex) {
		AdjustDelaySlotException(ex);
      //printf("Delay slot exception\n");
      throw ex;
   }
#endif
}

void ExecuteDelayslot_RTE()
{
#if !defined(NO_MMU)
   try {
#endif
      ExecuteDelayslot();
#if !defined(NO_MMU)
   }
   catch (SH4ThrownException& ex) {
      ERROR_LOG(INTERPRETER, "Exception in RTE delay slot");
   }
#endif
}

//General update

//3584 Cycles
#define AICA_SAMPLE_GCM 441
#define AICA_SAMPLE_CYCLES (SH4_MAIN_CLOCK/(44100/AICA_SAMPLE_GCM)*32)

int aica_sched = -1;
int rtc_sched = -1;

//14336 Cycles

const int AICA_TICK=145124;

static int AicaUpdate(int tag, int c, int j)
{
   UpdateArm(512*32);
   UpdateAica(1*32);

	return AICA_TICK;
}

static int DreamcastSecond(int tag, int c, int j)
{
	settings.dreamcast.RTC++;
#if FEAT_SHREC != DYNAREC_NONE
	bm_Periodical_1s();
#endif

	return SH4_MAIN_CLOCK;
}

// every SH4_TIMESLICE Cycles (fixed)
int UpdateSystem()
{
	//this is an optimisation (mostly for ARM)
	//makes scheduling easier !
	//update_fp* tmu=pUpdateTMU;
	
	Sh4cntx.sh4_sched_next -= SH4_TIMESLICE;
	if (Sh4cntx.sh4_sched_next<0)
		sh4_sched_tick(SH4_TIMESLICE);

	return Sh4cntx.interrupt_pend;
}

int UpdateSystem_INTC()
{
	if (UpdateSystem())
		return UpdateINTC();
	else
		return 0;
}

static void sh4_int_resetcache()
{
}

//Get an interface to sh4 interpreter
void Get_Sh4Interpreter(sh4_if* rv)
{
	rv->Run=Sh4_int_Run;
	rv->Stop=Sh4_int_Stop;
	rv->Start=Sh4_int_Start;
	rv->Step=Sh4_int_Step;
	rv->Skip=Sh4_int_Skip;
	rv->Reset=Sh4_int_Reset;
	rv->Init=Sh4_int_Init;
	rv->Term=Sh4_int_Term;
	rv->IsCpuRunning=Sh4_int_IsCpuRunning;

	rv->ResetCache=sh4_int_resetcache;
}

void Sh4_int_Init()
{
	verify(sizeof(Sh4cntx)==448);

	if (aica_sched == -1)
	{
		aica_sched=sh4_sched_register(0,&AicaUpdate);
		sh4_sched_request(aica_sched,AICA_TICK);

		rtc_sched=sh4_sched_register(0,&DreamcastSecond);
		sh4_sched_request(rtc_sched,SH4_MAIN_CLOCK);
	}
	memset(&p_sh4rcb->cntx, 0, sizeof(p_sh4rcb->cntx));
}

void Sh4_int_Term()
{
	Sh4_int_Stop();
	INFO_LOG(INTERPRETER, "Sh4 Term");
}
