/*
 *  acm : an aerial combat simulator for X
 *  Copyright (C) 1991-1998  Riley Rainey
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; version 2 dated June, 1991.
 *
 *  This program 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program;  if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave., Cambridge, MA 02139, USA.
 */

/*
 * IMPLEMENTATION NOTES.
 * 
 * This implementation basically exports the update_redraw() function which is
 * responsible for all the dynamics and graphics updates. Each time that function
 * gets called, a new updated frame is drawn. This function is also responsible
 * to keep the internal notion of time and "delta t".
 * 
 * This "delta t" is periodically adjusted so that the simulation time follows
 * the wall clock. Since the wall clock is given by the gettimeofday() function,
 * and since this function has very high precision but typically very coarse grain,
 * that comparison can be made only at wide intervals of time so that the grain
 * becomes negligible and the interval of time is meaningful.
 * 
 * Dynamics calculations are generally much lighter in terms of CPU usage than
 * graphic 3D calculations and screen updates, so it does not hurt to perform
 * several cycles of dynamic updates per each drawn frame in order to improve
 * the numerical stability of the simulated models (mainly, of the landing gear
 * with its very demanding timings). 50 Hz may be a good compromise, so if the
 * requested frame rate is, say, 20 Hz, then the dynamics should be re-calculated
 * 2 or 3 times per frame so attaining an update rate of 40 or 60 Hz respectively.
 */

#include <sys/time.h>
#include <assert.h>

#include "alarm.h"
#include "box.h"
#include "dis_if.h"
#include "events.h"
#include "pm.h"
#include "prompt.h"
#include "render.h"
#include "../util/memory.h"
#include "../util/timer.h"

#define update_IMPORT
#include "update.h"
#include "radar.h"

/**
 * Optimal update rate (Hz). This is a compromise between numerical stability
 * (requiring higher values) and CPU saving (requiring 1 as a minimum).
 * See also the comments about the updates_per_frame variable.
 */
#define UPDATE_RATE_OPTIMAL 60

#define FRAME_RATE_FEEDBACK_INTERVAL 60.0

/**
 * How many times to cycle through the dynamic calculations per displayed frame.
 * This value must be at least 1 (obviously), but the resulting update rate
 *
 *     update_rate = updates_per_frame * frame_rate
 * 
 * should be around optimal value. The actual attained update rate is
 * periodically evaluated, and this value is tuned accordingly in order to keep
 * the resulting update rate close to the optimal one.
 */
static int updates_per_frame = 10;

/**
 * When we started counting processed screen frames and dynamic updates
 * (monotonic system time, s).
 */
static double    start_count;

/** Number of processed screen frames since we start counting. */
static int       frame_count;

/** Current system monotonic time (s). */
static double sysmon;


/**
 * Returns the "system monotonic time" sysmon. Sysmon starts at 0 and
 * basically follows "gettimeofday()", but it is monotonic. It suffers of the
 * same coarse granularity of gettimeofday(), typically 10 ms under Linux and
 * 15 ms under Windows, so it is not suitable by itself for our simulator; it
 * is pretty useless to call this function at every update cycle of the
 * simulation because it would return the same value most of the times!
 */
static double update_updateSysmon()
{
	static int sysmon_initialized = 0;
	static struct timeval sysmon_prev;
	struct timeval curr;
	struct timezone tz;

	gettimeofday(&curr, &tz);
	
	if( ! sysmon_initialized ){
		/* Initialize monotonic clock. */
		sysmon_initialized = 1;
		sysmon_prev = curr;
		sysmon = 0.0;
	} else {
		/* Evaluates delta_usec since last invocation: */
		long delta_usec = 1000000 * (curr.tv_sec - sysmon_prev.tv_sec)
			+ (curr.tv_usec - sysmon_prev.tv_usec);
		if( delta_usec > 0 ){
			sysmon += 1e-6 * delta_usec;
			sysmon_prev = curr;
		} else if( delta_usec < 0 ){
			/* Clock step backward (user adjust, NTP, ...). Reset: */
			sysmon_prev = curr;
		}
	}
	return sysmon;
}


/**
 * Updates the simulation time curTime and the time interval deltaT overcoming
 * the limitation of the coarse granularity of the monotonic system time.
 * It does that by evaluating an average time interval deltaT between the calls
 * of this function, and then adjusting this interval so that the resulting
 * simulation time curTime smoothly follows the system monotonic time.
 * It is then guaranteed the sum of all the time intervals always matches the
 * total simulation time.
 */
void update_simulationTime ()
{
	static double dt = 0.01;  // current estimated dt
	static double t0;  // time of the last dt evaluation
	static int n; // no. of iterations
	
	/* Evaluate dt if enough data available. */
	n++;
	if( (n & 7) == 0  /* ...not too often */
	&& n > updates_per_frame /* ...include at least 1 frame */ ){
		double t1 = update_updateSysmon();
		if( t1 - t0 > 0.5 ){
			
			/* At least 0.5 s elapsed: several system ticks have been accounted,
			 * so time difference on sysmon is meaningful. Moreover, a good
			 * number of iterations have been accounted. Then the ratio
			 * (t1 - t0)/n is a good estimation of the real dt over the
			 * past n iterations.
			 * 
			 * But this is not enough. We also require the simulation time
			 * curTime to be as close as possible to the sysmon.
			 * So, evaluate dt so to realign curTime to the sysmon within a given
			 * recovery time. The recovery time is chosen so the realignment
			 * process be smooth and to prevent div by zero (1 term
			 * added).
			 * 
			 * Note that if the error is zero it means curTime is perfectly
			 * aligned with sysmon and the new evaluated dt is just the average
			 * time interval of the last n iterations.
			 */
			
			double error = curTime - t1;
			double recovery_time = 1.0 + fabs(error);
			dt = (recovery_time - error) * (t1 - t0) / (recovery_time * n);
			// Safety: bounds dt to +/-25% of the "optimal" update rate 
			if( dt < 0.75 / UPDATE_RATE_OPTIMAL )
				dt = 0.75 / UPDATE_RATE_OPTIMAL;
			else if( dt > 1.25 / UPDATE_RATE_OPTIMAL )
				dt = 1.25 / UPDATE_RATE_OPTIMAL;
			t0 = t1;
			n = 0;
		}
	}
	
	curTime += dt;
	deltaT = dt;
	halfDeltaTSquared = 0.5 * dt * dt;
}


/**
 * Basically updates the dynamics and displays a new frame on each viewer.
 * Performs the dynamics calculations updates over all the simulated entities,
 * updates the dead reckoning position of the remote entities, receives incoming
 * packets, draws the updates window frame on each active viewer, etc.
 * Periodically evaluates and returns the actual frame rate.
 * The caller is responsible to call this function periodically with a rate that
 * matches the user's requested frame rate, and avoiding to waste too much CPU
 * time by performing some delay from a frame to the next.
 * @return Actual frame rate (Hz) if non-negative, otherwise not available because
 * still evaluating. Note that a frame rate of zero is possible due to the int
 * limited precision!
 */
static int update_redraw(void)
{

	int       i, j;
	craft    *c;
	char     *killReason;

	/*
	 * Update the dynamics of the simulated entities and updates the dead
	 * reckoning of the remote entities.
	 */
	j = updates_per_frame;
	do {
		
		update_simulationTime();
		
		/*
		 * Jobs at high priority to be executed at UPDATE_RATE_OPTIMAL rate here.
		 */
	
		/* Update time in DIS interface module for correct DR calculations. */
		if (dis_if_haveAbsoluteTime)
			dis_if_setTimeAbsolute();
		else
			dis_if_setTime(curTime);
	
		/* Get latest remote events. */
		dis_if_receive();
		
		for ((i = 0, c = ptbl); i < manifest_MAXPLAYERS; (++i, ++c)) {
			if( c->type == CT_FREE || (c->flags & FL_BLACK_BOX) )
				continue;
			killReason = c->update(c);
			if( killReason != NULL )
				c->kill(c, killReason);
		}

		for ((i = 0, c = mtbl); i < manifest_MAXPROJECTILES; (++i, ++c)) {
			if(c->type == CT_FREE)
				continue;
			killReason = c->update(c);
			if( killReason != NULL )
				c->kill(c, killReason);
		}
		
		j--;
	} while(j > 0);
	
	/*
	 * Jobs with lesser priority to be executed at frame rate and
	 * at the "user's experience time scale":
	 */

	/*
	 * GUI events:
	 */
	events_window_keyb_buttons();

	box_input();

	/*
	 * Update radar emissions: visible targets and received beams.
	 */
	for (i = 0; i < manifest_MAXPLAYERS; i++){
		c = &ptbl[i];
		if( c->type != CT_FREE )
			radar_calculateEmissions(c);
	}
	
	/*
	 * Render cockpit view(s):
	 */

	frame_count++;
	render_drawCockpitViews();

	box_output();

	alarm_update();

	/*
	 * Evaluate actual frame rate and adjust update rate accordingly.
	 */
	int frame_rate = -1;
	double elapsed = sysmon - start_count;
	if (frame_count >= 2 && elapsed >= 0.2) {
		
		// Evaluate actual frame rate:
		frame_rate = frame_count / elapsed + 0.5;
		if( frame_rate < 1 )
			frame_rate = 1;
		
		// Adjust the update rate according to the actual frame rate:
		updates_per_frame = (double) UPDATE_RATE_OPTIMAL / frame_rate + 0.5;
		if( updates_per_frame < 1 )
			updates_per_frame = 1;
		
		// Reset frame rate counters:
		start_count = sysmon;
		frame_count = 0;
	}
	
	return frame_rate;
}


void update_loop(int frame_rate)
{
	if( frame_rate < 1 )
		frame_rate = 1;
	int frame_interval_ms = 1000 / frame_rate;
	
	double frame_rate_feedback_next = FRAME_RATE_FEEDBACK_INTERVAL;
	
	deltaT = 1.0 / UPDATE_RATE_OPTIMAL;
	halfDeltaTSquared = 0.5 * deltaT * deltaT;
	curTime = 0.0;

	timer_Type *t = timer_new();

	// Loop until there is at least a viewer.
	while ( vl_head != NULL ) {
		timer_reset(t);
		timer_start(t);
		int frame_rate_actual = update_redraw();
		if( frame_rate_actual >= 0 && curTime > frame_rate_feedback_next ){
			char s[99];
			snprintf(s, sizeof(s), "Frame rate: %d Hz", frame_rate_actual);
			prompt_broadcast_print(s);
			frame_rate_feedback_next = curTime + FRAME_RATE_FEEDBACK_INTERVAL;
		}
		timer_sleepMilliseconds(frame_interval_ms - timer_getElapsedMilliseconds(t));
	}
	
	memory_dispose(t);
}