{
  Global TODO
  - GetTimer leads to crash inside main loop
}

const
  // TODO check which android versions supported this flag
  AWINDOW_FLAG_FULLSCREEN = $400;

{$IF not Defined(DENG_DEMO_MODE)}
//{{{ TWindowEvent
function TWindowEvent.IsKeyDown: Boolean;
begin
  Result := (EventType = AINPUT_EVENT_TYPE_KEY) and
            (AKeyEvent_getAction(Event) = AKEY_EVENT_ACTION_DOWN);
end;

function TWindowEvent.IsKeyDown(_Key: Integer): Boolean;
begin
  // TODO use getUnicodeChar (or getKeyData) for virtual keyboard
  Result := IsKeyDown and (AKeyEvent_getKeyCode(Event) = _Key);
end;

function TWindowEvent.IsKeyUp: Boolean;
begin
  Result := (EventType = AINPUT_EVENT_TYPE_KEY) and
            (AKeyEvent_getAction(Event) = AKEY_EVENT_ACTION_UP);
end;

function TWindowEvent.IsKeyUp(_Key: Integer): Boolean;
begin
  // TODO use getUnicodeChar (or getKeyData) for virtual keyboard
  Result := IsKeyUp and (AKeyEvent_getKeyCode(Event) = _Key);
end;

function TWindowEvent.Key: Integer;
begin
  // TODO use getUnicodeChar (or getKeyData) for virtual keyboard
  Result := AKeyEvent_getKeyCode(Event);
end;

function TWindowEvent.IsChar: Boolean;
begin
  // Unsupported now
  Result := False;
end;

function TWindowEvent.IsCursorMove: Boolean;
var
  Action: LongInt;
begin
  if EventType <> AINPUT_EVENT_TYPE_MOTION then
    Exit(False);
  Action := AMotionEvent_getAction(Event) and AMOTION_EVENT_ACTION_MASK;
  Result := Action = AMOTION_EVENT_ACTION_MOVE;
end;

function TWindowEvent.IsLeftClickDown: Boolean;
var
  Action: LongInt;
begin
  if EventType <> AINPUT_EVENT_TYPE_MOTION then
    Exit(False);
  Action := AMotionEvent_getAction(Event);
  if (Action and AMOTION_EVENT_ACTION_MASK) <> AKEY_EVENT_ACTION_DOWN then
    Exit(False);
  
  Result := (AMotionEvent_getAction(Event) and AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) = 0;
 
end;

function TWindowEvent.GetCursor: TVec2f;
var
  Index: LongInt;
begin
  Index := (AMotionEvent_getAction(Event) and AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
  Result := Vec2f(AMotionEvent_getX(Event, Index), AMotionEvent_getY(Event, Index));
end;

function TWindowEvent.IsLeftClickUp: Boolean;
begin
  Result := (EventType = AINPUT_EVENT_TYPE_MOTION) and
            (AMotionEvent_getAction(Event) = AKEY_EVENT_ACTION_UP);
end;

function TWindowEvent.IsRightClickDown: Boolean;
begin
  Result := False;
end;

function TWindowEvent.IsRightClickUp: Boolean;
begin
  Result := False;
end;

function TWindowEvent.IsMiddleClickDown: Boolean;
begin
  Result := False;
end;

function TWindowEvent.IsMiddleClickUp: Boolean;
begin
  Result := False;
end;

function TWindowEvent.IsDoubleLeftClick: Boolean;
begin
  Result := False;
end;

function TWindowEvent.IsMouseWheel: Boolean;
begin
  Result := False;
end;

function TWindowEvent.MouseWheel: Integer;
begin
  Result := 0;
end;
//}}}
{$ENDIF}

procedure OnAppCmd(App: Pandroid_app; Cmd: cint32); cdecl;
var
  WinApi: PWinApi;
begin
  WinApi := PWinApi(App^.userData);
  case Cmd of
    // for Activity.onCreate see deng_app_begin.inc android_main
    APP_CMD_SAVE_STATE: begin
      Log('W', 'APP_CMD_SAVE_STATE');
      // The system has asked us to save our current state. Do so.
      // TODO so
    end;
    APP_CMD_START: begin // Activity.onStart
      Log('W', 'APP_CMD_START');
      WinApi.FResumed := False;
    end;
    APP_CMD_RESUME: begin // Activity.onResume
      Log('W', 'APP_CMD_RESUME');
      // Preparing to rendering, but actually do not anything
      WinApi.FResumed := True;
      // https://developer.android.com/training/system-ui/status.html#40
      ANativeActivity_setWindowFlags(WinApi.FState^.activity, AWINDOW_FLAG_FULLSCREEN, AWINDOW_FLAG_FULLSCREEN);
    end;
    APP_CMD_PAUSE: begin // Activity.onPause
      Log('W', 'APP_CMD_PAUSE');
      // Our visible UI has been halted, so we have to be responsive
      WinApi.FResumed := False;
    end; // now the app is in killable state on Honeycomb
    APP_CMD_STOP: begin // Activity.onStop
      Log('W', 'APP_CMD_STOP');
      WinApi.FResumed := False;
      { We should still consider deleting the EGLContext (and thus
        freeing resources) in onStop, even if the context is not bound.
        If the context is bound, it may be best to release the GLES
        resources manually using GLES calls. However, if there is no
        surface and thus no way to bind the context at the point at
        which we want to delete the GLES resources, the best option is
        to destroy the EGLContext manually using eglDestroyContext. }
      eglDestroyContext(WinApi.FDisplay, WinApi.FContext);
      WinApi.FContext := EGL_NO_CONTEXT;
    end; // now the app is in killable state
    APP_CMD_DESTROY: begin // Activity.onDestroy
      Log('W', 'APP_CMD_DESTROY');
      WinApi.FResumed := False;
    end; // now the app is in killable state
    APP_CMD_INIT_WINDOW: begin // Activity.surfaceCreated onNativeWindowCreated
      Log('W', 'APP_CMD_INIT_WINDOW');
      WinApi.FSurfaceCreated := True;
      // The window is being shown, get it ready.
      WinApi.UpdateReadyToRender;
    end;
    APP_CMD_WINDOW_RESIZED: begin // Activity.surfaceChanged onNativeWindowResized
      Log('W', 'APP_CMD_WINDOW_RESIZED');
      // Here it is garantied that we have positive width and height
      WinApi.FSurfaceCreated := True;
      WinApi.UpdateReadyToRender;
    end;
    APP_CMD_TERM_WINDOW: begin // Activity.surfaceDestroyed onNativeWindowDestroyed
      Log('W', 'APP_CMD_TERM_WINDOW');
      // The window is being hidden or closed, clean it up.
      // TODO
      //engine_term_display(engine);
      eglMakeCurrent(WinApi.FDisplay, EGL_NO_SURFACE,
                     EGL_NO_SURFACE, EGL_NO_CONTEXT);
      WinApi.FSurfaceCreated := False;
      WinApi.FSurface := EGL_NO_SURFACE;
      WinApi.FContextBound := False;
      WinApi.FReadyToRender := False;
    end;
    APP_CMD_GAINED_FOCUS: begin // Activity.onWindowFocusChanged(true)
      Log('W', 'APP_CMD_GAINED_FOCUS');
      // Our app has gained focus (top-most, visible and interact-able)
      // The window can gain focus before or after the surface is created
      {if (engine^.accelerometerSensor <> Nil) then
      begin
         ASensorEventQueue_enableSensor(engine^.sensorEventQueue, engine^.accelerometerSensor);
         // We'd like to get 60 events per second (in us).
         ASensorEventQueue_setEventRate(engine^.sensorEventQueue, engine^.accelerometerSensor, (1000L/60)*1000);
      end;}
      WinApi.FFocusGained := True;
      WinApi.UpdateReadyToRender;
    end;
    APP_CMD_LOST_FOCUS: begin // Activity.onWindowFocusChanged(false)
      Log('W', 'APP_CMD_LOST_FOCUS');
      // Our app has lost focus
      // This is to avoid consuming battery while not being used.
      // We should auto-pause gameplay, stop sounds/music, low rendering
      //The window can lose focus before or after the surface is destroyed
      WinApi.FFocusGained := False;
      WinApi.UpdateReadyToRender;
    end;
    APP_CMD_CONFIG_CHANGED: begin // Activity.onConfigurationChanged
      Log('W', 'APP_CMD_CONFIG_CHANGED');
    end;
  else
    Log('W', 'OnAppCmd got unprocessed cmd: ' + IntToStr(Cmd));
  end;
end;

function OnInputEvent(App: Pandroid_app; Event: PAInputEvent): cint32; cdecl;
var
  WinApi: PWinApi;
  Ev: TWindowEvent;
begin
   WinApi := PWinApi(App^.userData);
   Log('DE', 'OnInputEvent');
   if WinApi <> nil then begin
     Ev.Init;
     Ev.Event := Event;
     Ev.EventType := AInputEvent_getType(Event);
     // Send to WinApi.Window.FHandlers ...
     WinApi.OnWindowEvent(@Ev);
     Log('DE', 'OnInputEvent 1');
     Exit(1);
  end;
  Log('DE', 'OnInputEvent 0');
  Exit(0);
end;

constructor TWindow.Init(WinApi: PWinApi; X, Y, W, H: Integer; Title: PAnsiChar);
begin
  FWinApi := WinApi;
end;

destructor TWindow.Done;
begin
end;

procedure TWindow.Subscribe(Handler: TOnWindowEvent);
begin
  //SetLength(FHandlers, Length(FHandlers) + 1);
  //FHandlers[High(FHandlers)] := Handler;
end;

procedure TWindow.SwapBuffers;
begin
  if FWinApi.FDisplay = nil then
    Exit;
  eglSwapBuffers(FWinApi.FDisplay, FWinApi.FSurface);
end;

procedure TWindow.SetDisplayMode(X, Y: Integer; Fullscreen: Boolean);
begin
  // Nothing to do...
end;

procedure TWindow.SetShowCursor(Value: Boolean);
begin
  // Nothing to do...
end;

function TWindow.GetPos: TVec2i;
begin
  Result := FWinApi.GetPos;
end;

function TWindow.GetSize: TVec2i;
begin
  Result := FWinApi.GetSize;
end;

function TWindow.GetMouse: TVec2i;
begin
  Result := FWinApi.GetMouse - GetPos;
end;

constructor TWinApi.Init;
begin
  FWindow := TWindow.Create(@Self, 0, 0, 0, 0, '');
  FOnWindowEvent := EmptyEventHandler;
  FDisplay := EGL_NO_DISPLAY;
  FConfig  := nil;
  FSurface := EGL_NO_SURFACE;
  FContext := EGL_NO_CONTEXT;
  FNewContext := False;
  FResumed := False;
  FFocusGained := False;
  FSurfaceCreated := False;
  //FVisible := False;
  //FInteractable := False;
  FContextBound := False;
end;

const
 attribs24: array[0..8] of EGLint = (
           EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
           EGL_BLUE_SIZE, 8,
           EGL_GREEN_SIZE, 8,
           EGL_RED_SIZE, 8,
           EGL_NONE);
 attribs32: array[0..10] of EGLint = (
           EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
           EGL_BLUE_SIZE, 8,
           EGL_GREEN_SIZE, 8,
           EGL_RED_SIZE, 8,
           EGL_ALPHA_SIZE, 8,
           EGL_NONE);

procedure TWinApi.OnNewContext;
var
  Dur: TMetricDuration;
begin
  Dur.Init('egl_reupload_all_resources');
  DrawTexMan.ForceUploadAll;
  Dur.Done;
end;

function TWinApi.EGLReadyToRender: Boolean;
var
  w, h, Format, NumConfigs: EGLint;
begin
  if FState^.Window = nil then
    Exit(False);

  if FDisplay = EGL_NO_DISPLAY then begin
    Log('W', 'eglGetDisplay');
    FDisplay := eglGetDisplay(EGL_DEFAULT_DISPLAY); // onCreate
    Log('W', 'eglInitialize');
    eglInitialize(FDisplay, nil, nil); // onCreate
  end;

  // If we have a bound context and surface, then EGL is ready
  if not FContextBound then begin
    // If we have not bound the context and surface,
    // do we even _have_ a surface?
    if FSurface = EGL_NO_SURFACE then begin
      // No EGL surface; is EGL set up at all?
      if FContext = EGL_NO_CONTEXT then begin
        // Init EGL, the Config and Context here…
        Log('W', 'eglChooseConfig');
        eglChooseConfig(FDisplay, attribs32, @FConfig, 1, @numConfigs); // onCreate
        Log('W', 'eglGetConfigAttrib');
        eglGetConfigAttrib(FDisplay, FConfig, EGL_NATIVE_VISUAL_ID, @Format); // onCreate?
        Log('W', 'ANativeWindow_setBuffersGeometry');
        ANativeWindow_setBuffersGeometry(FState^.window, 0, 0, Format);
        Log('W', 'eglCreateContext');
        FContext := eglCreateContext(FDisplay, FConfig, nil, nil);
        if FContext = EGL_NO_CONTEXT then
          Exit;

        // Mark that we have a new context – this will
        // be important later, as a new context bound
        // means we can/must load content into it
        FNewContext := True;
      end;

      // Check if Android surface is ready
      if not FSurfaceCreated then
        Exit(False);

      // Create the EGLSurface here...
      FSurface := eglCreateWindowSurface(FDisplay, FConfig, FState^.window, nil);
      if FSurface = EGL_NO_SURFACE then
        Exit(False);
    end;

    // We have a surface and context, so bind them

    // eglMakeCurrent with the surface and context here…
    Log('W', 'eglMakeCurrent');
    if eglMakeCurrent(FDisplay, FSurface, FSurface, FContext) = EGL_FALSE then begin
      Log('W', 'Unable to eglMakeCurrent');
      Exit(False);
    end;
    FContextBound := True;

    Log('W', 'eglQuerySurface');
    eglQuerySurface(FDisplay, FSurface, EGL_WIDTH,  @w);
    eglQuerySurface(FDisplay, FSurface, EGL_HEIGHT, @h);
    FSize := Vec2i(w, h);

    if FNewContext then begin
      OnNewContext;
      FNewContext := False;
    end;
  end;

  // One way or another (free or requiring some amount of setup)
  // We have a fully initialized set of EGL objects… Off we go
  Exit(True);
end;

procedure TWinApi.UpdateReadyToRender;
begin
  FReadyToRender := FFocusGained and FResumed and EGLReadyToRender;
end;

function TWinApi.ReadyRender: Boolean;
begin
  Result := FReadyToRender;
end;

procedure TWinApi.TempInit;
var
  w, h, Format, NumConfigs: EGLint;
begin
  // initialize OpenGL ES and EGL

  (*
   * Here specify the attributes of the desired configuration.
   * Below, we select an EGLConfig with at least 8 bits per color
   * component compatible with on-screen windows
   *)
  Log('FDisplay');
  FDisplay := eglGetDisplay(EGL_DEFAULT_DISPLAY); // onCreate
  eglInitialize(FDisplay, nil, nil); // onCreate

  (* Here, the application chooses the configuration it desires. In this
   * sample, we have a very simplified selection process, where we pick
   * the first EGLConfig that matches our criteria  *)
  Log('eglChooseConfig');
  eglChooseConfig(FDisplay, attribs32, @FConfig, 1, @numConfigs); // onCreate
  Log(PAnsiChar('eglChooseConfig ' + IntToStr(numConfigs)));

  (* EGL_NATIVE_VISUAL_ID is an attribute of the EGLConfig that is
   * guaranteed to be accepted by ANativeWindow_setBuffersGeometry().
   * As soon as we picked a EGLConfig, we can safely reconfigure the
   * ANativeWindow buffers to match, using EGL_NATIVE_VISUAL_ID.  *)
  Log('eglGetConfigAttrib');
  eglGetConfigAttrib(FDisplay, FConfig, EGL_NATIVE_VISUAL_ID, @Format); // onCreate?

  Log(PAnsiChar('ANativeWindow_setBuffersGeometry ' + IntToStr(PtrInt(FState)) + ' ' + IntToStr(Format)));
  ANativeWindow_setBuffersGeometry(FState^.window, 0, 0, Format);

  Log('eglCreateWindowSurface');
  FSurface := eglCreateWindowSurface(FDisplay, FConfig, FState^.window, nil); // surfaceCreated
  Log('eglCreateContext');
  FContext := eglCreateContext(FDisplay, FConfig, nil, nil);

  Log('eglMakeCurrent');
  if eglMakeCurrent(FDisplay, FSurface, FSurface, FContext) = EGL_FALSE then begin
    Log('Unable to eglMakeCurrent');
    Exit;
  end;

  Log('eglQuerySurface');
  eglQuerySurface(FDisplay, FSurface, EGL_WIDTH,  @w);
  eglQuerySurface(FDisplay, FSurface, EGL_HEIGHT, @h);
  FSize := Vec2i(w, h);
end;

procedure TWinApi.OnInit(Width, Height, Bits, Flags: Integer; Title: PAnsiChar);
begin
  Log('Read FState');
  FState := _AndroidState;

  Log('Setting FState');
  FState^.userData := @Self;
  FState^.onAppCmd := @OnAppCmd;
  FState^.onInputEvent := @OnInputEvent;
end;

destructor TWinApi.Done;
begin
  if (FDisplay <> EGL_NO_DISPLAY) then begin
    eglMakeCurrent(FDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
    if (FContext <> EGL_NO_CONTEXT) then
      eglDestroyContext(FDisplay, FContext);
    if (FSurface <> EGL_NO_SURFACE) then
      eglDestroySurface(FDisplay, FSurface);
    eglTerminate(FDisplay);
  end;
end;

procedure TWinApi.ReadMessages;
var
  Ident, Events: cint;
  Source: Pandroid_poll_source;
begin
  ident := ALooper_pollAll(0, nil, @Events, @Source);
  while ident >= 0 do begin
    // Process this event.
    if (Source <> nil) then
       Source^.process(FState, Source);

    // If a sensor has data, process it now.
    if (ident = LOOPER_ID_USER) then begin
       {if (engine.accelerometerSensor != nil) then
       begin
          ASensorEvent event;
          while (ASensorEventQueue_getEvents(engine.sensorEventQueue, &event, 1) > 0) do
          begin
             LOGI("accelerometer: x=%f y=%f z=%f",
                     [event.acceleration.x, event.acceleration.y,
                     event.acceleration.z]);
          end;
       end;}
    end;

    ident := ALooper_pollAll(0, nil, @Events, @Source);
  end;
end;

function TWinApi.GetScreenSize: TVec2i;
begin
  Result := Vec2i(0, 0);
end;

function TWinApi.GetMouse: TVec2i;
begin
  Result := Vec2i(0, 0);
end;

procedure TWinApi.SetCursorVisibility(Visibility: Boolean);
begin
end;

procedure TWinApi.HideCursor;
begin
end;

procedure TWinApi.ShowCursor;
begin
end;

function TWinApi.GetTitle: TString;
begin
  Result := ''; // TODO
end;

procedure TWinApi.SetTitle(const Value: TString);
begin
end;

function TWinApi.GetPos: TVec2i;
begin
  Result := Vec2i(0, 0);
end;

procedure TWinApi.SetPos(const Value: TVec2i);
begin
  // Cannot do this...
end;

function TWinApi.GetSize: TVec2i;
begin
  Result := FSize;
end;

procedure TWinApi.SetSize(const Value: TVec2i);
begin
  // Cannot do this...
end;

function GetTimer: Integer;
var
  TS: TTimeSpec;
begin
  clock_gettime(CLOCK_MONOTONIC_RAW, @TS);
  Result := TS.tv_sec * 1000 + TS.tv_nsec div 1000000;
  //Result := 0;
end;
