{
    This file is part of Chentrah,
    Copyright (C) 2004-2008 Anton Rzheshevski (chebmaster@mail.ru).

    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, either version 2 of the License, or
    (at your option) any later version.

    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, see http://www.gnu.org/licenses/

 ********************************************************************** 

    This file contains the high-resolution timer routines.

 **********************************************************************}

const
  ctRDTSCFreqEstimationPeriod
    = {$ifdef win32} 5.0 {$else} 20.0 {$endif};
  ctFreqCheckPeriod
    = {$ifdef win32} 2.0 {$else} 5.0 {$endif};
  ctFreqDeviationTolerancy
    = {$ifdef win32} 0.05 {$else} 0.2 {$endif};
    //because I don't know how to raise thread priority in unix,
    //  so error marign should be much wider.

{$ifdef win32}
type
  MMRESULT = UINT;
const
  TIMERR_NOERROR = 0;
  TIMERR_NOCANDO = 96 + 1;
function timeBeginPeriod(x1: UINT): MMRESULT; stdcall; external 'winmm.dll' name 'timeBeginPeriod';
function timeEndPeriod(x1: UINT): MMRESULT; stdcall;  external 'winmm.dll' name 'timeEndPeriod';

{$endif}

var
  _startTSC: qword;
{$ifdef windows}
  _starttick: longint;
function Second: double; cdecl;
begin
  Result:= (GetTickCount() - _starttick) / 1000.0;
end;
{$else}
  _StartNow: TDateTime;
function Second(): double; cdecl;
begin
  Result:= (Now() - _StartNow) * SecondsPerDay;
end;
{$endif}

  function GetTimeStamp: qword; forward;

var
  _PrevTscCheckSec: double = 0;
  _PrevTsc: qword;
  _tsc: qword;
  function RecheckTSCFreq: longbool;
  var
    sec: double;
  begin
    try
      sec:= Second();
      if (sec - _PrevTscCheckSec) < TSCMeasuringInterval then Exit(false);
      asm
        pushf
        {$ifdef cpu64}
          push rdx
          push rax
        {$else}
          push edx
          push eax
        {$endif}
        rdtsc
        mov [_tsc], eax
        mov [_tsc + 4], edx
        {$ifdef cpu64}
          pop rax
          pop rdx
        {$else}
          pop eax
          pop edx
        {$endif}
        popf
      end;
      Result:= true;
      if {$ifdef cpu64}_tsc <= _prevTsc + 200{$else}_tsc <= _prevTsc{$endif} then Result:= false;
      if Result then MotherState.RdtscFrequency:=
        (_tsc - _prevTSC) / (sec - _prevTscCheckSec);
      _prevTSC:= _tsc;
      _PrevTscCheckSec:= sec;
    except end;
  end;
  
  function UsecByTsc (ptsc: pqword): double; cdecl;
  var q: qword;
  begin
    q:= ptsc^;
    asm
     {$ifdef cpu64}
       pushf
       push rdx
       push rax
       push rcx
       mov rcx, ptsc
       rdtsc
       mov [rcx], eax
       mov [rcx + 4], edx
       pop rcx
       pop rax
       pop rdx
       popf
     {$else}
      pushf
      push edx
      push eax
      push ecx
      mov ecx, ptsc
      rdtsc
      mov [ecx], eax
      mov [ecx + 4], edx
      pop ecx
      pop eax
      pop edx
      popf
     {$endif}
    end;
    if q >= ptsc^ then q:=ptsc^ - 1;
    Result:= (ptsc^ - q) * 1000000.0 / MotherState.RdtscFrequency;
  end;

  procedure InitCTimer;
  begin
    {$ifdef windows}
      VerboseLog('Calling timeBeginPeriod(1)...');
      {$ifdef cpu32}
        if timeBeginPeriod(1) = TIMERR_NOERROR
          then VerboseLogOk
          else Die(RuEn(
            'Не удалось переключить системный таймер на разрешение в 1 мс.',
            'Unable to switch the system timer to 1 milisecond precision.'));
        SetThreadPriority(GetCurrentThread, THREAD_PRIORITY_TIME_CRITICAL);
      {$endif}
      _starttick:= GetTickCount();
    {$else}
    _startnow:= Now();
    {$endif}
    asm
      pushf
      {$ifdef cpu64}
        push rdx
        push rax
      {$else}
        push edx
        push eax
      {$endif}
      rdtsc
      mov [_startTSC], eax
      mov [_startTSC + 4], edx
      {$ifdef cpu64}
        pop rax
        pop rdx
      {$else}
        pop eax
        pop edx
      {$endif}
      popf
    end;
    _PrevTSC:= _startTSC;
    MotherState.StartupDateTime:= Now();
    repeat until RecheckTscFreq;
    {$ifdef windows}
      {$ifdef cpu32}
        SetThreadPriority(GetCurrentThread, THREAD_PRIORITY_NORMAL);
      {$endif}
    {$endif}
    LastHeartbeatMoment:= MotherState.StartupDateTime;
  end;



