{
    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 manager class implementation.

 **********************************************************************}
(*
 My thanks to Jan Horn
   for OpenGL Delphi examples - http://www.sulaco.co.za/
   -- these allowed me to make my first step into OpenGL.
*)



//{$include cl_x_icon.inc}


  constructor TWindowManager.Create;
  var
    x, i: integer;
  begin

    //init some global variables

    inherited Create;

    MotherState.InFullScreenMode:= Config.Bool['video', 'fullscreen']; // and not MotherState.DeveloperMode;
    f_PointerVisible:= Yes;

    {$ifdef unix}
      {$ifdef darwin}

      {$else}
       //Соединяемся с X сервером
        Display:= XOpenDisplay(nil);
        if Display = nil then Die(MI_CANT_CONNECT_TO_X_SERVER);
        screen:= XDefaultScreen(Display);
        InitialWidth:=xlib.DisplayWidth(display, screen);
        InitialHeight:=xlib.DisplayHeight(display, screen);
      {$endif}
    {$else}
      InitialWidth:= GetSystemMetrics(SM_CXSCREEN);
      InitialHeight:= GetSystemMetrics(SM_CYSCREEN);
      ShowCursorCount:= 1;
    {$endif}

    if MotherState.InFullScreenMode then begin
      MotherState.DisplayWidth:= InitialWidth;
      MotherState.DisplayHeight:= InitialHeight;
    end
    else begin
      MotherState.DisplayWidth:= min(InitialWidth, max(820, Config['video', 'window_width']));
      MotherState.DisplayHeight:= min(InitialHeight, max(512, Config['video', 'window_height']));
    end;

    CreateWindow;

    {$ifdef unix}
      if MotherState.DebugMode then AddLog('Starting the pen tablet manager...');
      TabletManager:= TXinputTabletManager.Create(display, pointer(WindowHandle));
    {$endif}


    HasFocus:=True;
    f_focus:=True;
    MotherState.WindowVisible:=True;

    InitGL;

    if MotherState.DebugMode then begin
      AddLog('OpenGL extensions: ' + PChar(glGetString(GL_EXTENSIONS)));
     {$ifdef unix}
{ //not necessary
      if Assigned(glXQueryExtensionsString)
        then AddLog('GLX extensions: ' + PChar(glXQueryExtensionsString(Display, Screen)));
}
     {$endif}
    end;
  end;


  destructor  TWindowManager.Destroy;
  begin
    SaveQF;
    CloseGL;
    {$ifdef unix}
      TabletManager.Free;
    {$endif}
    CloseWindow;
    pointer(WindowManager):= nil; //clears the global variable
    inherited;
  end;
  
  procedure TWindowManager._CheckCursorMode();
  begin
   {$ifdef windows}
     //it's our task to restore the proper cursor each time Windows
     //  swithes it to something else
    if CursorCreated and not MotherState.MouseIsBeyondOurWindow
      then SetCursor(icon_hwcursor); //winAPI call

    if MotherState.OS in OSNeedsMousePointerHack //namely Vista
    then begin
      if (not f_PointerVisible or MotherState.UseHardware1bitCursor)
        //and not MotherState.MouseIsBeyondOurWindow
      then begin
        if ShowCursorCount < 1 then begin
          ShowCursor(True); //winAPI call
          inc(ShowCursorCount);
        end;
      end
      else begin
        if ShowCursorCount > 0 then begin
          ShowCursor(False); //just setting a completely transparent cursor is not enough
          dec(ShowCursorCount);
        end;
      end;
    end;

   {$endif}
    if MotherState.UseHardware1bitCursor <> f_prevUseHwCursor
    then begin
      f_prevUseHwCursor:= MotherState.UseHardware1bitCursor;
      _UploadPointer;
    end;
  end;
  
  procedure TWindowManager._CreateGLcursor;
  begin
    glGenTextures(1, @f_cursortexture);
    glBindTexture(GL_TEXTURE_2D, f_cursortexture);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
    _UploadGLCursor();
  end;
  
  procedure TWindowManager._UploadGLcursor;
  begin
    glBindTexture(GL_TEXTURE_2D, f_cursortexture);
    glTexImage2d(GL_TEXTURE_2D, 0, GL_RGBA, 32, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, @PointerBitmap.Bitmap);
  end;

  procedure TWindowManager._DrawGLcursor;
  var x, y: integer;
  begin
   if f_PointerVisible and not MotherState.MouseIsBeyondOurWindow
   then begin
     glEnable(GL_TEXTURE_2D);
     glEnable(GL_ALPHA_TEST);
     glEnable(GL_BLEND);
     glAlphaFunc(GL_GREATER, 0);
     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
     glDisable(GL_CULL_FACE);
     glDisable(GL_DEPTH_TEST);
     glViewport(0, 0, MotherState.DisplayWidth, MotherState.DisplayHeight);
     glMatrixMode(GL_PROJECTION);
     glLoadIdentity();
     glMatrixMode(GL_MODELVIEW);
     glLoadIdentity();
     glOrtho(0, MotherState.DisplayWidth, MotherState.DisplayHeight, 0, 0 ,1);
     glColor4f(1, 1, 1, MotherState.Fadein);
     glBindTexture(GL_TEXTURE_2D, f_cursortexture);
     x:= round(MouseX - PointerXHotSpot);
     y:= round(MouseY - PointerYHotSpot);
     glBegin(GL_QUADS);
       glTexCoord2f(0, 1);
       glVertex2f(x, y + 32); //bottom left corner
       glTexCoord2f(0, 0);
       glVertex2f(x, y);
       glTexCoord2f(1, 0);
       glVertex2f(x + 32, y);
       glTexCoord2f(1, 1);
       glVertex2f(x + 32, y + 32);
     glEnd;
   end;
  end;

  {$ifdef windows}
   procedure FlipBytes(b: Pbyte; size: integer);
   var
     i, j: integer;
     a: byte;
   begin
     For i:= 1 to Size do begin
       a:=0;
       for j:=0 to 7 do
         if (b^ and (1 shl j)) <> 0 then a:= a or (1 shl (7-j));
       b^:= a;
       inc(b);
     end;
   end;
  {$endif}

  procedure TWindowManager.SetPointerImage(p: pointer; xhs, yhs: integer);
  var
    x, y, j: integer;
    wA, wL: dword;
    averageL, averageA, averageV, z: float;
  const
    lkoeff: array[0..2] of float = (0.3, 0.5, 0.2);
    ditherfactor: array [0..3, 0..3] of float = (
      ( 0.00, 0.45, 0.10,  0.33),
      ( 0.75, 1.00, 0.66,  0.90),
      ( 0.10, 0.33, 0.00,  0.45),
      ( 0.66, 0.90, 0.75,  1.00));
  begin
    if Assigned(p) then with PointerBitmap do begin
      Move(p^, Bitmap, 32 * 32 * 4);
      
      if (xhs <0) or (yhs <0) or (xhs> 31) or (yhs > 31) then Die(
        MI_ERROR_PROGRAMMER_NO_BAKA, ['Invalid mouse pointer hot spot coordinates ('
         +IntToStr(xhs)+':'+IntToStr(yhs)+')']);

      // now compose the two 1-bit masks
      averageL:= 0;
      averageV:= 0;
      for y:= 0 to 31 do
        for x:= 0 to 31 do begin
          for j:=0 to 2 do begin
            averageL += Bitmap[y, x, j] * lkoeff[j] * (Bitmap[y, x, 3]/255) / (32*32);
          end;
          averageV += Bitmap[y, x, 3] / (32*32*255)
        end;
      if averageV < 0.01 then averageV:= 0.01;
      averageL:= averageL / averageV;
      averageA:= 0;
      for y:= 0 to 31 do
        for x:= 0 to 31 do
          averageA += Bitmap[y, x, 3] / (32*32);
      for y:= 0 to 31 do begin
        wA:=0;
        wL:=0;
        for x:= 31 downto 0 do begin
          wA:= wA shl 1;
          wL:= wL shl 1;
          if Bitmap[y, x, 3] > averageA * HW_POINTER_ALPHA_THRESHOLD then begin
            wA:= wA or 1;
            z:= 0.0;
            for j:=0 to 2
              do z+= Bitmap[y, x, j] * lkoeff[j]  * (Bitmap[y, x, 3]/255);
            if z > (averageL * ( 1 + (ditherfactor[x and $3, y and $3]-0.5) * HW_POINTER_DITHER_FACTOR))
              then wL:= wL or 1;
          end;
        end;
        XorMask[y]:= wL;
        AndMask[y]:= not wA;
        {$ifdef windows}
          FlipBytes(@XorMask[y], 4);
          FlipBytes(@AndMask[y], 4);
        {$endif}
      end;
      PointerXHotSpot:= xhs;
      PointerYHotSpot:= yhs;
      f_PointerVisible:= Yes;
    end
    else begin
      With PointerBitmap do begin
        FillChar(Bitmap, sizeof(Bitmap), 0);
        FillChar(Andmask, sizeof(AndMask), $ff);
        FillChar(XorMask, sizeof(XorMask), 0);
      end;
      PointerXHotSpot:= 1;
      PointerYHotSpot:= 1;
      f_PointerVisible:= No;
    end;
    _UploadPointer;
  end;

  procedure _LogGoDown;
  begin
    AddLog(
      RuEn('Инициировано закрытие программы:'#10#13'  %0.'
          ,'Program shutdown initiated:'#10#13'  %0.')
          ,[MotherState.SuddenShutdownReason]);
  end;

  procedure LogGoDown(s: WideString);
  begin
    MotherState.SuddenShutdownReason:= s;
    _LogGoDown;
  end;

  procedure LogGoDownWM(s: WideString);
  begin
    MotherState.SuddenShutdownReason:= PervertedFormat(RuEn(
      'окно получило оповещение %0' , 'the window received %0 notification'), [s]);
    _LogGoDown;
  end;

  procedure LogGoDownM(s: WideString);
  begin
    MotherState.SuddenShutdownReason:= PervertedFormat(RuEn(
      'получено сообщение %0', 'a system notification received, %0'), [s]);
    _LogGoDown;
  end;


Procedure TWindowManager.SwapBuffers;
var
  q_prev: qword;
begin
  glGetError;

  if not MotherState.UseHardware1bitCursor then _DrawGLcursor;
  UsecByTsc(@q_prev);
 {$ifdef unix}
   {$ifdef darwin}
     aglSwapBuffers(context);
   {$else}
     glXSwapBuffers(Display, WindowHandle);
   {$endif}
 {$else}
   wglSwapBuffers(display);
 {$endif}

  glFinish; {this is ignored by modern drivers (ogl 3.0+),
    but on legacy hardware with old drivers (ogl 2.x) is *vital*
    for getting a proper measured value.

    Yes, I'm still testing on GeForce FX 5200
  }

  //this shit is IMPORTANT! Used in quality auto-tuning.
  MotherState.LastSwapBuffersDuration:= UsecByTsc(@q_prev);

  if (MotherState.FPS < HW_POINTER_LOW_FPS_THRESHOLD) and not MouseIsGamepad
    then MotherState.UseHardware1bitCursor:= Yes;
  if MotherState.FPS > HW_POINTER_HIGH_FPS_THRESHOLD
    then MotherState.UseHardware1bitCursor:= No;

  //Иначе в семёрке курсор невидимый когда над заголовком окна
  if MotherState.MouseIsBeyondOurWindow and not MouseIsGamepad
                                  then MotherState.UseHardware1bitCursor:= Yes;
  if (MotherState.QF > fixedfont_min_qf_to_use_hq_texture)
  and not MotherState.FixedFontEmergencyDownsample
     then MotherState.FixedFontQuality:= 0;
  if MotherState.QF < fixedfont_max_qf_to_use_lq_texture
     then MotherState.FixedFontQuality:= 1;
end;


Procedure TWindowManager.MainLoop;
begin
  repeat
    OneCycle; //polling the window manager events and stuff
  //  if not ExitRequested and not HasFocus then Sleep(NO_FOCUS_SLEEP_INTERVAL);
    if CurrentOwner = NOT_A_MODULE //not called from inside the module DLL pulse event
      then OnIdle; //includes calling the module DLL pulse event
    NowRestarting:=No;
  Until MotherState.ExitRequested or MotherState.RestartRequested;
end;

function TWindowManager._readKeyState (i: integer): boolean;
begin
  Result:=f_KeyState[i];
end;


{$ifdef unix}
  {$ifdef darwin}
    {$include cl_winman_carbon.inc}
    {$include cl_winman_agl.inc}
  {$else}
    {$include cl_winman_x11.inc}
    {$include cl_winman_glx.inc}
  {$endif}
{$else}
  {$include cl_winman_win32.inc}
  {$include cl_winman_wgl.inc}
{$endif}




