{
    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 main function of the mother module

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


var
  EngineObject: TEngineObjects;
  EverythingIsOk: boolean = Yes;
  name, selfname: AnsiString;

  procedure SafeInitSound; cdecl; forward;
  procedure SafeInitVideo; cdecl; forward;


  procedure main();
   var
    f: file;
    i: integer;
    t: TDateTime;
    w: WideString;
  begin
   {$ifdef windows}
    // 2013: it causes a cascade of exceptions at the program termination in FPC 2.6.0 and 2.6.2
    //winwidestringalloc:= false; //WideString becomes incompatible with COM but works faster
   {$endif}
    InitMotherState;

    For i:=1 to ParamCount do
      if (lowercase (Paramstr(i))= '--restart')
         or (copy(lowercase(ParamStr(i)), 1, 9) = '--lockup-')
        then MotherState.RestartRequested:=Yes;
//    Sleep(100);
// ******** FIRST STAGE INIT: ***************************************
    Try
      CgeStartTime:=Now();
     {$ifdef Win32}
      CgeStartingTick:=GetTickCount();
     {$endif}
      if not ThisIsAnOnlyInstance and not MotherState.RestartRequested then begin
        AnotherInstanceIsRunning:=Yes;
        DontWriteALog:=Yes;
      end;

      InitUnicode;
      InitTranslit;

      MessageContainer:=TMessageContainer.Create;
      Config:=TConfigManager.Create;

      {$ifdef unix}
      if (Length(ParamStr(0)) >=8) and (copy(ParamStr(0), 1, 4) = '/tmp')
        then AddStopMessage(MI_NO_UPX_ALLOWED, [AppNick]);
      {$endif}
      // the "stop message" is that red screen which pops up later,
      //  after all the  initialization stages, and tells you (using the
      //  fullscreen OpenGL and fancy graphics) that no, this program won't run.
      
      {$ifdef windows}
      LoadFunctionsUnsupportedInWindows98();
      {$endif}

      DetectOSType;

      Randomize;
      DetermineInstallPath;

      WarningQueue:=TAOW.Create;
      EngineObject:=TEngineObjects.Create;

      if MotherState.InstallPath <> '' then begin
        {$ifdef windows}
          CreateSplashWindow; //P.S. Did it. move it to after paths are determined if I want to re-use the old strategy of many splash variations in bmp files
        {$endif}

        Config.LoadMainFile;
        
        SetCurrentDir(MotherState.InstallPath);

        with MotherState do begin
          DebugMode:=Config.Bool['main','debug_mode'];
          VerboseLog:= DebugMode;
          {$ifdef nodevmodewin32}
            DeveloperMode:= No;
          {$else}
            DeveloperMode:=Config.Bool['main','developer_mode'];
          {$endif}
        end;
        
        DetermineHomePath;


        if not ValidateWritepath(MotherState.HomePath)
        then begin
          AddStopMessage(MI_HOME_PATH_NOT_FOUND,
            [AnsiToWide(MotherState.HomePath), MotherState.UserNameUcs16]);
          MotherState.HomePath:= '';
        end;

        if MotherState.HomePath <> '' then begin
          MotherState.CacheDir:= MotherState.HomePath + LOcalStoreStr + PathSlash;

          DisplayLastWill; //if any. Causes error dialog and Halt()

          Config.LoadUserFile;
        end;
        AppNick:=MyAppNick;//UpperCase(ChangeFileExt(ExtractFileName(ParamStr(0)),''));

        ModulesBasePath:=Config.Path['Modules','BasePath'];
        MotherState.SessionDir:=CgePath(MotherState.HomePath + 'sessions' + DS);

        MessageContainer.LoadFiles;
      end
      else
        DontWriteALog:=True; //there's nowhere to write it
        
      if not MotherState.RestartRequested and not DontWriteALog and FileExists(MotherState.HomePath + 'LOG')
      then try
        AssignFile(f, MotherState.HomePath + 'LOG');
        Erase(f);
      except
        DontWriteALog:= Yes;
        AddStopMessage(MI_CANNOT_DELETE_OLD_LOG,
          [AnsiToWide(MotherState.HomePath) + 'LOG',
          (ExceptObject as Exception).Message, MotherState.UserNameUcs16]);
      end;

      Console:=TConsole.Create;


//byte(nil^):=0;

    Except
      //raise exception and die...
      KillSplashWindow;
      PreInitDie(PIYInitStageCrash, [(ExceptObject as Exception).Message, CallStack(0, '  ')]);
    End;



// ******** SECOND STAGE INIT: ****************************
//    repeat
      Try
        SecondStageInitTime:=Now();
        if MotherState.RestartRequested then begin
         // while not ThisIsAnOnlyInstance do Sleep(500);
          DontWriteALog:=No;
        end;
        if AnotherInstanceIsRunning
          then Die(MI_ERROR_MULTIPLE_INSTANCES_NOT_ALLOWED,[MessageContainer[MI_CGE_TITLE]]);

        {$ifdef nodevmodewin32}
        //I long since created a SEH hack to work around that
        if Config.Bool['main','developer_mode'] then AddStopMessage(RuEn(
          'Данная версия %0 (v%1),'#10#13+
          '  из-за изъяна в RTL Free Pascal 2.2.0,'#10#13+
          '  позволяет использовать режим разработчика'#10#13+
          '  только под Linux.',
          'This version of %0 (v%1),'#10#13+
          '  due to the bugs in the FreePascal 2.2.0 RTL,'#10#13+
          '  supports the developer mode only for Linux.'
          ), [
           MessageContainer[MI_CGE_TITLE],VersionToStr(VersionMajor, VersionMinor, BuildNumber)]);
        {$endif}

        {$ifdef buildmein}
        if MotherState.DeveloperMode then begin
          {$ifdef win32}
            GiveWarning('no_dev_mode_for_you-' + SystemSuffix,  MI_WARN_WARNING, MI_WARN_DEVELOPER_MODE_BUILTIN,
              [MessageContainer[MI_CGE_TITLE],VersionToStr(VersionMajor, VersionMinor, BuildNumber), MyInternalModuleName]);
          {$else}
            GiveWarning('no_dev_mode_for_you-' + SystemSuffix,  MI_WARN_WARNING, MI_WARN_DEVELOPER_MODE_WIN32_ONLY,
              [MotherState.OstID, {$ifdef windows}'Win64'{$else}{$ifdef darwin}'Mac OS X'{$else}'Linux'{$endif}{$endif}]);
          {$endif}
          MotherState.DeveloperMode:= false;
        end;
        {$endif}
        if MotherState.DeveloperMode and not MotherState.ProgramDirIsWriteable
          then AddStopMessage(MI_ERROR_DEVELOPER_MODE_NO_CAN_DO,[
                 AnsiToWide(MotherState.InstallPath) + MyAppName + '.ini', AnsiToWide(MotherState.InstallPath)]);

        if MotherState.RestartRequested
          then begin
            AddLog(RuEn('ПЕРЕЗАПУСК...','RESTARTING...'));
            MotherState.RestartRequested:=No;
            NowRestarting:=Yes;
          end
          else begin
            if MotherState.DebugMode then AddLog('First Stage Init took %0 seconds.',[DateTimeToP(Now() - CgeStartTime, 1)]);
          end;
        //AddLog(MessageContainer[MI_LOG_STARTED], [StrCurrentTime, MessageContainer.LID]);

        if MotherState.DebugMode then begin
          AddLog('Install path: %0',[AnsiToWide(MotherState.InstallPath)]);
{          AddLog('  Default install path: %0',[AnsiToWide(StdLocation)]);
          AddLog('  Alternate install path: %0',[AnsiToWide(AltLocation)]); }
          AddLog('Home path: %0',[AnsiToWide(MotherState.HomePath)]);
          AddLog('  Default home path: %0',[AnsiToWide(MotherState.StdHomePath)]);
          AddLog('  cache path: %0',[AnsiToWide(MotherState.CacheDir)]);
          AddLog('  sessions path: %0',[AnsiToWide(MotherState.SessionDir)]);
        end;

        ValidateWritePath(MotherState.CacheDir);
        ValidateWritePath(MotherState.SessionDir);

        {$ifdef windows}SetLastError(0);{$endif}

        InitCTimer;

        SetCurrentDir(MotherState.HomePath);


        w:= PervertedFormat(MessageContainer[MI_VERSION_INFO], [MessageContainer[MI_CGE_TITLE],
            VersionToStr(VersionMajor, VersionMinor, BuildNumber), CompileDate, CompileTime, CompilerVersion, CompileTarget]);
        if MotherState.DeveloperMode
          then w+= #10#13 + RuEn('  (режим разработчика)', '  (developer mode on)');
        AddLog(w);

        LogOSVersion;

        if MotherState.DebugMode then AddLog('Initializing the lockup guard...');
        LockupGuard:= TLockupGuard.Create; //менеджер автохаракири при зависании
        EngineObject.AddObj(LockupGuard);

       {$ifdef windows}
        if MotherState.DebugMode then AddLog('Error Mode was %0h', [IntToHex(clGetErrorMode(), 1)]);
        clSetErrorMode(SEM_NOGPFAULTERRORBOX or SEM_FAILCRITICALERRORS or SEM_NOOPENFILEERRORBOX);
        if MotherState.DebugMode then AddLog('Error Mode now %0h', [IntToHex(clGetErrorMode(), 1)]);
       {$endif}

       FormatSettings.DecimalSeparator:= '.';

       {$ifndef buildmein}
        if MotherState.DebugMode then AddLog('Activating the exception handling hack.');
        InitSehHack;
       {$endif}

       {$ifdef unix}
        SetSigtermHandler;
       {$endif}

       {$ifdef cpu32}
        MotherState.DebugInfopath:= MotherState.InstallPath + 'modules' + PathSlash + 'chentrah' + PathSlash + 'bin' + PathSlash;
        SetLength(LineInfoPaths, 1);
        LineInfoPaths[0]:= MotherState.DebugInfopath;
        {if MotherState.DeveloperMode or MotherState.DebugMode
          then InitLineInfo(nil); //preload the self-debug info}
        if not InitLineInfo(nil) then AddStopMessage(MI_DEBUG_INFO_NOT_FOUND, [LineInfoError]);
       {$endif}

        GetHotKeysFromConfig;
//        Containers:=TStringList.Create;
//        TexContainer:=TStringList.Create;
        
        if MotherState.PhysMemory < OsMinMem[MotherState.OS]
          then AddStopMessage(MI_NOT_ENOUGH_PHYSICAL_MEMORY, [
            OsName[MotherState.OS], OsMinMemRep[MotherState.OS],
            MotherState.PhysMemory, CgeString]);

        if MotherState.DebugMode then AddLog('Initializing resource manager...');
        InitResourceManager;

        //EngineObject.AddObj(TMd5List.Create);
        //Md5List:= EngineObject.Last as TMd5List;

        if MotherState.DebugMode then AddLog('Initializing Vampyre Imaging Library...');
        StartVampyre;

        LockupGuard.Checkup;

        if MotherState.DebugMode then AddLog('Creating the display/input manager...');
        LockupGuard.Guard(@SafeInitVideo, luc_VideoSystemStart);

        CgeEmptyCursor;

        Console.InitGL;

      {$ifdef windows}
        if MotherState.DebugMode then AddLog('Starting the pen tablet manager...');
        WinTabManager:= TWinTabmanager.Create;
        EngineObject.AddObj(WinTabManager);
      {$else}
        //In Linux, the tablet manager is created and owned by the window manager,
        //  due to deep integration with the main event loop
      {$endif}

        GamepadManager:= TGamepadManager.Create;
        EngineObject.AddObj(GamepadManager);

        MotherState.BlackoutStart:= Now();

        Console.Draw(No, Yes);

        if MotherState.DebugMode then AddLog('Loading the fixed font...');
        InitFixedFont;

        LoadQF;

        if MotherState.DebugMode then AddLog('Initializing sound...');

        LockupGuard.Guard(@SafeInitSound, luc_SoundSystemStart);

//        if DebugMode then AddLog('Loading the module...');
        EngineObject.AddObj(TModule.Create);
        Module:=EngineObject.Last as TModule;

      {$ifdef windows}
        if MotherState.DebugMode then AddLog('Deleting the splash screen...');
        KillSplashWindow;
      {$endif}

        InitFpsCounter;

        MotherState.LogoAnimationPlaying:= not MotherState.DebugMode and not NoHardwareAcceleration and not NowRestarting;
        MotherState.LogoAnimationStart:= Now();
        MotherState.BlackoutStart:= Now();
      Except
       {$ifdef windows}
        KillSplashWindow;
       {$endif}
        EverythingIsOk:=No;
        if not(ExceptObject is EFake) then Try
            Die(MI_DIED_INITIALIZING, ['']);
          Except
          End;
      End;
      KillSplashWindow;

      // ******** MAIN LOOP:

      if EverythingIsOk then try
        //if DebugMode then AddLog('Second Stage Init took %0 seconds.',[(Now() - SecondStageInitTime) * 86400.0]);
        if MotherState.DebugMode then AddLog('CGE initialization took %1:%0. Entering the main loop.',[DateTimeToP(Now() - CgeStartTime, 1), DateTimeToP(Now() - CgeStartTime, 0)]);
        MotherState.ProgramInitialized:= Yes;

        WindowManager.MainLoop;

       {$ifdef unix}
        if ReceivedSigterm then AddLog(RuEn(
          'Получен SIGTERM, закрываемся.',
          'SIGTERM received, performing shutdown.'));
       {$endif}
        if MotherState.DebugMode then AddLog('...exited the main loop.');
      Except
          //fake exceptions are required due to the
          // rather paranoidal reaction of the X server to closing the window -
          // it yells "fatal error!" and tries to kill our app on the spot...
          // so we'll cheat and just won't return from the callback function,
          // by raising a marked exception.
        if not (XServerBully) then begin
          EverythingIsOk:=No;
          if not (ExceptObject is EDying) then Try
            Die(MI_ERROR_UNCAUGHT_EXCEPTION);
          Except
          End;
        end
        else XServerBully:=No;// allows error messages again
      End;
//    until true;//not RestartRequested or not EverythingIsOk;

    PerformCgeShutdown;
  end;
  

  procedure SafeInitSound; cdecl;
  begin
    try
     {$ifdef windows}
      if Config.Bool[MotherState.OstId, 'use_directsound_not_openal']
        then SoundManager:=TDirectXSoundMan.Create
        else
     {$endif}
          SoundManager:=TOpenALSoundman.Create;
      EngineObject.AddObj(SoundManager);
    except
      pointer(SoundManager):= nil;
      if Config.bool['sound', 'allow_running_without']
      then
        AddWarning (MI_CAPTION_NOSOUND, StopDying(),[])
      else
        AddStopMessage(StopDying()); //clear the error messages buffer by copying them into a error screen.
    end;
  end;

  procedure SafeInitVideo; cdecl;
  begin
    WindowManager:= TWindowManager.Create;
    EngineObject.AddObj(WindowManager);
  end;


  procedure PerformCgeShutdown;
  begin
      // ******** CLOSING SEQUENCE:
      While EngineObject.Length > 0 do begin
        name:='<Unknown>';
        Try
          name:=EngineObject.Last.ClassName;
          if MotherState.DebugMode then AddLog('Destroying engine object #%0 (%1)', [EngineObject.Length, name]);
          if EngineObject.Last is TThread then begin
            (EngineObject.Last as TThread).Terminate;
            if MotherState.DebugMode then AddLog('  waiting for the thread to terminate...');
            (EngineObject.Last as TThread).WaitFor;
          end;
          EngineObject.Last.Free;
        Except
          Try
            Die('The %0.Free destructor has crashed.', [name]);
          Except
          End;
        End;
        EngineObject.Length := EngineObject.Length - 1;
      end;
//    pointer(LockupGuard):= nil; //it is released by now. Needed for KillMyself().
    KillSplashWindow;

    {$ifdef cpu32}
    TerminateSehHack;
    {$endif}

    AddLog(MI_LOG_END, [MessageContainer[MI_CGE_TITLE]]);

    if ((not EverythingIsOk) or MotherState.NowDying) and (WarningQueue.High >=0)
      then DisplayDyingYells;

    MessageContainer.Free;
    Config.Free;
    MotherState.Terminated:= true;
    DoneCriticalsection(MotherState.CriticalSection);

    if MotherState.RestartRequested
      then RestartMyself('--restart')
    else
      KillMyself();


//      if EverythingIsOk then Halt(0) else Halt(1);
//    TerminateProcess(
  end;

