{$MODE delphi}
{$MODESWITCH POINTERTOPROCVAR+}
{$i deng_defines.inc}
unit DWinApi;

interface

uses
  {$IF Defined(DENG_DEMO_MODE)}
  DEvLog,
  {$ENDIF}
  {$IF Defined(WINDOWS)}
    mmsystem, windows, dwmapi, gl, glext, // TODO do not use mmsystem here and in DControl
  {$ELSEIF Defined(ANDROID)}
    ctypes, looper, android_native_app_glue, egl, gles, native_window, input,
    UnixType, Linux,
  {$ENDIF}
  DEngBase,
  dtypes,
  dstrnum,
  dmath,
  dvector2i,
  dvector2f;

var
// Global settings. Must be initialized in main application before main loop.
// The mechanism of global settings should be refactored later.
WINAPI_SETTINGS : record
  ReceiveDoubleClicks: Boolean;
  CloseButton: Boolean;
  MultiSampling: Boolean;
  MultiSamplingLevel: LongInt;
  AllowResize: Boolean;
  ShapedWindow: Boolean;
end;

type
PWinApi = ^TWinApi;
{$IF Defined(WINDOWS)}
  TWndHandler = function(Msg: Cardinal; W, L: LongInt): Boolean of object;
{$ENDIF Defined(WINDOWS)}

{$IFDEF DENG_DEMO_MODE}
  {$DEFINE DENG_SUPPORT_EVLOG}
{$ENDIF}

{$IFDEF DENG_SUPPORT_EVLOG}
PWindowEventEvLog = ^TWindowEventEvLog;
TWindowEventEvLog = object
private
  EvLog: PEvLogRec;
public
end;
{$ENDIF}

PWindowEvent = ^TWindowEvent;
TWindowEvent = object
private
{$IF Defined(DENG_DEMO_MODE)}
  FIsEvLog: Boolean;
  EvLog: PEvLogRec;
  public property IsEvLog: Boolean read FIsEvLog;
{$ELSE}
  public const IsEvLog: Boolean = False;
{$ENDIF}
private
  procedure Init; inline;
private
{$IF Defined(WINDOWS)}
  Handle: HWND;
  // TODO: windows types instead of Cardinal and LongInt for cross-platform
  Msg: Cardinal;
  wParam: LongInt;
  lParam: LongInt;
{$ELSEIF Defined(ANDROID)}
  Event: PAInputEvent;
  EventType: TInt32;
{$ENDIF}
public
  procedure Log(_Eng: Pointer);

  function IsClose: Boolean; inline;

  function IsKeyDown: Boolean; overload; inline;
  function IsKeyDown(_Key: LongInt): Boolean; overload; inline;
  function IsKeyUp: Boolean; overload; inline;
  function IsKeyUp(_Key: LongInt): Boolean; overload; inline;
  function Key: LongInt; inline;

  function IsAltPressed: Boolean; inline;
  function IsShiftPressed: Boolean; inline;

  function IsChar: Boolean; inline;

  function JoyId: LongInt; overload; inline;
  function IsJoyMove: Boolean; overload; inline;
  function IsJoyDown: Boolean; overload; inline;
  function IsJoyDown(Joy: LongInt): Boolean; overload; inline;
  function IsJoyUp: Boolean; overload; inline;
  function IsJoyUp(Joy: LongInt): Boolean; overload; inline;
  function Joy: LongInt; overload; inline;
  function GetJoyPos: TVec2i; overload; inline;

  function IsCursorMove: Boolean; inline;
  function IsLeftClickDown: Boolean; inline;
  function IsLeftClickUp: Boolean; inline;
  function IsRightClickDown: Boolean; inline;
  function IsRightClickUp: Boolean; inline;
  function IsMiddleClickDown: Boolean; inline;
  function IsMiddleClickUp: Boolean; inline;
  function IsDoubleLeftClick: Boolean; inline;
  function GetCursor: TVec2f; inline;

  function IsResized: Boolean; inline;
  function Width: LongInt; inline;
  function Height: LongInt; inline;

  function IsMouseWheel: Boolean; inline;
  function MouseWheel: LongInt; inline;
end;

// Returns True if event was consumed
TOnWindowEvent = function (Event: PWindowEvent): Boolean of object;
TOnWindowEventFunc = function (Event: PWindowEvent): Boolean;

PWindow = ^TWindow;
TWindow = object
private
  FWinApi: PWinApi;
  FHandlers: array of TOnWindowEvent;
  FWindowedSize: TVec2i;
  FFullscreen: Boolean;
{$IF Defined(WINDOWS)}
  // TODO: windows types instead of Cardinal and Integer for cross-platform
  Msg: Cardinal;
  wParam: LongInt;
  lParam: LongInt;
  FHandle: HWND;
  FDC: HDC;
  FRC: HGLRC;
  FHasFocus: Boolean;
  FReady: Boolean;
  // For dragging and resizing
  FDragging: Boolean;
  FStartedDragX, FStartedDragY: LongInt;
  FResizing: Boolean;
  FX1Initial, FX1Factor: LongInt;
  FY1Initial, FY1Factor: LongInt;
  FX2Initial, FX2Factor: LongInt;
  FY2Initial, FY2Factor: LongInt;
  function WndProc(Msg: Cardinal; wParam, lParam: LongInt): Boolean;
  function CreatePixelFormat(Color, Depth, Stencil: LongInt): Boolean;
  function CreatePixelFormatAdvanced(Title: PAnsiChar; X, Y, Width, Height, Color, Depth, Stencil: LongInt): Boolean;
  function InitHandle(ClassName, Title: PAnsiChar;
                      Flags, FlagsEx: Cardinal;
                      X, Y, W, H: LongInt): Boolean;
  function InitFakeBlurredRegion: Boolean;
  function InitDC: Boolean;
  function InitRC: Boolean;
  function StartResizing(X, Y, X1, Y1, X2, Y2: LongInt): Boolean;
{$ELSEIF Defined(ANDROID)}
{$ENDIF}
public
  List: Cardinal;
  constructor Init(WinApi: PWinApi; X, Y, W, H: LongInt; Title: PAnsiChar);
  destructor Done; virtual;
  function Create(WinApi: PWinApi; X, Y, W, H: LongInt; Title: PAnsiChar): PWindow; static;
  procedure Free;
  procedure Subscribe(Handler: TOnWindowEvent);
  procedure SwapBuffers;
  procedure SetDisplayMode(X, Y: LongInt; Fullscreen: Boolean);
  procedure SetShowCursor(Value: Boolean);
  function GetShowCursor: Boolean;
  procedure SetCursor(const Pos: TVec2i);
  function GetPos: TVec2i; inline;
  function GetSize: TVec2i; inline;
  function GetMouse: TVec2i; inline;
  property Size: TVec2i read GetSize;
end;

TOnWindowEventProxy = object
  OnEvent: TOnWindowEventFunc;
  function Call(Event: PWindowEvent): Boolean;
end;

TWinApi = object
public type
  PWindowEvent = DWinApi.PWindowEvent;
  TWindowEvent = DWinApi.TWindowEvent;
private
  FWindow: PWindow;
  FOnWindowEvent: TOnWindowEvent;
  FOnWindowEventProxy: TOnWindowEventProxy;
{$IF Defined(ANDROID)}
  FState: Pandroid_app;
  FDisplay: EGLDisplay;
  FConfig: EGLConfig;
  FFormat: EGLInt;
  FSurface: EGLSurface;
  FContext: EGLContext;
  FNewContext: Boolean;
  FSize: TVec2i;
  FResumed: Boolean; // active phase of app lifecycle
  FSurfaceCreated: Boolean; // the app has a surface
  FFocusGained: Boolean; // the app is in focus
  //FVisible: Boolean; // the app is visible and should be rendered
  //FInteractable: Boolean; // the app is interactable
  FContextBound: Boolean; // eglMakeCurrent called
  FReadyToRender: Boolean;
  procedure TempInit;
  // when EGL is fully initialized and there is new context
  procedure OnNewContext;
  function EGLReadyToRender: Boolean;
  procedure UpdateReadyToRender;
  public function ReadyRender: Boolean; inline;
{$ENDIF}
public
  constructor Init;
  destructor Done; virtual;
  function Create: PWinApi;
  procedure Free;
  procedure OnInit(Width, Height, Bits, Flags: LongInt; Title: PAnsiChar);
  procedure SetOnEventHandler(OnEvent: TOnWindowEvent); overload; inline;
  procedure SetOnEventHandler(OnEvent: TOnWindowEventFunc); overload; inline;
  function OnWindowEvent(Event: PWindowEvent): Boolean;
  function EmptyEventHandler(Event: PWindowEvent): Boolean;
  procedure ReadMessages;
  function GetScreenSize: TVec2i;
  function GetMouse: TVec2i;
  procedure SetCursorVisibility(Visibility: Boolean);
  procedure HideCursor;
  procedure ShowCursor;
  function HasFocus: Boolean;
  function IsActive: Boolean;
  procedure ToggleFullScreen;
  procedure MakeFullscreen;
  procedure MakeWindowed;
  function IsFullScreen: Boolean;
  property Window: PWindow read FWindow;
  property ScreenSize: TVec2i read GetScreenSize;
  function GetTitle: TString;
  procedure SetTitle(const Value: TString);
  property Title: TString read GetTitle write SetTitle;
  function GetPos: TVec2i;
  procedure SetPos(const Value: TVec2i);
  property Pos: TVec2i read GetPos write SetPos;
  function GetSize: TVec2i;
  procedure SetSize(const Value: TVec2i);
  property Size: TVec2i read GetSize write SetSize;
  procedure SetTransperency(Alpha: Single); // only for ShapedWindow
end;

function GetTimer: LongInt;
function GetTimerMicroSeconds: Double;

{$IFDEF ANDROID}
var
  _AndroidState: Pandroid_app;
{$ENDIF}

implementation

uses
  {$IFDEF ANDROID}native_activity,{$ENDIF}
  // TODO это временное добавление, чтобы протестировать как работает перезагрузка текстур
  //      из RAM в GPU после сворачивания/разворачивания окна. В будущем это подключение
  //      следует убрать и реализовать через более общий механизм подсистем в DEng
  DEng,
  DMetrics;

procedure TWindowEvent.Init;
begin
  {$IFDEF DENG_DEMO_MODE}
  FIsEvLog := False;
  {$ENDIF}
end;

function TWindow.Create(WinApi: PWinApi; X, Y, W, H: LongInt; Title: PAnsiChar): PWindow;
begin
  New(PWindow(Result), Init(WinApi, X, Y, W, H, Title));
end;

procedure TWindow.Free;
begin
  if @Self <> nil then
    Dispose(PWindow(@Self), Done);
end;

function TOnWindowEventProxy.Call(Event: PWindowEvent): Boolean;
begin
  Result := OnEvent(Event);
end;

function TWinApi.Create: PWinApi;
begin
  New(PWinApi(Result), Init);
end;

procedure TWinApi.Free;
begin
  if @Self <> nil then
    Dispose(PWinApi(@Self), Done);
end;

procedure TWinApi.SetOnEventHandler(OnEvent: TOnWindowEvent);
begin
  FOnWindowEvent := OnEvent;
end;

procedure TWinApi.SetOnEventHandler(OnEvent: TOnWindowEventFunc);
begin
  FOnWindowEventProxy.OnEvent := OnEvent;
  FOnWindowEvent := FOnWindowEventProxy.Call;
end;

function TWinApi.OnWindowEvent(Event: PWindowEvent): Boolean;
begin
  Eng.Control.OnWindowEvent(Event);
  Result := FOnWindowEvent(Event);
end;

function TWinApi.EmptyEventHandler(Event: PWindowEvent): Boolean;
begin
  Result := False;
end;

{$I DWinApi_evlog.inc}

{$IF Defined(WINDOWS)}
{$I DWinApi_windows.inc}
{$ENDIF}

{$IF Defined(ANDROID)}
{$I DWinApi_android.inc}
{$ENDIF}

initialization
  WINAPI_SETTINGS.ReceiveDoubleClicks := False;
  WINAPI_SETTINGS.CloseButton := False;
  WINAPI_SETTINGS.MultiSampling := False;
  WINAPI_SETTINGS.MultiSamplingLevel := -1;
  WINAPI_SETTINGS.AllowResize := False;
  WINAPI_SETTINGS.ShapedWindow := False;
end.
