{
    This file is part of Chentrah,
    Copyright (C) 2004-2012 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/

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


  {$include mo_globaldefs.h}
  {$typeinfo on}

unit mo_module;

interface
uses
  SysUtils, Classes, {$ifdef windows} Windows, {$endif}
   typinfo, chepersy, mo_classes, mo_hub,
    mo_resources, {mo_game,} mo_menu, mo_sessionstack{, mo_indexer, mo_text}
    ,zstream
//,mo_sortedchain
, mo_rollback
, mo_threads
, mo_quass
  , mo_viewport
    ;
  
//  function InternalHandleMessage (msg: TCbMessage; p0, p1, p2: integer): boolean;
  function _Pulse: boolean;
  function _Save: boolean;
  function _Free: boolean;
  
  procedure RegisterStandardModuleClasses;
  
const
  NonKeys: set of TKey = [KEY_NULL, KEY_MWHEEL_DOWN, KEY_MWHEEL_UP];
//  MainMenuInvocationKey: TKey = KEY_ESCAPE;
  JoystickHistoryLimit = 10; //The last movements saved for possible stochastical filtering. Analog joysticks are noisy beasts.

//I move the fields, than need not be saved as a part of the session, to global variables (gv_*).
//The module class is a singleton anyway.
var
  SessionSaved: boolean = false;
  gv_ThisRunFrame: integer = 0;
  gv_IsAlreadyWaitingForThreadsToTerminate: boolean = false;
  gv_SessionHash: TResourceHash;
  gv_GoingToEraseSession: boolean = false;
  gv_QuickSaveHotKey,
  gv_QuickLoadHotKey,
  gv_MainMenuHotKey: TKeySetArray;
  gv_FirstCycle: boolean = true;

type
  TKeyboardState = array[TKey] of longbool;

  TPenState = packed record
    x, y, pressure,
    xTilt, yTilt, Rotation: float;
  end;

  TJoystickPosition = packed record
    x, y, dx, dy: float;
  end;
  TJoystickState = array of TJoystickPosition;
  TJoysticksStates = array[TJoystickType] of TJoystickState;
  TJoyType = record
    case integer of
    0: (trec: TJoystickType);
    1: (tnum: integer);
  end;

  { TModule }

  TModule = class(TManagedObject)
  protected
    f_firstRun: boolean;
    f_FrameTime: float;
    DeleteOnUnload: TArrayOfManagedObjects;
    procedure ResizeControl(c: TControl);
    procedure RenderControl(c: TControl);
    procedure CycleControl(c: TControl);
    procedure RegisterJoystickMovement(t: TJoystickType; _x, _y, _dx, _dy: float);
    function _get_pressed_keys: TKeySet;
  public
    BackgroundTasks: TSortedList;
    Control: TControl;
    MainMenu: TControl;
    mousex, mousey,
    prevDisplayWidth, prevDisplayHeight: integer;
    InsertMode,
    RestorePressedKeysStateAfterLoading: boolean;
    KeyDown: TKeyboardState;
    WMsg: array of TWindowMessage;
    TextInput: WideString; //the text input messages add to this
    PenState: TPenState;
    JoysticksStates: TJoysticksStates;
    SessionId: AnsiString;
    SessionSaved: boolean;
    QualityAssurer: TQualityAssurer;

    constructor Create;
    destructor Destroy; override;
    procedure RegisterFields; override;
    procedure AfterLoading; override;
    procedure BeforeSaving; override;
    function Cycle: boolean;
    
//    procedure OnGetFocus; virtual;
//    procedure OnLoseFocus; virtual;
    procedure Pulse; virtual;
    procedure OnCreate; virtual;
    procedure OnDestroy; virtual;
    procedure SwitchSession(newId: AnsiString);
    procedure ForkSession(newId: AnsiString);
    procedure SaveSession;
    procedure EraseCurrentSession; //causes a switch to session 'default';
    procedure Restart;
    procedure RollbackSession; //causes the module to restart
    function GetSessionsList: TArrayOfAnsiString; {will crash gleefully if it finds a folder with a name it doesn't like >:)
      Not to mention it's slow, lists the session root folder for the current module for folder names
    }

    procedure ProcessMessages; virtual;

    procedure OnPress(key: TKey); virtual;
    procedure OnRelease(key: TKey); virtual;

    procedure InvokeMenu(mclacc: CControl); virtual;
    property PressedKeys: TKeySet read _get_pressed_keys;
    function CheckHotkey(k: Tkey; def: TKeySetArray): boolean;
    procedure InitVolatileFields();
    function GetConfigStr(name: AnsiString; defval: AnsiString = ''): AnsiString;
    procedure SetConfigStr(name, val: AnsiString);
    procedure AddPerishable(o: TManagedObject);
    procedure RemovePerishable(o: TManagedObject);
  end;
  
  CModule = class of TModule;
  procedure CreateModule(c: CModule);
  procedure G_SaveSession; cdecl;
  
var
  Module: TModule;

  MyModClass: CModule;


implementation

  procedure RegisterStandardModuleClasses;
  begin
    RegisterResourceClasses;
    RegClass(TViewPort);
    RegClass(TModule);
    RegisterStandardMenuClasses;
  end;



  procedure TModule.RegisterFields;
  begin
    RegClass(TControl);
    RegClass(TImage);
    RegClass(TStatic2dTexture);
    RegClass(TDynamic2dTexture);
    RegClass(TModuleMainMenu);
    RegClass(TSortedList);
//    RegClass(TGuiSkin);
    RegType('*TKeyboardState', TypeInfo(longbool), [0, high(TKeyboardState)]);
    RegType(TypeInfo(TPenState), SizeOf(TPenState), ['x','y','pressure','xtilt','ytilt','rotation',TypeInfo(float)]);
    RegType(TypeInfo(TJoystickType));
    RegType(TypeInfo(TJoystickPosition), Sizeof(TJoystickPosition), ['x','y','dx','dy',TypeInfo(float)]);
    RegType(TypeInfo(TJoystickState), TypeInfo(TJoystickPosition));
    RegType(TypeInfo(TJoysticksStates), TypeInfo(TJoystickState), TypeInfo(TJoystickType));

    inherited;
    ListFields([
      'f_firstrun', @f_firstrun, TypeInfo(boolean),
      '-f_FrameTime', @f_FrameTime, TypeInfo(float), //no saving amidst the cycle
      'DeleteOnUnload', @DeleteOnUnload, typeinfo(TArrayofManagedObjects),
      'BackgroundTasks', @BackgroundTasks, TypeInfo(TSortedList),
      'Control', @Control, TypeInfo(TControl),
      'MainMenu', @MainMenu, typeinfo(TControl),
//      'GuiSkin', @GuiSkin, TypeInfo(TGuiSkin),
//      'TextureIndex', @TextureIndex, TypeInfo(TIndexer),
      'mousex', @mousex,
      'mousey', @mousey,
      'prevDisplayWidth', @prevDisplayWidth,
      'prevDisplayHeight', @prevDisplayHeight,  TypeInfo(integer),
      'InsertMode', @InsertMode,
      'RestorePressedKeysStateAfterLoading', @RestorePressedKeysStateAfterLoading, TypeInfo(boolean),
      'KeyDown', @KeyDown, '*TKeyboardState',
      '-wMsg', @WMSG, '*pointer',
      'TextInput', @TextInput, typeinfo(WideString),
      'PenState', @PenState, typeinfo(TPenState),
      'JoyStates', @JoysticksStates, typeinfo(TJoysticksStates),
      'session_id', @SessionId, typeinfo(AnsiString),
      '-SessionSaved', @SessionSaved, typeinfo(Boolean), //skipped
      '-QA', @QualityAssurer, CPS_POINTER //skipped
     ]);
  end;

  procedure TModule.AfterLoading;
  begin
    DeleteUnclaimedResources;
    Module:=Self;
    if not RestorePressedKeysStateAfterLoading
      then FillChar(KeyDown, sizeof(KeyDown), 0); //set all keys' pressed state to false
    if Assigned(MainMenu) then begin
      MainMenu.Kill;
      //InvokeMenu;
    end;
    QualityAssurer:= TQualityAssurer.Create;
  end;
  
  procedure TModule.BeforeSaving;
  begin

  end;

  procedure TModule.ResizeControl(c: TControl);
  begin
    if Assigned(c.Lower) then ResizeControl(c.Lower);
    c.OnResize;
  end;

  procedure TModule.RenderControl(c: TControl);
  begin
    if Assigned(c.Lower) then RenderControl(c.Lower);
    c.Render;
  end;

  procedure TModule.CycleControl(c: TControl);
  begin
    if Assigned(c.Lower) then CycleControl(c.Lower);
    c.Cycle;
  end;

  Constructor TModule.Create;
  begin
    inherited;
    Module:=Self;
    f_FirstRun:=Yes;
    QualityAssurer:= TQualityAssurer.Create;
//    TextureIndex:=TIndexer.Create;
  end;
  
  Destructor TModule.Destroy;
  var i: integer;
  begin
    if MotherState^.WindowExists and not MotherState^.ExitRequested then begin

      //deleting non-saveable opengl resources, namely shaders.
      //will cause Access Violation in msvcrt.dll
      // if do this when window is already destroyed

      for i:=0 to High(DeleteOnUnload) do
        if Assigned(DeleteOnUnload[i])
          then DeleteOnUnload[i].Free;
    end;

    OnDestroy;
    inherited;
  end;

  procedure TModule.AddPerishable(o: TManagedObject);
  begin
    SetLength(DeleteOnUnload, Length(DeleteOnUnload) + 1);
    DeleteOnUnload[High(DeleteOnUnload)]:= o;
  end;

  procedure TModule.RemovePerishable(o: TManagedObject);
  var i, j: integer;
  begin
    i:=0;
    while i <= High(DeleteOnUnload) do begin
      if DeleteOnUnload[i] = o then begin
        for j:=i to High(DeleteOnUnload) - 1 do
          DeleteOnUnload[j]:= DeleteOnUnload[j + 1];
        SetLength(DeleteOnUnload, Length(DeleteOnUnload) - 1);
        Exit;
      end;
      Inc(i);
    end;
  end;

  function TModule.Cycle: boolean;
  begin
    Result:=Yes;
    if f_FirstRun then begin
      OnCreate;
      f_firstRun:=No;
    end
    else Pulse;
  end;

{  procedure TModule.OnGetFocus;
  begin
    f_frametime:=0;
  end;
  
  procedure TModule.OnLoseFocus;
  begin

  end;
}

    
  procedure TModule.Pulse;
  begin
    if gv_FirstCycle then begin
      Self.InitVolatileFields;
      gv_FirstCycle:= false;
    end;
    if not Assigned(BackgroundTasks) then BackgroundTasks:= TSortedList.Create();
    SessionSaved:= false;
    if (MotherState^.ModuleState = ms_WaitingForThreadsToTerminate)
      and not gv_IsAlreadyWaitingForThreadsToTerminate then begin
      Control:= TThreadsStopIndicator.Create(); //everything is reset, utterly breaking the controls chain
      gv_IsAlreadyWaitingForThreadsToTerminate:= Yes;
    end;
    if ((prevDisplayWidth <> MotherState^.DisplayWidth)
      or (prevDisplayHeight <> MotherState^.DisplayHeight))
    then begin
      if Assigned(Control) then ResizeControl(Control);
      prevDisplayWidth:= MotherState^.DisplayWidth;
      prevDisplayHeight:= MotherState^.DisplayHeight;
    end;
    ProcessMessages;
    if (gv_ThisRunFrame >= 10) then QualityAssurer.Pulse;
    if Assigned(Control) then CycleControl(Control);
    if Assigned(Control) then RenderControl(Control);
    inc(gv_ThisRunFrame);
    if (gv_ThisRunFrame >= 3) and (BackgroundTasks.Count > 0)  and not Assigned(Threadmanager)
    then try
       //Lets the module run the first two frames dry, with background tasks hanging in limbo.
       //  This way, if a background thread crashes, it crashes safely, without the need to restart the entire application.
      Threadmanager:= TThreadManager.Create;
      ThreadManager.Start;
    except
      Die('Крах при запуске потоков фоновой обработки.','Crashed trying to create background threads.');
    end;
  end;

  procedure TModule.OnCreate;
  begin
//    GuiSkin:= TGuiSkin.Create('*I/modules/chentrah/guiskins/default.ini');
  end;

  procedure TModule.ProcessMessages;
  var
    e: PInputEvent;
    t, c, mx, my: integer;
  begin
    e:= MotherState^.InputEvents;  //an array terminated with element with EventType of 0.
    if not Assigned(e) then Exit;
    while e^.EventType <> 0 do begin
      case e^.EventType of
        CGM_PRESS, CGM_RELEASE, CGM_TYPE: begin
          mousex:= Trunc(e^.mx);
          mousey:= Trunc(e^.my);
          case e^.EventType of
            CGM_PRESS: OnPress(e^.key);
            CGM_RELEASE: OnRelease(e^.key);
            CGM_TYPE: TextInput+= e^.character;
          end;
        end;
        CGM_MOUSEMOVE: begin
          mx:=_GetCgm();
          AfterEfCheck;
          my:=_GetCgm();
          AfterEfCheck;
          if (Trunc(e^.mx) <> mousex) or (Trunc(e^.my) <> mousey) then begin
            mousex:= Trunc(e^.mx);
            mousey:= Trunc(e^.my);
            OnPress(KEY_NULL);
          end;
        end;
        CGM_PEN: begin
          PenState.x:= e^.PenX;
          PenState.y:= e^.PenY;
          PenState.pressure:= e^.PenPressure;
          PenState.xTilt:= e^.PenReserved1;
          PenState.yTilt:= e^.PenReserved2;
          PenState.Rotation:= e^.PenReserved3;
          OnPress(KEY_TABLET_PEN_ACTION);
          MotherState^.CullFrameRate:= 999; //pedal to metal! (disable framerate culling for the next frame: pen tablet messages may flow in at a very high rate)
        end;
        CGM_JOYSTICK: begin
          RegisterJoystickMovement(e^.JoyType, e^.JoyX, e^.JoyY, e^.JoydX, e^.JoydY);
        end;
      else
        ; //silently ignore unknown events Die(MI_ERROR_PROGRAMMER_NO_BAKA, ['TModule.ProcessMessages() :Invalid input ' + IntToStr(t)]);
      end;
      inc(e);
    end;
  end;

  procedure TModule.OnPress(key: TKey);
  begin
    if (ord(key) < 0) or (ord(key) > 255) then Die('Invalid pressed key, scan code = %0', [ord(key)]); //shit happens

    if key <> KEY_NULL then begin
      if
        CheckHotKey(key, gv_MainMenuHotKey)
        and (not Assigned(MainMenu) or MainMenu.Scraped) then begin

          InvokeMenu(TModuleMainMenu);
        Exit;
      end;
      if CheckHotKey(key, gv_QuickSaveHotKey) then begin
        SaveSession;
        Exit;
      end;
      if CheckHotKey(key, gv_QuickLoadHotKey) then begin
        RollbackSession;
        Exit;
      end;
    end;
    if not (Key in NonKeys) and
      (KeyDown[key] and not (MotherState^.TextInput and (key in KeysAcceptingAutoRepeatInTextInputMode)))
    then Exit;
    KeyDown[key]:=Yes;
    if Assigned(Control)
      then Control.OnKey(key);
  end;

  procedure TModule.OnRelease(key: TKey);
  begin
    if not KeyDown[key] then Exit;
    KeyDown[key]:=No;
    if Assigned(Control)
      then Control.OnKey(key);
  end;

  procedure TModule.InvokeMenu(mclacc: CControl);
  begin
    if Assigned(MainMenu) then MainMenu.Kill;
    MainMenu:= mclacc.Create;
  end;

  procedure TModule.RegisterJoystickMovement(t: TJoystickType; _x, _y, _dx, _dy: float);
  var i, it: integer;
  begin
    it:= ord(t);
    if (it < 0) or (it > ord(high(TJoystickType))) then Exit; //when range checking is off, shit could happen.
    if length(JoysticksStates[t]) < JoystickHistoryLimit
      then SetLength(JoysticksStates[t],
        length(JoysticksStates[t]) + 1);
    if length(JoysticksStates[t]) > JoystickHistoryLimit then SetLength(JoysticksStates[t], JoystickHistoryLimit);
    for i:= high(JoysticksStates[t]) downto 1 do JoysticksStates[t][i]:= JoysticksStates[t][i - 1];
    with JoysticksStates[t][0] do begin
      x:= _x;
      y:= _y;
      dx:= _dx;
      dy:= _dy;
    end;
  end;

  function GetChepersyErrorLog: WideString;
  var i: integer;
  begin
    Result:='';
    for i:= CpsError.Count - 1 downto 0 do
      Result+= Utf8Decode(CpsError[i]);
  end;

  function GetChepersyWarnings: WideString;
  var i: integer;
  begin
    Result:='';
    for i:= CpsParserWarnings.Count - 1 downto 0 do
      Result+= Utf8Decode(CpsParserWarnings[i]);
  end;
  
  procedure TModule.OnDestroy;
  begin
    if Assigned(Control) then Control.Free;
  end;

  procedure TModule.SwitchSession(newId: AnsiString);
  begin
    _SetConfigStr(MotherState^.ModulePureName, 'SessionId', PAnsiChar(newId)); //will not have effect until the next time the module is loading
    AddLog(RuEn('Переключение сессии с %0 на %1.','Switching the session from %0 to %1.'),[mo_sessionstack.SessionId, newID]);
    Restart; //will eventually lead to current session being saved.
  end;

  procedure TModule.ForkSession(newId: AnsiString);
  begin
    _SetConfigStr(MotherState^.ModulePureName, 'SessionId', PAnsiChar(newId)); //will not have effect until the next time the module is loading
    AddLog(RuEn('Сессия %0 форкнута как %1.','Session %0 forked as %1.'),[mo_sessionstack.SessionId, newID]);
    mo_sessionstack.SessionId:= newID;
    Restart; // to actualize creating the folder
  end;

  procedure TModule.EraseCurrentSession;
  begin
    AddLog(RuEn('Текущая сессия %0 помечена для удаления.','The current session %0 is marked for deletion.'),[mo_sessionstack.SessionId]);
    gv_GoingToEraseSession:= Yes; //causes the normal session saving routine to erase the session instead.
    SwitchSession('default');
  end;

  procedure TModule.Restart;
  begin
    MotherState^.ModuleState:= ms_Unloading;
  end;

var
  FirstRun: boolean = Yes;

  procedure G_SaveSession; cdecl;
  var
    tempfilename: ansistring;
    fs: TFileStream;
    nu: integer;
    cs: TCompressionStream;
  begin
    if SessionSaved then begin
      AddLog(RuEn('Сессия уже сохранена.','Session is already saved.'));
      Exit;
    end;
    tempfilename:= SessionFileNameWoExt + 'temporary.$$$';
    if MotherState^.VerboseLog then AddLog('Saving session: temporary file name "%0"',[tempfilename]);
    _MakeSurePathExists(PChar(ExtractFilePath(tempfilename)));
    AfterEfCheck;
    if MotherState^.NeedToKeepModuleResources()
      then StoreResourceClassesList; {After introducing OpenAL this critter
       started crashing craptacularly at the program exit, managing to
       lock the sound card and hang the chentrah process in a zombie state
       so that even sudo kill won't help.
      The only way to get everything working ahgain is restarting the X server.
      I suspect there's something wrong with the cross-module exception
       handling mechanism in the Linux version.}
    fs:= TFileStream.Create(tempfilename, fmCreate);
    cs:= TCompressionStream.Create(clMax, fs);
    if not CpsStore(Module, MI_OF_SESSION, cs) then begin
      fs.Free;
      Die(GetChepersyErrorLog());
    end;
    cs.Free;
    fs.Free;
    PutSavedSessionOntoStack(tempfilename);
    SessionSaved:= true;
  end;
  
  procedure SafeSaveSession();
  begin
    try
      G_saveSession;
    except
      Die(RuEn('Крах при сохранении сессии!','Crashed saving the session!'));
    end;
  end;
  
  procedure TModule.saveSession;
  begin
    SafeSaveSession;
  end;

  procedure TModule.RollbackSession;
  begin
    MotherState^.RollBackTheSessionOnNextLoad:= Yes;
    MotherState^.ModuleState:= ms_Unloading;
    MotherState^.BlackoutStart:= Now();
  end;

  procedure LoadSession; cdecl;
  var
    filename: AnsiString;
    fs: TFileStream;
    ds: TDecompressionStream;
    OL: chepersy.TArrayOfManagedObjects;
    i: integer;
    LO: chepersy.TManagedObject;
  begin
    SessionSaved:= false;
    filename:= GetSessionFileName();
    if MotherState^.VerboseLog then AddLog('Loading session: file "%0"',[filename]);
    Module.Free;
    fs:= TFileStream.Create(filename, fmShareDenyWrite);
    ds:= TDecompressionStream.Create(fs);
    SetLength(OL, 1);
    LO:= CpsLoad( MI_OF_SESSION, ds, OL);
    if not Assigned(LO) then Die(RuEn(
      'Загруженная сессия состоит из пустого объекта',
      'The loaded session resolved to an empty object'
    ));
    if not (LO is TModule) then Die(RuEn(
      'Корневой объект загруженной сессии (%0) не является потомком TModule',
      'The root object of the loaded session (%0) is not a TModule descendant'),
      [string(LO.ClassName)]
    );
    Module:= LO as TModule;
    ds.Free;
    fs.Free;
    try
      for i:=0 to high(OL) do
        if 0 = (OL[i].Cpsmask and CPS_ROLLBACK_COPY_MASK) //this is not a backup copy
          then OL[i].AfterLoading
          else OL[i].CpsMask:= OL[i].CpsMask or CPS_ROLLBACK_COPY_JUST_LOADED_MASK;
    except
      Die(RuEn(
        'Сбой при актуализации объекта %0 из %1 (класс %2)',
        'Failed to actualize object %0 of %1 (class %2)')
        ,[i, high(OL) + 1, string(OL[i].ClassName)]);
    end;
    SetLength(OL, 0);
    if CpsParserWarnings.Count > 0 then AddLog(RuEn(
      '~ ПРЕДУПРЕЖДЕНИЯ ПРИ ЗАГРУЗКЕ СЕССИИ: ~'#10#13'%0'#10#13'~ КОНЕЦ ПРЕДУПРЕЖДЕНИЙ ~',
      '~ WARNINGS AT SESSION LOADING: ~'#10#13'%0'#10#13'~ END WARNINGS ~'),
      [GetChepersyWarnings()]);
    FinalizeSessionRollback;
    if not Assigned(Module) then Die(GetChepersyErrorLog());
    Module.SessionID:= mo_sessionstack.SessionId;
  end;

  function TModule.GetSessionsList: TArrayOfAnsiString;
  var
    sl: TSessionList;
    i: integer;
  begin
    sl:= TSessionList.Create(Self.SessionId);
    SetLength(Result, sl.Count);
    for i:=0 to sl.Count - 1 do Result[i]:= sl.SID[i];
  end;

  function TModule._get_pressed_keys: TKeySet;
  var k: TKey;
  begin
    Result:= [];
    for k in TKey do if Self.KeyDown[k] then Include(Result, k);
  end;

  function TModule.CheckHotkey(k: Tkey; def: TKeySetArray): boolean;
  var
    pk: TKeySet;
    i: integer;
  begin
//pk:=[k]; addlog('* %0',[GetOneHotKeyName(pk)]);
    //if Self.KeyDown[k] then Exit(false);
    result:= false;
    pk:= Self.PressedKeys + [k]; //cache it
    for i:= 0 to High(def) do begin
      if ([k] * def[i]) <> [k] then continue;
      if (def[i] * pk) = def[i] then begin Result:= true; break end;
    end;
  end;

  procedure TModule.InitVolatileFields();
  begin
    mo_sessionstack.GetSessionIDFromConfig;
    SessionID:= mo_sessionstack.SessionId;
    try
      gv_QuickSaveHotKey:= ParseHotKeyString(GetConfigStr('quick_save_hot_key','F5'));
      gv_QuickLoadHotKey:= ParseHotKeyString(GetConfigStr('quick_load_hot_key','F9'));
      gv_MainMenuHotKey:= ParseHotKeyString(GetConfigStr('main_menu_hot_key','ESCAPE,GAMEPAD_START'));
    except
      Die('Не удалось загрузить настройки горячих клавиш','Failed to load hot key config values')
    end;
  end;

  function TModule.GetConfigStr(name: Ansistring; defval: AnsiString = ''): AnsiString;
  begin
    Result:= string(_GetConfigStr(MotherState^.ModulePureName, PAnsiChar(name)));
    if Result = '' then Result:= defval;
  end;

  procedure TModule.SetConfigStr(name, val: Ansistring);
  begin
    _SetConfigStr(MotherState^.ModulePureName, PAnsiChar(name), PAnsiChar(val));
  end;

  procedure SafeLoadSession;
  begin
    try
      LoadSession;
    except
      MotherState^.RollBackTheSessionOnNextLoad:= false;
      Die(RuEn('Крах при загрузке сессии','Crashed loading the session'));
    end;
  end;

    function PureName(a: string): string;
    //uniform file name, identic for both Windows and Linux
    begin
      Result:=ChangeFileExt(ExtractFileName(a), '');
      if (Length(Result) >=3) and (Copy(Result, 1, 3)='lib')
        then Result:=Copy(Result, 4, Length(Result) - 3);
    end;

  var G_c: CModule;

  procedure CreateModule(c: CModule);
  begin
    G_c:=c;
  end;

  var DoOnce: boolean = true;
  
  procedure ReallyCreateModule;
  begin
    GenHash(@gv_SessionHash);
    AfterEfCheck;

    RegisterStandardModuleClasses;
    RegClass(MyModClass);
    CreateModule(MyModClass);
    if HaveASessionToLoad
      then SafeLoadSession
      else begin
        AddLog(RuEn('Первый запуск: создаю сессию с нуля!','First run: creating the session from the scratch!'),[]);
        Module:=G_c.Create;
      end;
  end;

var
  wms: WideString;

  function _Pulse: boolean;
  begin
    Result:=false;
    try
      if DoOnce then ReallyCreateModule;
      Module.Cycle;
      if DoOnce then Module.OnPress(KEY_NULL);
      Result:=True;
      DoOnce:=false;
//SaveSession;
    except
     {$ifndef buildmein}
      try
     {$endif}
        if MotherState^.NowDying or not (ExceptObject is Exception) then begin
          if DoOnce then Die ('Крах при создании модуля.', 'Crashed creating the module')
                    else Die('Крах модуля.', 'The module has crashed.');
        end
        else
          if DoOnce
            then
              Die(RuEn(
                'Крах при создании модуля:'#10#13'  %0',
                'Crashed creating the module:'#10#13'  %0')
                , [TellException(ExceptObject as Exception)])
            else
              Die(RuEn(
                'Крах модуля:'#10#13'  %0',
                'The module crashed:'#10#13'  %0')
                , [TellException(ExceptObject as Exception)]);
     {$ifndef buildmein}
      except end;
     {$endif}
    end;
  end;
  
  function _Save: boolean;
  begin
    if MotherState^.DebugMode then AddLog('The module DLL is executing _Save');
    Result:=true;
    if Assigned(Module) then begin
      if MotherState^.RollBackTheSessionOnNextLoad
      then AddLog(RuEn(
          'Сессия НЕ сохраняется, так как был запрошен её откат.',
          'Session is NOT saved because its rollback was requested.'), [])
      else begin
        if gv_GoingToEraseSession then begin
          AddLog(RuEn(
                    'Стираю текущую сессию "%0".',
                    'Erasing the current session "%0".'), [mo_sessionstack.SessionId]);
          EraseSession(mo_sessionstack.SessionId);
          SessionSaved:= true;
          PlaySound('crashed.wav');
        end
        else try
          SafeSaveSession;
        except
          Result:=false;
        end;
      end;
    end;
    if MotherState^.DebugMode then AddLog('The module DLL has finished executing _Save');
  end;
  
  function _Free: boolean;
  begin
    if MotherState^.DebugMode then AddLog('The module DLL is executing _Free');
    if not SessionSaved then _Save;
    if Assigned(Module) then try
      Result:=true;
      Module.Free;
      pointer(Module):=nil;
      AddLog(RuEn('Модуль выгружен.','The module has been unloaded.') + #10#13#10#13)
    except
      Result:=false;
      try
        Die('Крах модуля при выгрузке.','The module has crashed while unloading.');
      except end;
      MotherState^.NowDying:= false;
    end;
    //if Assigned(Module) then Module.Free;

    if MotherState^.DebugMode then AddLog('The module DLL has finished executing _Free');
  end;
end.
