{
    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 window class.

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

 procedure TWindowManager.OnMouseMove;
 var ne: integer;
 begin
   if (rMxPrev <> f_MouseX) or (rMyPrev <> f_MouseY) then begin
     //did it *really* move? Only then shutdown the fake pointer position made by the game controller right thumb
     CombinedMx:= f_MouseX;
     CombinedMy:= f_MouseY;
     gcMxPrevi:= f_MouseY;
     gcMyPrevi:= round(CombinedMy);
     MouseIsGamepad:= false;
     rMxPrev:= f_MouseX;
     rMyPrev:= f_MouseY;
   end;
   ne:= NewInputEvent();
   with MotherState.InputEvents[ne] do begin
     EventType:= CGM_MOUSEMOVE;
     mx:= MouseX;
     my:= MouseY;
   end;
 end;

 function TWindowManager.IsHotkey(scancode: integer; HK: array of integer): boolean;
 var
   i: integer;
 begin
   Result:=No;
   if Length(HK) = 0 then Exit;
   For i:=0 to Length(HK) - 2 do
     if not Pressed[HK[i]] then Exit;
   if HK[Length(HK) - 1] = scancode then Result:=Yes;
 end;

 procedure TWindowManager.OnPress(scancode: integer);
 var i: integer;
   ne: integer;
 begin
   if (scancode < 0) or (scancode > 255) then Die('Invalid scancode %0',[scancode]);
   if (scancode = ord(Key_Space)) then Module.ReactToSpace;
   if (scancode = ord(Key_Escape)) then Module.ReactToEsc;

   if MotherState.ModuleState <> ms_Loaded
     then MotherState.TextInput:= false;

   If IsHotKey(scancode, AbortHotKey) then begin
     _RequestExit;
     VerboseLog('Hot keys pressed - exit requested.');
     Exit;
   end;
   If IsHotKey(scancode, RestartHotKey) then begin
     MotherState.RestartRequested:=Yes;
     VerboseLog('Hot keys pressed - restart requested.');
     Exit;
   end;
   If IsHotKey(scancode, LanguageHotKey) then begin
     VerboseLog('Hot keys pressed - language reset.');
     MessageContainer.SetLanguage(-1);
     Exit;
   end;


{   if MotherState.DeveloperMode then
     For i:=1 to 10 do
       If IsHotKey(scancode, ModuleHotkey[i]) then begin
         Module.Reload(i);
         Exit;
       end;
}

   If MotherState.DeveloperMode and IsHotKey(scancode, SessionRollBackHotKey) then begin
       Module.RollBackSession;
       Exit;
     end;
     
   If IsHotKey(scancode, ModuleHotkey[0]) then begin
       Module.GoToModuleSelectionMenu;
       Exit;
     end;
{   If IsHotKey(scancode, MainModuleHotKey) then begin
     ModuleManager._ActiveModuleNum:=0;
     VerboseLog('Hot keys pressed - switching to default module.');
     Exit;
   end;}
   if IsHotKey(scancode, ConsoleToggleHotkey) then begin
     Console.AlwaysVisible:=not(Console.AlwaysVisible);
     Exit;
   end;

   if IsHotKey(scancode, QualityFactorManualOverrideToggleHotkey) then begin
     MotherState.QF_ManualOverride:=not(MotherState.QF_ManualOverride);
     if MotherState.QF_ManualOverride
       then AddLog(RuEn('Коэффициент качества переведён на ручное управление.','Quality Factor manual override on.'))
       else AddLog(RuEn('Коэффициент качества переключён обратно на автомат.','Quality Factor control switched back to automatic.'));
     Exit;
   end;

   if IsHotKey(scancode, ConsoleScrollUpHotkey) then begin
     if Console.AlwaysVisible then begin
       Console.ScrolledBack:=Console.ScrolledBack + 1;
       Exit;
     end;
     if MotherState.QF_ManualOverride then begin
       MotherState.QF += max(1, round(MotherState.QF / 15));
       if MotherState.QF > QF_MAX then MotherState.QF:= QF_MAX;
       EXit;
     end;
   end;

   if IsHotKey(scancode, ConsoleScrollDownHotkey) then begin
     if Console.AlwaysVisible then begin
       Console.ScrolledBack:=Console.ScrolledBack - 1;
       Exit;
     end;
     if MotherState.QF_ManualOverride then begin
       MotherState.QF-= max(1, round(MotherState.QF / 15));
       if MotherState.QF < QF_MIN then MotherState.QF:= QF_MIN;
       EXit;
     end;
   end;

   AnyKey:= True;
   ne:= NewInputEvent();
   with MotherState.InputEvents[ne] do begin
     EventType:= CGM_PRESS;
     key:= TKey(scancode);
     mx:= MouseX;
     my:= MouseY;
   end;
 end;

 procedure TWindowManager.OnRelease(scancode: integer);
 var ne: integer;
 begin
   ne:= NewInputEvent();
   with MotherState.InputEvents[ne] do begin
     EventType:= CGM_RELEASE;
     key:= TKey(scancode);
     mx:= MouseX;
     my:= MouseY;
   end;
 end;

 procedure TWindowManager.OnType(input: WideChar);
 var ne: integer;
 begin
   AnyKey:= True;
   ne:= NewInputEvent();
   with MotherState.InputEvents[ne] do begin
     EventType:= CGM_TYPE;
     character:= input;
     mx:= MouseX;
     my:= MouseY;
   end;
 end;

 procedure TWindowManager.OnPen(x, y, pressure, reserved1, reserved2, reserved3: single);
 var ne: integer;
 begin
//AddLog('Pen tablet: x=%0   y=%1   pressure=%2', [x,y,pressure]);
   MotherState.PenActivityDetected:= true;
   ne:= NewInputEvent();
   with MotherState.InputEvents[ne] do begin
     EventType:= CGM_PEN;
     PenX:= x;
     PenY:= y;
     PenPressure:= pressure;
     PenReserved1:= reserved1;
     PenReserved2:= reserved2;
     PenReserved3:= reserved3;
   end;
 end;

procedure TWindowManager.AddRightThumbToMouseMovement(x,y: float);
var
  a, accel, nx, ny, decel: float;
begin
  a:= sqrt(sqr(x) + sqr(y));
  if (a > 1) then begin
    x:= x / a;
    y:= y / a;
    a:= 1.0;
  end;
  nx:= x * MotherState.GCToMouseSpeedMultiplier;
  ny:= y * MotherState.GCToMouseSpeedMultiplier;
  gcMxV+= (nx - gcMx);
  gcMyV+= (ny - gcMy);
  gcMx:= nx;
  gcMy:= ny;
  if (a > (MotherState.GCToMouseAccelThreshold / 100)) then begin
    accel:= sqr((a - (MotherState.GCToMouseAccelThreshold / 100)) / (1.0 - (MotherState.GCToMouseAccelThreshold / 100)));
    gcMxAcc:= accel * x/a * MotherState.GCToMouseAccelMultiplier;
    gcMyAcc:= accel * y/a * MotherState.GCToMouseAccelMultiplier;
  end else begin
    gcMxAcc:= 0;
    gcMyAcc:= 0;
    gcMxV:= gcMx;
    gcMyV:= gcMy;
  end;
end;

procedure TWindowManager.EmulateMouseViaGamepad;
var
  delta: double;
  ne: integer;
begin
  delta:= Math.max(0.0, Math.min(0.1, UsecByTsc(@PreviousCcToMTsc) / 1000000.0));
  gcMxV+= delta * gcMxAcc;
  gcMyV+= delta * gcMyAcc;
  CombinedMx+= delta * gcMxV;
  CombinedMy-= delta * gcMyV;
  CombinedMx:= Math.max(0, Math.min(CombinedMx, MotherState.DisplayWidth));
  CombinedMy:= Math.max(0, Math.min(CombinedMy, MotherState.DisplayHeight));
  if (round(CombinedMx) <> gcMxPrevi) or (round(CombinedMy) <> gcMyPrevi) then begin
    gcMxPrevi:= round(CombinedMx);
    gcMyPrevi:= round(CombinedMy);
    //MotherState.MouseIsBeyondOurWindow:= false;
    MouseIsGamepad:= true;
    ne:= NewInputEvent();
    with MotherState.InputEvents[ne] do begin
      EventType:= CGM_MOUSEMOVE;
      mx:= CombinedMx;
      my:= CombinedMy;
    end;

  end;
end;

 // The main game heartbeat event
 procedure TWindowManager.OnIdle;
 var delta: double;
//qq: qword;
 begin
   LockupGuard.Checkup;

   if MotherState.GamepadPresent then EmulateMouseViaGamepad
   else begin
     CombinedMx:= f_MouseX;
     CombinedMy:= f_MouseY;
   end;

   if Assigned(GamepadManager) then GamepadManager.Pulse;

   MotherState.WindowHasFocus:= Self.f_focus;

   if Assigned(Module) and (Focus
   or (f_LoseFocusFramesCount <= FRAMES_TO_CONTINUE_RENDER_AFTER_LOSING_FOCUS)) then begin

     MotherState.CullFrameRate:= FPS_CULL_DEFAULT;
     MotherState.SuspendSwbMeasurement:= false;

     Module.CheckDate;
     Module.Pulse;

     MotherState.fadein:= FancyFunc( (Now() - MotherState.BlackoutStart) * 84600 / FADE_IN_TIME);
     Console.SetFadeIn(MotherState.fadein);



     MeasureFps;
     Console.Draw(
       (MotherState.ModuleState = ms_Crashed) and not Module.BimLoaded,
       ((MotherState.ModuleState <> ms_Loaded) or MotherState.LogoAnimationPlaying) and not Module.BimLoaded);
     RenderFpsCounter;

     if Assigned(Console) and MotherState.LogoAnimationPlaying
       then Console.DrawLogo;

     RecheckTSCFreq;
//usecbytsc(@qq);
     SwapBuffers;

     CheckBatteryStatus;

//addlog('> %0',[round(usecbytsc(@qq))]);
     if (MotherState.CullFrameRate < 300) then begin
       delta:= abs(MouseX - MxFpsCull) + abs(MouseY - MyFpsCull);
       if (FPS_CULL_DEFAULT = MotherState.CullFrameRate) // иными словами, модуль не менял его
         and (delta >= FPS_CULL_DEFAULT_MOUSE_DELTA_THRESHOLD)
         and not MotherState.MouseIsBeyondOurWindow
         then MotherState.CullFrameRate:= FPS_CULL_DEFAULT_MOUSE_MOVED;

       delta:= UsecByTsc(@PreviousFpsCullTsc) - 1000000.0 / max(FPS_CULL_DEFAULT, MotherState.CullFrameRate);
       //WTF, Error: Incompatible type for arg no. 2: Got "Extended", expected "LongInt"
       //Ah, *this* fuck. I defined my own min() somewhere.
       delta:= 1.0 * Sign(delta) * Math.min(abs(delta), 1000000.0 / FPS_CULL_DEFAULT); {
         куллинг дельты - абсолютно необходимая вещь. Бывает, кадр занимает секунд по пять,
         при старте или если антивирус дрочит при запуске модуля.
       }
       if delta < 0 then begin
          CgeSleep(-Round(delta / 1000.0));
          delta:= 0;
       end;
       UsecByTsc(@PreviousFpsCullTsc);
       PreviousFpsCullTsc+= round(delta);
       MxFpsCull:= round(MouseX);
       MyFpsCull:= round(MouseY);
     end;
   end
   else Sleep(NO_FOCUS_SLEEP_INTERVAL);
//addlog('* %0',[round(usecbytsc(@qq))]);
 end;
 



