//
//$ FA(20/07/2001): Replace 100000 by 10000 in mt_minper()

//$BLG 13/12/2005: - Timers precision, evaluation and reliability have been greatly improved.
//                 - new functions: 
//                   - _gettimerscapabilities[] [I I] - Retrieves client timer capabilities
//                   - _settimerperiod[Timer I] I     - Dynamic modification of timer period
//                 - _starttimer()                    - May now use period of 1ms and above (if possible on client)

#include <stdio.h>
#include <string.h>


#include <windows.h>
#include <winuser.h>
#include <mmsystem.h>

#include "common/kernel.h"
#include "listlab.h"
#include "common/common.h"
#include "OS_specific/windows/winscol.h"

/* reflexes type TIMER */
#define RFLTIMER_NB     1
#define RFLTIMER_CLOCK  0

int OBJTYPTIMER;


int OBJTYPTIMERMM;
int WM_TMM;
TIMECAPS tc;

#define MT_NB 4096
int mt_per[MT_NB];
int mt_nxt[MT_NB];
int mt_param[MT_NB];
int (*mt_fun[MT_NB])(int i,int param);
int mt_ind;

int mt_timer;
int mt_curper;
int flag_tm;

int mt_new()
{
	int i;

	for(i=0;i<mt_ind;i++) if (!mt_per[i]) return i;
	if (mt_ind>=MT_NB) return -1;
	mt_ind++;
	return mt_ind-1;
}

int mt_minper()
{
	int i,min;

	min=-1;
	for(i=0;i<mt_ind;i++)
		if ((mt_per[i])&&((min==-1)||(mt_per[i]<min))) min=mt_per[i];
	if (min==-1) return min;
	min>>=3;
	//$BLG - v4.6a4
	//Minimum value set from 10ms to system'min'ms
	//if (min<10) return 10;
	if ((uint) min < tc.wPeriodMin) return tc.wPeriodMin;
	if (min>10000) return 10000; //$ FA(20/07/2001)
	return min;
}


int mt_event(mmachine m,HWND h,unsigned msg,UINT x,LONG y,int *ret)
{
  int t,i;

//  MMechostr(MSKDEBUG,"### event\n",GetTickCount());
  //$BLG - v4.6a4 : Why this +1 ? counter is solely based on tickcount
  //t=GetTickCount()+1;
  t = timeGetTime();

  for(i=0;i<mt_ind;i++) 
    if (mt_per[i])
    {
    	if (t-mt_nxt[i]>=0)
		  {
			  (*mt_fun[i])(i,mt_param[i]);
			  //$BLG - v4.6a4
			  //Better precision, less losses: 990FPS instead of 970
			  //if (mt_per[i]) mt_nxt[i]+=(1+((t-mt_nxt[i])/mt_per[i]))*mt_per[i];
				if (mt_per[i]) mt_nxt[i] = t - ((t - mt_nxt[i]) % mt_per[i]) + mt_per[i];
		  }
	  }

  flag_tm=0;
  return 0;
}

void CALLBACK mt_clockCB(UINT sys,UINT uMsg,DWORD x,DWORD dw1,DWORD dw2)
{
	if (!flag_tm)
	{
		flag_tm=1;
		PostMessage(hscol,WM_TMM,0,0);
	}
}

int restart()
{
	int t;

	t=mt_minper();
//	MMechostr(MSKDEBUG,"restart %d %d %d\n",mt_curper,t,GetTickCount());
	if ((mt_curper)&&(t!=mt_curper))
	{
//		MMechostr(MSKDEBUG,"##stop %d\n",mt_curper);
		timeKillEvent(mt_timer);
		mt_curper=0;
	}
	if ((!mt_curper)&&(t!=-1))
	{
		mt_curper=t;
//		MMechostr(MSKDEBUG,"##restart %d\n",mt_curper);
		//$BLG - v4.6a4 : increasing timer accuracy
		//mt_timer=timeSetEvent(t,tc.wPeriodMax,mt_clockCB,0,TIME_PERIODIC);
		mt_timer=timeSetEvent(t,0,mt_clockCB,0,TIME_PERIODIC);
	}
	return 0;
}

int mt_start(int per,int param,int (*fun)(int i,int param))
{
	int i;
//	MMechostr(MSKDEBUG,"start %d\n",per);
	if ((per<=0)||(fun==NULL)) return -1;
	//$BLG - v4.6a4 - Removed next line
	//Furthermore, minimum value should be set to tc.wPeriodMin, not 10
	//if (per<10) per=10;
	i=mt_new();
	if (i<0) return -1;
	mt_per[i]=per;
	mt_param[i]=param;
	mt_fun[i]=fun;
	//mt_nxt[i]=GetTickCount()+per;
	mt_nxt[i]=timeGetTime()+per;
	restart();
//	MMechostr(MSKDEBUG,"### new %d\n",i); // SH000821
	return i;
}

int mt_del(int i)
{
//	MMechostr(MSKDEBUG,"### del %d\n",i); // SH000821
	if ((i<0)||(i>=MT_NB)) return -1;
	mt_per[i]=0;
	while((mt_ind>0)&&(mt_per[mt_ind-1]==0)) mt_ind--;
	restart();
	return 0;
}

int mt_ini()
{
	mt_ind=0;
	mt_curper=0;
	flag_tm=0;
	return 0;
}

int ClockCB(int sys,int param)
{
	int k;
	mmachine m;

	m=(mmachine)param;
	k=OBJbeginreflex(m,OBJTYPTIMERMM,sys,RFLTIMER_CLOCK);
	if (k==0) OBJcallreflex(m,0);
	return 0;
}

//$BLG - v4.6a4
int TMMMgettimerscapabilities(mmachine m)
{
  int iSuccess;
  
  if (MMpush(m, tc.wPeriodMin<<1)) return MERRMEM;
  if (MMpush(m, tc.wPeriodMax<<1)) return MERRMEM;
  if (MMpush(m, 4))                return MERRMEM;
  iSuccess = MBdeftab(m);
  return iSuccess;
}

//$BLG - v4.6a4
int TMMMsettimerperiod(mmachine m)
{
	int tmr, per, i;
	
	per = MMpull(m)>>1;
	tmr = MMpull(m)>>1;
	if ((tmr == NIL) || (per == NIL))    return MMpush(m, NIL);
	if ((uint) per < tc.wPeriodMin) per = tc.wPeriodMin;
	if ((uint) per > tc.wPeriodMax) per = tc.wPeriodMax;
	i = MMfetch(m,tmr,1)>>1;
	mt_nxt[i] = timeGetTime() + per;
	mt_per[i] = per;
	return MMpush(m, 0);
}

int TMMMstarttimer(mmachine m)
{
  int freq,k,sys;

  freq=MMget(m,0);
  if ((MMget(m,1)==NIL)||(freq<=0))
    {
      MMpull(m);
      MMset(m,0,NIL);
      return 0;
    }
  sys=mt_start(freq>>1,(int)m,ClockCB);
  if (sys<0)
    {
      MMpull(m);
      MMset(m,0,NIL);
      return 0;
    }
  if (MMpush(m,sys*2)) return MERRMEM;
  if (MMpush(m,0*2)) return MERRMEM;
  if (MMpush(m,3*2)) return MERRMEM;
  if (k=MBdeftab(m)) return k;
  return OBJcreate(m,OBJTYPTIMERMM,sys,-1,0);
}

int TMMMdeltimer2(mmachine m,int handsys,int objm)
{
	mt_del(handsys);
	MMstore(m,objm>>1,1,NIL); //SH000821
  return 0;
}

int TMMMdeltimer(mmachine m)
{
  int i;
  
  i=MMget(m,0)>>1;
  if (i==NIL) return 0;
  OBJdelTH(m,OBJTYPTIMERMM,MMfetch(m,i,1)>>1);
  MMset(m,0,0);
  return 0;
}

int TMMMrfltimer(mmachine m)
{
  return OBJaddreflex(m,OBJTYPTIMERMM,RFLTIMER_CLOCK);
}

#define NTIMEPKG 6
char* timename[NTIMEPKG]=
{
	"Timer","_starttimer","_rfltimer","_deltimer"
	//$BLG - v4.6a4 : Sta
	, "_gettimerscapabilities", "_settimerperiod"
	//$BLG - End
};

int (*timefun[NTIMEPKG])(mmachine m)=
{
 NULL,TMMMstarttimer,TMMMrfltimer,TMMMdeltimer
 //$BLG - v4.6a4 : Sta
 , TMMMgettimerscapabilities, TMMMsettimerperiod
 //$BLG - End
};

int timenarg[NTIMEPKG]=
{
 TYPTYPE,2,3,1
 //$BLG - v4.6a4 : Sta
 , 0, 2
 //$BLG - End
};

char* timetype[NTIMEPKG]=
{
    NULL,"fun [Chn I] Timer","fun [Timer fun [Timer u0] u1 u0] Timer","fun [Timer] I"
    //$BLG - v4.6a4 : Sta
    , "fun [] [I I]", "fun [Timer I] I"
    //$BLG - End
};

int SCOLloadTimer(mmachine m)
{
  timeGetDevCaps(&tc, sizeof(TIMECAPS));
	//$BLG - v4.6a4
	//Note: According to MSDN, timeBeginPeriod should be set to min value, not max value.
	//      The original approach was probably to optimize CPU use if very fast timers were
	//      not useful. But timeBeginPeriod() doesn't set a timer speed, it sets a timer
	//      resolution/precision. Up to v4.6a3, we were limited to the default precision
	//      (15ms on WinXP, thus 64 actions per second). Depending on system, we may now
	//      use up to 1000 actions per second.
	//timeBeginPeriod(tc.wPeriodMax);
	timeBeginPeriod(tc.wPeriodMin);
	WM_TMM=OBJgetUserEvent();
	OBJdefEvent(WM_TMM,mt_event);
	OBJTYPTIMERMM=OBJregister(RFLTIMER_NB,0,TMMMdeltimer2,"OBJTYPTIMERMM");
//$ FA(23/11/2001)
  if (tc.wPeriodMin <= 0 || tc.wPeriodMin > 1000) {
    const char* msg = 
      "Il se peut qu'il y ait un conflit avec d'autres logiciels intallés sur votre ordinateur."
      "Essayer de désactiver votre anti-virus (si vous en avez un) et de redémarrer votre ordinateur."
      "Pour en savoir plus, vous pouvez consulter la note technique à l'URL suivante : "
      "http://techno.cryonetworks.com/Support/SCOL/SCOL_Support.htm\n\n"
      "There may be a problem with other software installed on your computer. Try to disable your "
      "antivirus software (in case you have one) and restart your computer. To know more about this problem,"
      "you can read the technical note at the following Internet address: "
      "http://techno.cryonetworks.com/Support/SCOL/SCOL_Support.htm";
    MessageBox(hscol, msg, "Fatal Error", MB_OK);
    return -1;
  }
//
/*	SetThreadPriority(GetCurrentThread(),THREAD_PRIORITY_LOWEST);*/

	return PKhardpak(m,"timer.pkg",NTIMEPKG,timename,timefun
		   ,timenarg,timetype);
}

int SCOLendTimer(mmachine m)
{
  if (mt_curper)
	{
		MMechostr(1,">>>>>kill timer\n");
		timeKillEvent(mt_timer);
		mt_curper=0;
	}
	//$BLG - 4.6a4 : We use wPeriodMin to get the best possible timer resolution (precision)
	//timeEndPeriod(tc.wPeriodMax);
	timeEndPeriod(tc.wPeriodMin);
	return 0;
}