#include "raine.h"
#include "ingame.h" // print_ingame
#include "profile.h"

/* Profiling in SDL : */
/* They announce RDTSC for v1.3. */
/* But I need it now for RDTSC profiling (the rdtsc timer has 1 million */
/* ticks / second, where the default SDL timer has now 1000 ticks / */
/* second... It's clearly not enough. */

/* In fact this file is necessary only if you want to use SDL alone.
   If you want to use SDL AND Allegro, then just forget about it. */

int use_rdtsc;
UINT32 timer_next_update;	// timer_frame_count at next fps() update
UINT32 timer_start_count;	// timer_frame_count at fps() start
UINT32 render_start_count;	// render_frame_count at fps() start

UINT32 cpu_frame_count;		// number of frames of cpu emulation
UINT32 render_frame_count;	// number of frames of video emulation
UINT32 skip_frame_count;		// number of frames since last video emulation

struct PROFILE_RESULTS profile_results[PRO_COUNT] =
{
   { " Misc", 0, 0 },
   { "  Cpu", 0, 0 },
   { "Sound", 0, 0 },
   { " Draw", 0, 0 },
   { " Blit", 0, 0 },
   { "  Pal", 0, 0 },
   { " Free", 0, 0 },
};

#ifdef RDTSC_PROFILE

#if defined(RAINE_DOS) || defined(RAINE_UNIX) || defined(__MINGW32__)
#define RDTSC_32(dest) ({				    \
   __asm__ __volatile__ (				    \
   " rdtsc "                                                \
   : "=a" (((UINT32 *)dest)[0])                             \
   :							    \
   : "%edx" ); })                                           \

#define RDTSC_64(dest) ({				    \
   __asm__ __volatile__ (				    \
   " rdtsc "                                                \
   : "=a" (((UINT32 *)dest)[0]), "=d" (((UINT32 *)dest)[1]) \
   : ); })						    \

#else

#undef UINT64
#define UINT64 __int64

DEF_INLINE UINT32 read_rdtsc_32(void)
{
	UINT32 time_rdtsc;

	__asm
	{
		push	eax
		push	edx
//		rdtsc
		_emit	0x0f
		_emit	0x31
		mov		time_rdtsc, eax
		pop		edx
		pop		eax
	}

	return time_rdtsc;
}

DEF_INLINE UINT64 read_rdtsc_64(void)
{
   UINT32 time_rdtsc_low,time_rdtsc_high;

   __asm
   {
      push	eax
      push	edx
    //rdtsc
      _emit	0x0f
      _emit	0x31
      mov	time_rdtsc_low, eax
      mov	time_rdtsc_high, edx
      pop	edx
      pop	eax
   }

   return ( (time_rdtsc_high<<32) | (time_rdtsc_low));
}

#define RDTSC_32(dest)		((UINT32 *)dest)[0] = read_rdtsc_32()

#define RDTSC_64(dest)		((UINT64 *)dest)[0] = read_rdtsc_64()

#endif

typedef struct PROFILE
{
   UINT32 start;			// Internal (start time)
   UINT32 stop; 		// Internal (stop time)
   UINT32 cycles;		// Cycle Count
   UINT8 state; 		// 0 - Empty; 1 - started; 2 - stopped
} PROFILE;

static struct PROFILE Profile[PRO_COUNT];

void ProfileStart(UINT8 mode)
{
   UINT32 rdtsc_cycles;

   if(Profile[mode].state!=1){

   RDTSC_32( &rdtsc_cycles );

   Profile[mode].start = rdtsc_cycles;

   Profile[mode].state = 1;

   }
}

void ProfileStop(UINT8 mode)
{
   UINT32 rdtsc_cycles;

   if(Profile[mode].state==1){

   RDTSC_32( &rdtsc_cycles );

   Profile[mode].stop = rdtsc_cycles;

   Profile[mode].cycles += (Profile[mode].stop - Profile[mode].start);

   Profile[mode].state = 2;

   }
}

#define P_UPDATE_RATE	(8)

static int pcount;

void UpdateProfile(void)
{
   if(Profile[0].state==2){

   int total,total2,ta;

   pcount++;

   if(pcount>P_UPDATE_RATE){
   pcount=0;

   for(ta=0;ta<PRO_COUNT;ta++){
      Profile[ta].cycles = Profile[ta].cycles / PRO_COUNT;
   }

   total2 = total = Profile[0].cycles;

   if(raine_cfg.show_fps_mode==3){

   for(ta=1;ta<PRO_COUNT;ta++){
      if(Profile[ta].state==2){
	 profile_results[ta].percent = (Profile[ta].cycles * 100)/total;
	 total2 -= (Profile[ta].cycles);
      }
      else{
	 profile_results[ta].percent = 0;
      }
   }
   if(total2>0){
      profile_results[0].percent = (total2 * 100)/total;
   }
   else{
      profile_results[0].percent = 0;
   }

   }
   else{

   for(ta=1;ta<PRO_COUNT;ta++){
      if(Profile[ta].state==2){
	 profile_results[ta].cycles = Profile[ta].cycles;
	 total2 -= (Profile[ta].cycles);
      }
      else{
	 profile_results[ta].cycles = 0;
      }
   }
   profile_results[0].cycles = total2;

   }


   for(ta=0;ta<PRO_COUNT;ta++){
      Profile[ta].state=0;
      Profile[ta].cycles=0;
   }

   }

   }

}

#endif

static void update_fps(int quiet)
{
  if(raine_cfg.show_fps_mode>4) raine_cfg.show_fps_mode=0;
  if((use_rdtsc==0)&&(raine_cfg.show_fps_mode>2)) raine_cfg.show_fps_mode=0;

  switch(raine_cfg.show_fps_mode){
  case 0x00:
    if(quiet) print_ingame(120,"FPS Counter Disabled");
    return;
    break;
  case 0x01:
    timer_next_update = read_ingame_timer() + 60;	// set time of next update
    render_start_count = render_frame_count + 1;	// render count at start
    if(quiet) print_ingame(120,"FPS Counter Enabled");
    return;
    break;
  case 0x02:
    timer_start_count = read_ingame_timer();		// start time
    timer_next_update = timer_start_count + 60;	// set time of next update
    render_start_count = render_frame_count + 1;	// render count at start
    if(quiet) print_ingame(120,"Average FPS Counter Enabled");
    return;
    break;
  case 0x03:
    if(quiet) print_ingame(120,"RDTSC Timing Enabled (percentages)");
    return;
    break;
  case 0x04:
    if(quiet) print_ingame(120,"RDTSC Timing Enabled (cycles)");
    return;
    break;
  }
}

void switch_fps_mode(void)
{
   raine_cfg.show_fps_mode++;
   update_fps(1);
}

void init_fps(void)
{
   update_fps(0);
}

/******************************************************************************/

volatile static UINT32 pc_timer;			// timer, returned by read_ingame_timer()

#ifdef RDTSC_PROFILE

volatile static UINT64 rdtsc_times[6];		// timer, returned by read_ingame_timer()

static UINT64 last_rdtsc_counter;		// rdtsc count from last read_ingame_timer()
UINT32 cycles_per_frame;			// cpu cycles per frame, or 0 if not computed
static UINT32 cycles_per_second;		// cpu cycles per second
static UINT32 remained_cycles;			// left over cycles from last read_ingame_timer()

#endif

#ifdef RDTSC_PROFILE

static UINT32 timer_setup_rdtsc(UINT32 timer_interval, void *param)
{
  UINT64 *rdtsc_times = (UINT64*)param;
  int count = 0;
  while (count < 6 && rdtsc_times[count])
    count++;
  if(count<6){
    RDTSC_64( &rdtsc_times[count] );
  }
  return timer_interval;
}

/*  static void ingame_timer_proc_rdtsc(void) */
/*  { */
/*     saUpdateSound(0);		// HIRO-SHI: check stream buffer */
/*  } */

/*  END_OF_FUNCTION(ingame_timer_proc_rdtsc); */

#endif

static UINT32 ingame_timer_proc_nordtsc(UINT32 timer_interval, void *param)
{
  // saUpdateSound(0);		// HIRO-SHI: check stream buffer
   ((UINT32 *)param)++;
   return timer_interval;
}

static SDL_TimerID id;

void reset_ingame_timer(void)
{
  if (!cycles_per_frame)
    return;

  pc_timer = 0;
#ifdef RDTSC_PROFILE
   if(use_rdtsc){
      RDTSC_64( &last_rdtsc_counter );
      remained_cycles = 0;
   }
#endif
   cpu_frame_count = 0;
   render_frame_count = 0;
   init_fps();
}

void start_ingame_timer(void)
{
   int ta;

#ifdef RDTSC_PROFILE
   if(!use_rdtsc){
#endif
     // Notice that the minimum granularity of the timer is now 10ms
     // So this function has very little chances to be called 60 times
     // / second. It's just something like "hope for the best" !

     id = SDL_AddTimer(1000/60,ingame_timer_proc_nordtsc,&pc_timer);

#ifdef RDTSC_PROFILE
   }
   else{

      if(!cycles_per_frame){		// Need to work out cycles/frame, this takes about 1/2 second

      pc_timer = 0;

      memset(rdtsc_times,0,sizeof(rdtsc_times));
      id = SDL_AddTimer(100,timer_setup_rdtsc,rdtsc_times);

      while (!rdtsc_times[5]);

      SDL_RemoveTimer(id);
      id = 0;

      cycles_per_second = (UINT32) ((rdtsc_times[5] - rdtsc_times[0])*2);

      cycles_per_frame = cycles_per_second / 60;

#ifdef RAINE_DEBUG
      print_debug("Approx CPU Speed: %d MHz\n", cycles_per_second/1000000);
      print_debug("RDTSC Cycles/frame: %d/60 = %d\n", cycles_per_second, cycles_per_frame);
#endif
      }
   }
#endif
   reset_ingame_timer();
}

void stop_ingame_timer(void)
{
#ifdef RDTSC_PROFILE
/*     if(use_rdtsc) */
/*        remove_int(ingame_timer_proc_rdtsc); */
/*     else */
#endif
  if (id)
    SDL_RemoveTimer(id);
}

#ifdef RDTSC_PROFILE
UINT32 read_ingame_cycles(UINT64 *last_rdtsc_counter) {
  UINT64 rdtsc_counter;
  UINT32 dif=0;
  if(use_rdtsc){
    RDTSC_64( &rdtsc_counter );

    dif = (rdtsc_counter - *last_rdtsc_counter);
    *last_rdtsc_counter = rdtsc_counter;
  }
  return dif;
}
#endif

UINT32 read_ingame_timer(void)
{
#ifdef RDTSC_PROFILE
   if(use_rdtsc){

      UINT64 rdtsc_counter;
      UINT32 dif;

      RDTSC_64( &rdtsc_counter );

      dif = (rdtsc_counter - last_rdtsc_counter) + remained_cycles;

      if(dif >= cycles_per_frame){
	 last_rdtsc_counter = rdtsc_counter;
	 while(dif >= cycles_per_frame){
	    dif -= cycles_per_frame;
	    pc_timer++;
	 }
	 remained_cycles = dif;
      }

   }
#endif
   return pc_timer;
}

#ifdef RDTSC_PROFILE
void reset_rdtsc_timer(UINT64 *last_rdtsc_counter) {
   if(use_rdtsc){
      RDTSC_64( last_rdtsc_counter );
   }
}
#endif
