{
    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/

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


  function SetWindowLong(hWnd:HWND; nIndex:longint; dwNewLong:cardinal):cardinal; stdcall; external 'user32' name 'SetWindowLongA';
  

  var
    glob_focus: boolean = true;



  procedure TWindowManager._UploadPointer;
  var
    cursorInfo: TIconInfo;
    cursor: THandle;
    OldCursor: HCursor;
    EmptyBitmap, EmptyMask: array[0..((32*32) div 8) - 1] of byte;
  begin
    _UploadGLcursor;
    OldCursor:= icon_hwcursor;

    if MotherState.UseHardware1bitCursor
      then icon_hwcursor:= CreateCursor(
        system.Hinstance, PointerXHotSpot, PointerYHotSpot, 32, 32, @PointerBitmap.AndMask, @PointerBitmap.XorMask)
      else begin
        FillChar(EmptyMask, Sizeof(EmptyBitmap), $ff);
        FillChar(EmptyBitmap, Sizeof(EmptyBitmap), 0);
        icon_hwcursor:= CreateCursor(system.Hinstance, 1, 1, 32, 32, @EmptyMask, @EmptyBitmap);
      end;
    SetCursor(icon_hwcursor);

    if CursorCreated then DestroyCursor(OldCursor);
    CursorCreated:= Yes;
  end;
  
  // Эта процедура обрабатывает приходящие в адрес окна сообщения.
  function WndProc(hWnd: HWND; Msg: UINT;  wParam: WPARAM;  lParam: LPARAM): LRESULT; stdcall;
  begin
     if msg = WM_ACTIVATE then begin
       // cannot be processed in the main message loop:
       // arrives only here, in the window procedure
       Case LOWORD(wParam) of
        WA_ACTIVE, WA_CLICKACTIVE: glob_focus:=True;
        WA_INACTIVE: glob_focus:=false;
       end;
     end;
     if msg = WM_SYSCOMMAND then begin
       Case wParam of
         SC_HOTKEY, SC_TASKLIST:
           if assigned(WindowManager) and WindowManager.Focus and MotherState.InFullScreenMode
             then Exit(-1);
       end;
     end;
     if msg = WM_KILLFOCUS then glob_focus:= false;
     if msg = WM_SETFOCUS then glob_focus:= true;
     Result := DefWindowProc(hWnd, Msg, wParam, lParam)    // Default result if nothing happens
  end;
  

(*
  if (fullscreen)												// Are We Still In Fullscreen Mode?
  {
  	dwExStyle=WS_EX_APPWINDOW;								// Window Extended Style
  	dwStyle=WS_POPUP | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;	// Windows Style
  	ShowCursor(FALSE);										// Hide Mouse Pointer
  }
  else
  {
  	dwExStyle=WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;						// Window Extended Style
  	dwStyle=WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;	// Windows Style
  }

  *)
    

  procedure TWindowManager.CreateWindow;
  var
    wndClass : TWndClass;
    _rect: TRect;
    s: string;
    w,h: integer;
    dwStyle, dwExStyle: dword;
  begin
    if MotherState.DebugMode then AddLog('Creating the window...');

    if MotherState.InFullScreenMode then begin
      dwStyle:= dword(WS_POPUP) or dword(WS_MAXIMIZE) or dword(WS_VISIBLE) or dword(WS_CLIPCHILDREN);
      dwExStyle:= WS_EX_APPWINDOW or WS_EX_TOPMOST;
    end else begin
      dwStyle:= WS_CAPTION or WS_SYSMENU or WS_SIZEBOX or WS_VISIBLE or WS_MAXIMIZEBOX or WS_MINIMIZEBOX or WS_CLIPCHILDREN;
      dwExStyle:= WS_EX_APPWINDOW or WS_EX_WINDOWEDGE;
    end;

    s:=AppNick+'_OpenGL_WND';

    // Заполняем бланк-формуляр, которым будем кормить процедуру создания класса окна
    ZeroMemory(@wndClass, SizeOf(wndClass));
    with wndClass do begin
      style         := CS_HREDRAW or    // Перерисовывать окно целиком при изменении ширины
                       CS_VREDRAW or    // ...то же, для высоты
                       CS_OWNDC;        // Окну нужен собственный контекст устройства
      lpfnWndProc   := @WndProc;        // Оконная процедура, которая будет ловить мессаги
      hInstance := {$ifdef fpc}
                    System.HInstance;
                   {$else}
                    SysInit.HInstance;
                   {$endif}
      lpszClassName := PChar(s);
      hIcon:=LoadIcon({$ifdef fpc}System.HInstance{$else}SysInit.Hinstance{$endif}, PChar('MAINICON'));
    end;

    // Регистрируем класс окна (ну, вот нельзя так просто создать окно - ещё,
    //   види-те ли, класс нужен... Нет, явно Виндовс писали извращенцы.)
    if (0=Windows.RegisterClass(wndClass)) then Die('Windows.RegisterClass() returned 0');

    MotherState.ScreenWidth:= GetSystemMetrics(SM_CXSCREEN);
    MotherState.ScreenHeight:= GetSystemMetrics(SM_CYSCREEN);

    if not MotherState.InFullScreenMode then begin
     //adjust the initial width and height with regards to the window decorations
      _rect.left:=1;
      _rect.top:=1;
      _rect.right:=MotherState.DisplayWidth;
      _rect.bottom:=MotherState.DisplayHeight;
      AdjustWindowRectEx(_Rect, dwStyle, false, dwExStyle);
      MotherState.DisplayWidth:=_rect.right - _rect.left + 1;
      MotherState.DisplayHeight:=_rect.bottom - _rect.top + 1;
      w:= MotherState.DisplayWidth;
      h:= MotherState.DisplayHeight;
    end else begin
      w:= MotherState.ScreenWidth;
      h:= MotherState.ScreenHeight;
    end;

    // Создаём окно
    WindowHandle:= CreateWindowEx
      (dwExStyle, PChar(s), PChar(AppNick), dwStyle,
        (MotherState.ScreenWidth - w) div 2,
        (MotherState.ScreenHeight - h) div 2,
         w, h, 0, 0, hInstance, nil);
    if WindowHandle = 0
      then Die('CreateWindowEx() returned 0');

    //SetForegroundWindow(WindowHandle);

    MotherState.WindowExists:=True;
    if MotherState.DebugMode then AddLogOk;
  end;

  procedure  TWindowManager.CloseWindow;
  begin
    if not MotherState.WindowExists then begin
      AddLog(RuEn(
        'Не удалось удалить окно за неимением такового.',
        'Unable to destroy the window due to absence of it.'));
      Exit
    end
    else begin
      if (WindowHandle <> 0) and IsWindow(WindowHandle) and not DestroyWindow(WindowHandle)
      then begin
        AddLog('DestroyWindow() call failed!');
        WindowHandle := 0;
      end
      else
        if MotherState.DebugMode then AddLog('Window destroyed.');
    end;
    // Снимаем с учёта класс окна
    if (not Windows.UnRegisterClass(PChar(AppNick+'_OpenGL_WND'), hInstance))
    then  AddLog('UnRegisterClass() call failed!');
    
    MotherState.WindowExists:=False;

    if CursorCreated then DestroyCursor(icon_hwcursor);
  end;

  {$ifndef fpc}
  function GetKeyboardState(lpKeyState:PBYTE):WINBOOL; external 'user32' name 'GetKeyboardState';
  {$endif}

  procedure TWindowManager.UpdateWindowSize;
  begin
    MotherState.ScreenWidth:= GetSystemMetrics(SM_CXSCREEN); //each frame. A dumb but robust method.
    MotherState.ScreenHeight:= GetSystemMetrics(SM_CYSCREEN);
    GetClientRect(WindowHandle, rect);
    if ((rect.right - rect.left + 1) <> MotherState.DisplayWidth)
    or ((rect.bottom - rect.top + 1) <> MotherState.DisplayHeight)
      then begin
        MotherState.DisplayWidth:=rect.right - rect.left + 1;
        MotherState.DisplayHeight:=rect.bottom - rect.top + 1;
        if not MotherState.InFullScreenMode then begin
          Config['video', 'window_width']:= MotherState.DisplayWidth;
          Config['video', 'window_height']:= MotherState.DisplayHeight;
        end;
      end;
  end;

  procedure TWindowManager.GrabPointer;
  var
    _rect: TRect;
    point: TPoint;
  begin
{
    _DeltaMouse:=true;
      GetClientRect(WindowHandle, rect);
      point.x:=rect.right div 2;
      point.y:=rect.bottom div 2;
      ClientToScreen(Windowhandle, Point);
      SetCursorPos(point.x, point.y);
      
   }
  end;
  
  procedure TWindowManager.UngrabPointer;
  begin
  
  end;

  const
    MAPVK_VK_TO_VSC = 0;
    MAPVK_VK_TO_VSC_EX = 4;
  function DivineScanCode(var msg: TMSG): integer; //as in "divination", not "divinity".
  begin
    { Note: Wine behaves differently.
        In original Windows bit 24 (extended keyboard key)
        is duplicated in the bit 23 of the scancode itself.
        Wine doesn't do that.}
    Result:= ((msg.lParam shr 16) and $FF) or ((msg.lParam shr 17) and $80);
{   if (MotherState.OS in WindowsEmulators) then case Result of
      ДОДЕЛАТЬ!
      $xx: Result:= KEY_LEFT_WINDOWS;
      $xx: Result:= KEY_RIGHT_WINDOWS;
      $xx: Result:= KEY_APP_MENU;
    end;}
  end;



  Procedure TWindowManager.OneCycle;
  var
    msg: TMsg;
    i, oldmx, oldmy, px, py: integer;
    point: TPoint;
    KeyboardState: array[0..255] of byte;
    buff: array[0..10] of WideChar;
    abuff: array[0..10] of AnsiChar;
    numchars: integer;
    scancode: integer;
    procedure CheckIfMouseMoved(per_cycle: boolean = false);
    begin
      GetCursorPos(point);
      px:= point.x;
      py:= point.y;
      ScreenToClient(Windowhandle, point);
      if per_cycle then begin
        oldmx:=f_MouseX;
        oldmy:=f_MouseY;
        MotherState.WindowOriginX:= px - point.x;
        MotherState.WindowOriginY:= py - point.y;
      end;
      f_MouseX:=point.x - rect.left;
      f_MouseY:=point.y - rect.top;
      MotherState.MouseIsBeyondOurWindow:= (f_MouseY < 0) or (f_MouseX < 0)
                              or (f_MouseY > MotherState.DisplayHeight)
                              or (f_MouseX > MotherState.DisplayWidth);
      if ((f_MouseX <> oldmx) or (f_MouseY <> oldmy)) and (per_cycle) then OnMouseMove;
//here goes the code for handling the grabbed pointer *********************************
// *********************  IMPLEMENT IT! ***********************************************
    end;
  begin
    if IsWindow(WindowHandle) then begin {я уже задолбался
      отлавливать все возможные варианты закрытия окна,
      после которого ни о чём не догадывающаяся программа продолжает работать,
      не отображаясь на Панели задач, но грузя процессор и деловито
      отправляя в никуда поток команд OpenGL... :(
    }

      f_focus:= glob_focus and (GetForegroundWindow() {GetFocus()} = WindowHandle);
      f_HadMessages:= false;
      if f_focus
        then f_LoseFocusFramesCount:=0
        else inc(f_LoseFocusFramesCount);

      if MotherState.InFullScreenMode and not f_focus then begin
        LogGoDown(RuEn('окно потеряло фокус','the window lost focus'));
        MotherState.ExitRequested:=true;
      end
      else begin
        if Assigned(WinTabManager) then begin
          WinTabmanager.Pulse;
          for i:=0 to high(WinTabManager.Events) do
            with WinTabManager.Events[i] do
              OnPen((x * MotherState.ScreenWidth) - MotherState.WindowOriginX,
                    ((1-y) * MotherState.ScreenHeight) - MotherState.WindowOriginY,
                    pressure, reserved1, reserved2, reserved3);
        end;
        CheckIfMouseMoved(true);
        _CheckCursorMode();
        While PeekMessage(msg, 0, 0, 0, PM_REMOVE) do begin
          case msg.message of
            WM_KEYDOWN:
              begin
                f_HadMessages:= true;
                scancode:= DivineScanCode(msg);
                numchars:=0;
                if MotherState.TextInput then begin
                  GetKeyboardState(@KeyboardState[0]);
{ //Researching the Unicode support state in the vanilla Windows 98 SE:
addlog('ToUnicode: %0 %1',[msg.wparam, (msg.lParam shr 16) and $FF]);
addlog('-----------------------------------');
addlog('%0  %1',[msg.wparam, (msg.lparam shr 16) and $ff]);
for i:=0 to 255 do addlog('%0,',[KeyboardState[i]]);
addlog('------------------------');
}
                  numchars:=ToUnicode(msg.wparam, (msg.lParam shr 16) and $FF, @KeyboardState[0], @buff[0], 10, 0);
                  if (NumChars = 0) and (MotherState.OS = ostWin9x) then begin
                    //Windows 98 doesn't have the Unicode support installed.
                    //  Assuming system locale to be Russian/Cyrillic CP1251
                    numchars:= ToAscii(msg.wparam, (msg.lParam shr 16) and $FF, @KeyboardState[0], @abuff[0], 0);
                    for i:=0 to numchars - 1 do
                      buff[i]:= Cp1251CharToWideChar(abuff[i]);
                  end;
                end;
                if ((msg.lParam and $FFFF ) = 1)
                  or (MotherState.TextInput and (TKey(scancode) in KeysAcceptingAutoRepeatInTextInputMode))
                then begin
                  if ({(numchars < 1) and} (TKey(scancode) < KEY_MWHEEL_DOWN))
                    or (TKey(scancode) in KeysWorkingInTextInputMode)
                  then begin
                    if (not f_KeyState[scancode])
                      or (MotherState.TextInput and (TKey(scancode) in KeysAcceptingAutoRepeatInTextInputMode))
                    then begin
                      f_KeyState[scancode]:=true;
                      CheckIfMouseMoved();
                      OnPress(scancode);
                    end;
                  end;
                  for i:=0 to numchars - 1 do
                    if buff[i] >= ' ' //no special characters!
                      then begin
                        OnType(buff[i]);
                        OnPress(0); {notify the recipient of the updates,
                                     since OnType just adds to the internal buffer
                                     of the Chentrah module.
                                     The zero scan code is the mouse movement message}
                      end;
                end;
              end;
            WM_KEYUP:
              begin
                f_HadMessages:= true;
                scancode:= DivineScanCode(msg); //угадать скан-код на кофейной гуще
                if (scancode < ord(KEY_MWHEEL_DOWN))
                  and f_KeyState[scancode]
                then begin
                  f_KeyState[scancode]:=false;
                  CheckIfMouseMoved();
                  OnRelease(scancode);
                end;
              end;
            WM_SYSKEYDOWN:
              begin
                f_HadMessages:= true;
              end;
            WM_SYSKEYUP:
              begin
                f_HadMessages:= true; ;
              end;
            WM_LBUTTONDOWN: begin
              f_HadMessages:= true;
              if not f_KeyState[ord(KEY_MOUSE_LEFT)] then begin
                f_KeyState[ord(KEY_MOUSE_LEFT)]:=true;
                CheckIfMouseMoved();
                OnPress(ord(KEY_MOUSE_LEFT));
              end;
            end;
            WM_LBUTTONUP: begin
              f_HadMessages:= true;
              if f_KeyState[ord(KEY_MOUSE_LEFT)] then begin
                f_KeyState[ord(KEY_MOUSE_LEFT)]:=false;
                CheckIfMouseMoved();
                OnRelease(ord(KEY_MOUSE_LEFT));
              end;
            end;
            WM_RBUTTONDOWN:
              if not f_KeyState[ord(KEY_MOUSE_RIGHT)] then begin
                f_KeyState[ord(KEY_MOUSE_RIGHT)]:=true;
                CheckIfMouseMoved();
                OnPress(ord(KEY_MOUSE_RIGHT));
              end;
            WM_RBUTTONUP: begin
              f_HadMessages:= true;
              if f_KeyState[ord(KEY_MOUSE_RIGHT)] then begin
                f_KeyState[ord(KEY_MOUSE_RIGHT)]:=false;
                CheckIfMouseMoved();
                OnRelease(ord(KEY_MOUSE_RIGHT));
              end;
            end;
            WM_MBUTTONDOWN: begin
              f_HadMessages:= true;
              if not f_KeyState[ord(KEY_MOUSE_MIDDLE)] then begin
                f_KeyState[ord(KEY_MOUSE_MIDDLE)]:=true;
                CheckIfMouseMoved();
                OnPress(ord(KEY_MOUSE_MIDDLE));
              end;
            end;
            WM_MBUTTONUP: begin
              f_HadMessages:= true;
              if f_KeyState[ord(KEY_MOUSE_MIDDLE)] then begin
                f_KeyState[ord(KEY_MOUSE_MIDDLE)]:=false;
                CheckIfMouseMoved();
                OnRelease(ord(KEY_MOUSE_MIDDLE));
              end;
            end;
            WM_MOUSEMOVE: begin
              // the mouse movement is custom-handled now
              //f_HadMessages:= true; //probably won't work correctly in Win 2k & XP
            end;
            WM_MOUSEWHEEL: begin
                f_HadMessages:= true;
                i:=smallint(HIWORD(msg.wParam)) div 120;
                while i > 0 do begin
                  CheckIfMouseMoved();
                  OnPress(ord(KEY_MWHEEL_UP));
                  dec(i);
                end;
                while i < 0 do begin
                  CheckIfMouseMoved();
                  OnPress(ord(KEY_MWHEEL_DOWN));
                  inc(i);
                end;
              end;
            WM_SYSCOMMAND: begin
              case msg.wParam of
                SC_CLOSE: begin
                  LogGoDownWM('WM_SYSCOMMAND + SC_CLOSE');
                  MotherState.ExitRequested:= Yes;
                end;
                SC_MAXIMIZE:;
                SC_SCREENSAVE, SC_MINIMIZE:
                  if MotherState.InFullScreenMode then begin
                    LogGoDown(RuEn('окно потеряло фокус','the window lost focus'));
                    MotherState.ExitRequested:=true;
                  end;
                SC_MONITORPOWER:
                  if msg.lParam > 0 then begin
                    LogGoDown(RuEn('монитор выключается или переходит в спящий режим','your monitor shuts down or goes to sleep'));
                    MotherState.ExitRequested:=true;
                  end
              end;
            end;
            WM_SIZE: begin
              if msg.wparam <> SIZE_MINIMIZED then begin
                f_HadMessages:= true;
                UpdateWindowSize;
              end;
            end;
            WM_CLOSE:
              begin
                LogGoDownWM('WM_CLOSE');
                MotherState.ExitRequested:=true;
              end;
            WM_DESTROY:
              begin
                LogGoDownWM('WM_DESTROY');
                MotherState.ExitRequested:=true;
              end;
          else
             ;
          end;
          {if not msg.Message in [WM_QUIT, WM_CLOSE, WM_DESTROY] then} begin
            TranslateMessage(msg);
            DispatchMessage(msg);
          end;
        end;
      end;
      if f_HadMessages then f_LoseFocusFramesCount:=0;
    end
    else begin // IsWindow(WindowHandle)
      LogGoDown(RuEn('окно перестало быть','the window is no more'));
      MotherState.WindowExists:= No;
      MotherState.ExitRequested:= Yes;
    end;
  end;







