unit MainForm;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, ASObjects, ASKernel, UxPanel, ASConsole,
  ASFcnExp, Generics.Defaults, Generics.Collections, Vcl.ExtCtrls,
  System.Actions, Vcl.ActnList, Vcl.Menus, Vcl.AppEvnts;

type
  TPopupMenu = class(UxPanel.TPopupMenu2)
  end;
  TAlgosimMainForm = class(TUxForm, IHelpfulControl)
    GlobalShortcuts: TActionList;
    aAbortAll: TAction;
    AppMenu: TPopupMenu;
    miDocumentation: TMenuItem;
    About1: TMenuItem;
    aAlgosim: TAction;
    N1: TMenuItem;
    aNewProgram: TAction;
    aOpenProgram: TAction;
    aNewConsole: TAction;
    Newprogram1: TMenuItem;
    Openprogram1: TMenuItem;
    N2: TMenuItem;
    Newconsole1: TMenuItem;
    N3: TMenuItem;
    mnuToolWindows: TMenuItem;
    miIdentifiers: TMenuItem;
    miTasks: TMenuItem;
    miWindowManager: TMenuItem;
    miVisualObjectsManager: TMenuItem;
    N4: TMenuItem;
    N5: TMenuItem;
    miCharacterBrowser: TMenuItem;
    miSettingsManager: TMenuItem;
    miOtherWindows: TMenuItem;
    N6: TMenuItem;
    Abortall1: TMenuItem;
    mnuLayouts: TMenuItem;
    aNextQuickLayout: TAction;
    aPrevQuickLayout: TAction;
    aDocEd: TAction;
    aIdentPanel: TAction;
    aTasks: TAction;
    aDocHtmlCompile: TAction;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure StartupTimerTimer(Sender: TObject);
    procedure aAbortAllExecute(Sender: TObject);
    procedure miDocumentationClick(Sender: TObject);
    procedure About1Click(Sender: TObject);
    procedure aAlgosimExecute(Sender: TObject);
    procedure aNewConsoleExecute(Sender: TObject);
    procedure aNewProgramExecute(Sender: TObject);
    procedure aOpenProgramExecute(Sender: TObject);
    procedure miIdentifiersClick(Sender: TObject);
    procedure miTasksClick(Sender: TObject);
    procedure miWindowManagerClick(Sender: TObject);
    procedure miVisualObjectsManagerClick(Sender: TObject);
    procedure miSettingsManagerClick(Sender: TObject);
    procedure miCharacterBrowserClick(Sender: TObject);
    procedure miOtherWindowsClick(Sender: TObject);
    procedure AppMenuPopup(Sender: TObject);
    procedure aNextQuickLayoutExecute(Sender: TObject);
    procedure aPrevQuickLayoutExecute(Sender: TObject);
    procedure aDocEdExecute(Sender: TObject);
    procedure aIdentPanelExecute(Sender: TObject);
    procedure aTasksExecute(Sender: TObject);
    procedure aDocHtmlCompileExecute(Sender: TObject);
  private
    FKernel: TASKernel;
    FConsoleJobs: TDictionary<TGUID, TConsoleForm>;
    FExitOnJobQuit: Boolean;
    FQuittingWindow: HWND;
    FOutputToActiveInstance: Boolean;
    FNoConsoleInput: Boolean;
    FNoModalWarnings: TGUID;
    FUxDebugTimer: TTimer;
    FKernelInitializing: Boolean;
    function KernelCommand(ACommand: Cardinal; const AJobID: TGUID;
      AParam1, AParam2, AParam3, AParam4: NativeInt): TCmdRes;
    procedure KernelOutput(const AWorkItem: TASKernel.TWorkQueueItem;
      AResult: TAlgosimObject);
    procedure KernelQueueChanged(AKernel: TASKernel; AState: TExecStateEx;
      AError: Boolean);
    procedure TextEditorAddInstance(Sender: TObject);
    procedure TableEditorAddInstance(Sender: TObject);
    procedure PreExitTermination;
    procedure QuittingDialogConstructed(Sender: TObject);
    procedure QuittingDialogDestroyed(Sender: TObject);
    function RunScript(const AScriptName: string;
      ASync: Boolean = False): TAlgosimObject;
    procedure ModalBegin(Sender: TObject);
    procedure ModalEnd(Sender: TObject);
    procedure ModalWarning(Sender: TObject);
    procedure UxDebugTimerTimer(Sender: TObject);
    procedure TextEditorDispBitmap(ABitmap: TBitmap);
    procedure ReloadLayouts(Sender: TObject);
  public
    procedure MakeKernelAsyncRequest(ASender: TConsoleForm; const AInput: string); overload;
    procedure MakeKernelAsyncRequest(const ASender: TGUID; const AInput: string); overload;
    procedure UpdateTasks(AState: TExecStateEx; AError: Boolean);
    function ContextHelp: Boolean;
    function StoragePath: string;
    property Kernel: TASKernel read FKernel;
    property NoConsoleInput: Boolean read FNoConsoleInput write FNoConsoleInput;
    property OutputToActiveInstance: Boolean read FOutputToActiveInstance write FOutputToActiveInstance;
  end;

var
  AlgosimMainForm: TAlgosimMainForm;

procedure AssertMainThread;
function IsURL(const S: string): Boolean;

implementation

uses
  Math, StrUtils, DateUtils, IOUtils, TextEditor, ASTableEditor, TDMB,
  ShellAPI, ASExecutionContext, ASSounds, ClientVisuals, ASVisualization,
  ClientDefs, FrontEndProps, Rux, ASSettings, AboutForm, GrphTest,
  ModalProtection, DevMode, DocEd, ASDoc, dlgmod;

{$R *.dfm}

type
  TLayoutMenuItem = class(TMenuItem)
  public var
    LayoutFile: string;
  public
    procedure Click; override;
  end;

procedure AssertMainThread;
begin
  if TThread.Current.ThreadID <> MainThreadID then
    raise Exception.Create('This facility can only be used from the main thread.');
end;

function IsURL(const S: string): Boolean;
const
  Protocols: array[0..8] of string = ('http://', 'https://', 'ftp://',
    'file:', 'gopher://', 'ws://', 'wss://', 'mailto:', 'www.');
var
  Protocol: string;
begin
  for Protocol in Protocols do
    if S.StartsWith(Protocol, True) then
      Exit(True);
  Result := False;
end;

procedure TAlgosimMainForm.StartupTimerTimer(Sender: TObject);
begin

  if Sender is TTimer then
  begin

    if TTimer(Sender).Tag = 0 then
    begin

      TTimer(Sender).Tag := 1;
      try
        if ParamStr(1).ToLower = '/grphtest' then
          ApplyLayout(LoadLayoutFromFile(GrphTestLayout), True)
        else
          ApplyLayout(LoadLayoutFromFile(GetDefaultLayoutPath), True);
      finally
        if ControlCount = 0 then
        begin
          var LConsole := TUxPanel.CreateWith('Console');
          InsertPanel(LConsole, 0);
          LConsole.FocusContent;
        end;
        if Assigned(FKernel) then
        begin
          FKernelInitializing := True;
          try
            FKernel.Init;
          finally
            FKernelInitializing := False;
          end;
        end;
      end;

    end
    else
    begin

      TTimer(Sender).Enabled := False;
      TTimer(Sender).OnTimer := nil;

      if ParamStr(1).ToLower = '/grphtest' then
        TGrphTest.RunTests
      else if ParamStr(1).ToLower = '/makeht' then
      begin
        TASDoc.CreateHypertextVersion;
        Application.Terminate;
      end
      else
      begin

        RunScript('startup.prg');

        if not TASSettings.GetSettingBool('NoQuickStartGuide') then
        begin
          var LDontAskAgain := False;
          if
            TD('Welcome to Algosim')
              .Text('Do you want to open the quick start guide?')
              .CustomIcon(Application.Icon)
              .YesNo
              .Verification('&Don’t ask again', @LDontAskAgain)
              .Execute(Self)
            = mrYes
          then
          begin
            const LTopic = 'Quick start guide';
            if TASDoc.TopicExists(LTopic) then
            begin
              var LMon := Screen.MonitorFromWindow(Handle);
              if Assigned(LMon) and (LMon.WorkareaRect.Width > ScaleValue(1400)) then
              begin
                WindowState := TWindowState.wsMaximized;
                try
                  ApplyLayout(LoadLayoutFromFile('Console and documentation'), True);
                except
                end;
              end;
              TDocBrowser.ShowDoc(LTopic, False);
            end
            else
              ShellExecute(Handle, 'open', 'https://algosim.org/doc/Quick start guide.html', nil, nil, SW_SHOWNORMAL);
          end;
          if LDontAskAgain then
            TASSettings.SetSetting('NoQuickStartGuide', True);
        end;

      end;

    end;

  end;

end;

function TAlgosimMainForm.StoragePath: string;
begin
  Result := TPath.GetHomePath + '\Rejbrand\Algosim';
end;

procedure TAlgosimMainForm.ReloadLayouts(Sender: TObject);
begin
  UxPanel.EnumLayouts;
  if (Sender is TMenuItem) and (TMenuItem(Sender).Parent = mnuLayouts) then
  begin
    var LInputs := TList<TInput>.Create;
    try
      var LInput := Default(TInput);
      LInput.Itype := INPUT_KEYBOARD;
      LInput.ki.wVk := VK_MENU;
      LInput.ki.dwFlags := 0;
      LInputs.Add(LInput);
      LInput.ki.wVk := Ord('A');
      LInput.ki.dwFlags := 0;
      LInputs.Add(LInput);
      LInput.ki.dwFlags := KEYEVENTF_KEYUP;
      LInputs.Add(LInput);
      LInput.ki.wVk := VK_MENU;
      LInput.ki.dwFlags := KEYEVENTF_KEYUP;
      LInputs.Add(LInput);
      LInput.ki.wVk := Ord('L');
      LInput.ki.dwFlags := 0;
      LInputs.Add(LInput);
      LInput.ki.dwFlags := KEYEVENTF_KEYUP;
      LInputs.Add(LInput);
      SendInput(LInputs.Count, LInputs.List[0], SizeOf(TInput));
    finally
      LInputs.Free;
    end;
  end;
end;

function TAlgosimMainForm.RunScript(const AScriptName: string; ASync: Boolean):
  TAlgosimObject;
begin
  Result := nil;
  var fn := TPath.Combine(StoragePath, AScriptName);
  if Assigned(FKernel) and FileExists(fn) then
  begin
    var s := TFile.ReadAllText(fn, TEncoding.UTF8).Trim;
    if not s.IsEmpty then
      if ASync then
        Result := FKernel.Evaluate(s)
      else
        FKernel.EvaluateAsync(s);
  end;
end;

procedure TAlgosimMainForm.aAbortAllExecute(Sender: TObject);
begin
  if Assigned(FKernel) then
    FKernel.AbortAll;
end;

procedure TAlgosimMainForm.aAlgosimExecute(Sender: TObject);
begin
  if Screen.ActiveCustomForm is TUxForm then
  begin
    const LFrm = Screen.ActiveCustomForm as TUxForm;
    LFrm.ShowAppMenu;
  end;
end;

procedure TAlgosimMainForm.About1Click(Sender: TObject);
begin
  TAboutFrm.ShowAboutForm;
end;

procedure TAlgosimMainForm.aDocEdExecute(Sender: TObject);
begin

  if not IsDevMode then
    Exit;

  var LDocEd := TDocEdFrm.Create(Self);

  LDocEd.Kernel := TASKernel.Create;
  LDocEd.OwnsKernel := True;
  LDocEd.LoadIdents;
  LDocEd.Show;

end;

procedure TAlgosimMainForm.aDocHtmlCompileExecute(Sender: TObject);
begin

  if not IsDevMode then
    Exit;

  ShellExecute(
    Handle,
    nil,
    PChar(Application.ExeName),
    '/makeht',
    nil,
    SW_SHOWNORMAL
  );

end;

procedure TAlgosimMainForm.aIdentPanelExecute(Sender: TObject);
begin
  GotoPanel(TIdentifierForm);
end;

procedure TAlgosimMainForm.aNewConsoleExecute(Sender: TObject);
begin
  TUxForm.CreateNewForm(TConsoleForm);
end;

procedure TAlgosimMainForm.aNewProgramExecute(Sender: TObject);
begin
  TUxForm.CreateNewForm(TProgramEditorForm);
end;

procedure TAlgosimMainForm.aNextQuickLayoutExecute(Sender: TObject);
begin
  if Length(QuickLayouts) > 0 then
  begin
    QuickLayoutIdx := Succ(QuickLayoutIdx) mod Length(QuickLayouts);
    ApplyLayout(LoadLayoutFromFile(GetLayoutPath(QuickLayouts[QuickLayoutIdx])), True);
  end;
end;

procedure TAlgosimMainForm.aOpenProgramExecute(Sender: TObject);
begin
  var LFrm := TUxForm.CreateNewForm<TProgramEditorForm>;
  LFrm.OpenFileDialog;
end;

procedure TAlgosimMainForm.AppMenuPopup(Sender: TObject);
begin
  mnuLayouts.Clear;
  for var LPreset in UxPanel.CachedLayouts do
  begin
    if not FileExists(LPreset) then
      Continue;
    var mi := TLayoutMenuItem.Create(mnuLayouts);
    const LName = TPath.GetFileNameWithoutExtension(LPreset);
    mnuLayouts.Add(mi);
    mi.Caption := LName;
    mi.LayoutFile := LPreset;
    mi.Hint := Format('Applies the “%s” layout.', [LName]);
  end;
  mnuLayouts.NewBottomLine;
  var mi := TMenuItem.Create(mnuLayouts);
  mi.Caption := 'Reload layouts';
  mi.Hint := 'Searches for new layout files in the file system.';
  mi.OnClick := ReloadLayouts;
  mnuLayouts.Add(mi);
end;

procedure TAlgosimMainForm.aPrevQuickLayoutExecute(Sender: TObject);
begin
  if Length(QuickLayouts) > 0 then
  begin
    QuickLayoutIdx := (QuickLayoutIdx + Length(QuickLayouts) - 1) mod Length(QuickLayouts);
    ApplyLayout(LoadLayoutFromFile(GetLayoutPath(QuickLayouts[QuickLayoutIdx])), True);
  end;
end;

procedure TAlgosimMainForm.aTasksExecute(Sender: TObject);
begin
  GotoPanel(TTaskListForm);
end;

function TAlgosimMainForm.ContextHelp: Boolean;
begin
  TDocBrowser.ShowDoc('Algosim', True, sbIndex);
  Result := True;
end;

procedure TAlgosimMainForm.FormCloseQuery(Sender: TObject;
  var CanClose: Boolean);
begin
  if Assigned(FKernel) and FKernel.AsyncRunning then
  begin
    CanClose := TD
      .Text('Do you want to terminate the current computation and remove any queued jobs?')
      .YesNoCancel
      .Execute = mrYes;
    if CanClose then
    begin
      FKernel.AbortAll;
      PreExitTermination;
    end;
    CanClose := False;
    Exit;
  end;
  var Res := RunScript('exit.prg', True);
  CanClose := not Assigned(Res) or Res.ToBoolean;
end;

procedure TAlgosimMainForm.PreExitTermination;
begin
  FExitOnJobQuit := True;
  FQuittingWindow := 0;
  with TTaskDialog.Create(Self) do
    try
      Caption := Self.Caption;
      Title := 'Waiting for the current computation to be aborted...';
      MainIcon := tdiNone;
      CommonButtons := [];
      with Buttons.Add do
      begin
        Caption := 'Terminate Algosim immediately';
        ModalResult := mrAbort;
      end;
      with Buttons.Add do
      begin
        Caption := 'Cancel';
        ModalResult := mrCancel;
      end;
      Flags := Flags + [tfPositionRelativeToWindow];
      OnDialogConstructed := QuittingDialogConstructed;
      OnDialogDestroyed := QuittingDialogDestroyed;
      Application.ModalStarted;
      try
        if Execute and (ModalResult = mrAbort) then
          ExitProcess(1)
        else
          FExitOnJobQuit := False;
      finally
        Application.ModalFinished;
      end;
    finally
      FQuittingWindow := 0;
      Free;
    end;
end;

procedure TAlgosimMainForm.QuittingDialogConstructed(Sender: TObject);
begin
  if Sender is TTaskDialog then
    FQuittingWindow := TTaskDialog(Sender).Handle;
end;

procedure TAlgosimMainForm.QuittingDialogDestroyed(Sender: TObject);
begin
  FQuittingWindow := 0;
end;

procedure TAlgosimMainForm.FormCreate(Sender: TObject);
begin

  CreateMutex(nil, False, 'Rejbrand Algosim');

  {$IFDEF DEBUG}
  ReportMemoryLeaksOnShutdown := True;
  Caption := Caption + ' [DEBUG]';
  {$ENDIF}

  AppMenu.Tag := STARTMENU_TAG;
  TUxForm.AppMenu := AppMenu;
  TUxForm.AppMenuCaption := 'Algosim';

  FConsoleJobs := TDictionary<TGUID, TConsoleForm>.Create;

  TTextEditor.OnAddInstance := TextEditorAddInstance;
  TTextEditor.TextEditorDispBitmapProc := TextEditorDispBitmap;
  TASTableEditor.OnAddInstance := TableEditorAddInstance;

  FKernel := TASKernel.Create;
  FKernel.OnCommand := KernelCommand;
  FKernel.OnOutput := KernelOutput;
  FKernel.OnQueueChanged := KernelQueueChanged;
  FKernel.SaveHistResults := True;
  try
    FKernel.PropStore.AddSubstore(TFrontEndProperties.Create(Self));
  except
    on E: Exception do
      TD('Algosim was unable to create the front-end property store.')
        .Details(E.Message)
        .Info
        .Execute;
  end;

  TAppModalProtection.OnWarning := ModalWarning;

  var LAppEvents := TApplicationEvents.Create(Self);
  LAppEvents.OnModalBegin := ModalBegin;
  LAppEvents.OnModalEnd := ModalEnd;

//  begin
//    var L := TUxLayoutStack.Create(uxoHorizontal);
//    try
//      L.Items.Add(TUxLayoutPanel.Create('Console'));
//      var S := TUxLayoutStack.Create(uxoVertical);
//      S.Items.Add(TUxLayoutPanel.Create('Identifiers'));
//      S.Items.Add(TUxLayoutPanel.Create('Console'));
//      S.Items.Add(TUxLayoutPanel.Create('Window manager'));
//      S.Items.Add(TUxLayoutPanel.Create('Clocks'));
//      L.Items.Add(S);
//      ApplyLayout(L)
//    finally
//      L.Free;
//    end;
//  end;

//  var LPanel := TUxPanel.CreateWith(TConsoleForm);
//  InsertPanel(LPanel, 0);


  Rux.TUx.ThemeUpdate(TUxTheme.Create(TASSettings.GetSettingColor('ThemeColor', UxPanel.GDefaultColor)));
  Rux.TUx.RegisterCallback(
    procedure
    begin
      if not Rux.ColorSettingChanging then
      begin
        Rux.ColorChanging := True;
        try
          TASSettings.SetSetting('ThemeColor', TUx.ThemeData.Accent0);
        finally
          Rux.ColorChanging := False;
        end;
      end;
    end
  );

  UxPanel.EnumLayouts;

  DefaultLayoutName := TASSettings.GetSettingString('DefaultLayout', 'Console (only)');
  QuickLayouts := TASSettings.GetSettingString('QuickLayouts').Split([';']);
  TidyQuickLayouts;

  var LStartupTimer := TTimer.Create(Self);
  LStartupTimer.Interval := 50;
  LStartupTimer.OnTimer := StartupTimerTimer;

  if TASSettings.GetSettingBool('StartMaximized') then
    WindowState := TWindowState.wsMaximized;

end;

procedure TAlgosimMainForm.FormDestroy(Sender: TObject);
begin
  FreeAndNil(FKernel);
  FreeAndNil(FConsoleJobs);
end;

function TAlgosimMainForm.KernelCommand(ACommand: Cardinal; const AJobID: TGUID;
  AParam1, AParam2, AParam3, AParam4: NativeInt): TCmdRes;
begin

  if Kernel = nil then
    Exit(TCmdRes.Unhandled);

  AssertMainThread;

  case ACommand of
    CLIENT_COMMAND_SYSTEM:
      begin
        Result := ShellExecute(Handle, nil, PChar(AParam1), PChar(AParam2), nil, SW_SHOWNORMAL) > 32;
      end;
    CLIENT_COMMAND_CLS:
      begin
        if AParam1 = 0 then
        begin
          var LConsoleForm := TConsoleForm(nil);
          if
            Assigned(FConsoleJobs)
              and
            FConsoleJobs.TryGetValue(AJobID, LConsoleForm)
              and
            TConsoleForm.VerifyInstance(LConsoleForm)
          then
            LConsoleForm.Console.CliClear
          else if TConsoleForm.ActiveInstance <> nil then
            TConsoleForm.ActiveInstance.Console.CliClear;
        end
        else
          TTextBufferViewer.TextBufferClear(PChar(Aparam1));
        Result := True;
      end;
    CLIENT_COMMAND_PRINT:
      begin
        if AParam1 = 0 then
        begin
          var LConsoleForm := TConsoleForm(nil);
          if
            Assigned(FConsoleJobs)
              and
            FConsoleJobs.TryGetValue(AJobID, LConsoleForm)
              and
            TConsoleForm.VerifyInstance(LConsoleForm)
          then
            LConsoleForm.Console.CliWrite(PChar(AParam2), PChar(AParam3))
          else if TConsoleForm.ActiveInstance <> nil then
          begin
            var LConsole := TConsoleForm.ActiveInstance.Console;
            if Assigned(LConsole) then
            begin
              const LMakeUndoRoot = FKernelInitializing and not LConsole.TextFile.FileModified;
              LConsole.CliWrite(PChar(AParam2), PChar(AParam3));
              if LMakeUndoRoot then
              begin
                LConsole.TextFile.FileModified := False;
                TConsoleForm.ActiveInstance.UpdatePanelCaption;
              end;
            end;
          end;
        end
        else
          TTextBufferViewer.TextBufferAppend(PChar(Aparam1), PChar(AParam2));
        Result := True;
      end;
    CLIENT_COMMAND_EXIT:
      begin
        SendMessage(GetCapture, WM_CANCELMODE, 0, 0);
        PostMessage(Handle, WM_CLOSE, 0, 0);
        Result := True;
      end;
    CLIENT_COMMAND_PLAYSOUND:
      begin
        PASSound(AParam1).Play;
        Result := True;
      end;
    CLIENT_COMMAND_STOPSOUND:
      begin
        TASSound.Stop;
        Result := True;
      end;
    CLIENT_COMMAND_DISPLAY, CLIENT_COMMAND_WINDOW:
      begin
        CreateFrame(TObject(AParam1) as TAlgosimObject, PChar(AParam2));
        Result := True;
      end;
    CLIENT_COMMAND_SETDIAGRAM:
      begin
        if PChar(AParam1) = '@popup' then
          TVisualization.Popups := True
        else if PChar(AParam1) = '@panel' then
          TVisualization.Popups := False
        else
        begin
          TVisualization.Diagram := PChar(AParam1);
          var dummy: Boolean;
          if not TVisualization.Diagram.IsEmpty and (AParam2 <> 0) then
            PAlgosimReference(AParam2)^ := TVisualization.GetDiagram(dummy).Diagram.CreateReference;
        end;
        Result := True;
      end;
    CLIENT_COMMAND_SETSCENE:
      begin
        if PChar(AParam1) = '@popup' then
          TVisualization.Popups := True
        else if PChar(AParam1) = '@panel' then
          TVisualization.Popups := False
        else
        begin
          TVisualization.Scene := PChar(AParam1);
          var dummy: Boolean;
          if not TVisualization.Scene.IsEmpty and (AParam2 <> 0) then
            PAlgosimReference(AParam2)^ := TVisualization.GetScene(dummy).Scene.CreateReference;
        end;
        Result := True;
      end;
    CLIENT_COMMAND_CLEARDIAGRAM:
      begin
        TVisualization.Diagram := PChar(AParam1);
        var dummy: Boolean;
        var LCtl := TVisualization.GetDiagram(dummy);
        LCtl.ClearDiagram;
        if AParam2 <> 0 then
          PAlgosimReference(AParam2)^ := LCtl.Diagram.CreateReference;
        Result := True;
      end;
    CLIENT_COMMAND_CLEARSCENE:
      begin
        TVisualization.Scene := PChar(AParam1);
        var dummy: Boolean;
        var LCtl := TVisualization.GetScene(dummy);
        LCtl.ClearScene;
        if AParam2 <> 0 then
          PAlgosimReference(AParam2)^ := LCtl.Scene.CreateReference;
        Result := True;
      end;
    CLIENT_COMMAND_ADDVISUAL:
      begin
        TVisualization.Visualize(TObject(AParam1) as TVisual, PAlgosimReference(AParam2));
        Result := True;
      end;
    CLIENT_COMMAND_REMOVEVISUAL:
      begin
        TVisualization.RemoveVisual(PGUID(AParam1)^);
        Result := True;
      end;
    CLIENT_COMMAND_CONFIGVISUAL:
      begin
        TVisualization.ConfigVisual(PGUID(AParam1)^, TObject(AParam2) as TAlgosimStructure);
        Result := True;
      end;
    CLIENT_COMMAND_EXPORTVISUAL:
      begin
        TVisualization.ExportVisual(PGUID(AParam1)^, PChar(AParam2), TObject(AParam3) as TAlgosimStructure);
        Result := True;
      end;
    CLIENT_COMMAND_QUERYVISOBJ:
      begin
        var R := TObject(AParam1) as TVisualRec;
        Result := TVisualization.QueryVisual(R);
      end;
    CLIENT_COMMAND_ENUMVISOBJ:
      begin
        var L := TObject(AParam1) as TList<TGUID>;
        TVisualization.EnumVisuals(L);
        Result := True;
      end;
    CLIENT_COMMAND_ENUMSETTINGS:
      begin
        var L := TObject(AParam1) as TList<string>;
        L.AddRange(TASSettings.KnownSettingNames);
        Result := True;
      end;
    CLIENT_COMMAND_QUERYSETTING:
      begin
        var S := PSetting(AParam1);
        var S2 := Default(TSetting);
        Result := Assigned(S) and TASSettings.TryGetSetting(S^.Name, S2);
        S^ := S2;
      end;
    CLIENT_COMMAND_APPLYSETTING:
      begin
        var S := string(PChar(AParam1));
        var LType := TSettingDataType(AParam2);
        var LRes := True;
        if not InRange(Ord(LType), Ord(Low(TSettingDataType)), Ord(High(TSettingDataType))) then
          LRes := False
        else
        begin
          case LType of
            sdtBoolean:
              TASSettings.SetSetting(S, Boolean(AParam3));
            sdtInteger:
              TASSettings.SetSetting(S, Integer(AParam3));
            sdtColor:
              TASSettings.SetSetting(S, TColor(AParam3));
            sdtDouble:
              TASSettings.SetSetting(S, PDouble(AParam3)^);
            sdtString:
              TASSettings.SetSetting(S, string(PChar(AParam3)));
            sdtDateTime:
              TASSettings.SetSetting(S, PDateTime(AParam3)^);
          else
            LRes := False;
          end;
        end;
        Result := LRes;
      end;
    CLIENT_COMMAND_ENUMPANELS:
      begin
        var L := TObject(AParam1) as TList<TPanelRec>;
        L.AddRange(TUxPanel.PanelRecArr);
        Result := True;
      end;
    CLIENT_COMMAND_CLOSEPANEL:
      begin
        var p: PGUID;
        if AParam1 <> 0 then
        begin
          p := PGUID(AParam1);
          if Assigned(TUxPanel.Instances) then
            for var LInstance in TUxPanel.Instances do
              if LInstance.GUID = p^ then
              begin
                LInstance.Close;
                Break;
              end;
        end;
        Result := True;
      end;
    CLIENT_COMMAND_SAVEOBJECT:
      begin
        if TObject(AParam1) is TAlgosimReference then
          TVisualization.ExportVisual(TAlgosimReference(AParam1).GUID, '', TObject(AParam3) as TAlgosimStructure)
        else
          (TObject(AParam1) as TAlgosimObject).SaveToFile(Self);
        Result := True;
      end;
    CLIENT_COMMAND_FRONTENDEXCEPTION:
      begin
        TThread.ForceQueue(
          nil,
          procedure
          begin
            if AParam1 = 1 then
              raise EClientException.Create('(Delayed) front-end exception triggered programmatically.')
            else
              raise Exception.Create('(Delayed) front-end exception triggered programmatically.');
          end
        );
        Result := True;
      end;
    CLIENT_COMMAND_GETFPCW:
      begin
        if AParam1 <> 0 then
        begin
          PWord(AParam1)^ := Get8087CW;
          Result := True;
        end
        else
          Result := False;
      end;
    CLIENT_COMMAND_SETFPCW:
      begin
        var CW: Word := ClientFPUCW;
        asm
          FNCLEX
          FLDCW  CW
        end;
        Result := True;
      end;
    CLIENT_COMMAND_UXPANELDEBUG:
      begin
        TUxContainer.DebugMode := AParam1 <> 0;
        {$IFDEF DEBUG}
          if (AParam1 <> 0) and not Assigned(FUxDebugTimer) then
          begin
            FUxDebugTimer := TTimer.Create(Self);
            FUxDebugTimer.OnTimer := UxDebugTimerTimer;
          end;
          if AParam1 = 0 then
            FreeAndNil(FUxDebugTimer);
        {$ENDIF}
        Result := True;
      end;
    CLIENT_COMMAND_UXOBJECTS:
      begin
        if (AParam1 <> 0) and Assigned(TUxForm.Instances) then
          PInteger(AParam1)^ := TUxForm.Instances.Count;
        if (AParam2 <> 0) and Assigned(TUxContainer.Instances) then
          PInteger(AParam2)^ := TUxContainer.Instances.Count;
        if (AParam3 <> 0) and Assigned(TUxPanel.Instances) then
          PInteger(AParam3)^ := TUxPanel.Instances.Count;
        Result := True;
      end;
    CLIENT_COMMAND_DEBUGTEXT:
      begin
        if AParam1 <> 0 then
        begin
          var S := string(PChar(AParam1));
          TDbgLogForm.DoLog(S, nil);
          Result := True;
        end
        else
          Result := False;
      end;
    CLIENT_COMMAND_DEBUGCLEAR:
      begin
        TDbgLogForm.ClearLog;
        Result := True;
      end;
    CLIENT_COMMAND_GETLOG:
      begin
        if (AParam1 <> 0) and (TObject(AParam1) is TList<string>) then
        begin
          var L := TList<string>(AParam1);
          L.AddRange(TDbgLogForm.GetLog);
          Result := True;
        end
        else
          Result := False;
      end;
    CLIENT_COMMAND_APPLYLAYOUT:
      begin
        if AParam1 <> 0 then
        begin
          var S := string(PChar(AParam1)).Trim;
          try
            ApplyLayout(LoadLayoutFromFile(S), True);
            Result := True;
          except
            on E: Exception do
            begin
              TDbgLogForm.DoLog('CLIENT_COMMAND_APPLYLAYOUT: ', E);
              Result := False;
            end;
          end;
        end
        else
          Result := False;
      end;
  else
    Result := TCmdRes.Unhandled;
  end;

end;

procedure TAlgosimMainForm.KernelOutput(const AWorkItem: TASKernel.TWorkQueueItem;
  AResult: TAlgosimObject);
begin

  if FKernel = nil then
    Exit;

  AssertMainThread;

  try

    var LOutputted := False;

    var LConsoleForm := TConsoleForm(nil);
    if
      Assigned(FConsoleJobs)
        and
      FConsoleJobs.TryGetValue(AWorkItem.UID, LConsoleForm)
    then
    begin
      FConsoleJobs.Remove(AWorkItem.UID);
      if Assigned(LConsoleForm) and TConsoleForm.VerifyInstance(LConsoleForm) then
      begin
        LConsoleForm.KernelOutput(AWorkItem.Kernel, AResult);
        LOutputted := True;
      end
      else if TConsoleForm.ActiveInstance <> nil then
      begin
        TConsoleForm.ActiveInstance.KernelOutput(AWorkItem.Kernel, AResult);
        LOutputted := True;
      end;
    end
    else if OutputToActiveInstance then
    begin
      if TConsoleForm.ActiveInstance <> nil then
      begin
        TConsoleForm.ActiveInstance.KernelOutput(AWorkItem.Kernel, AResult);
        LOutputted := True;
      end;
    end;

    if IsGalleryObject(AResult) then
      CreateFrame(AResult, '')
    else if AResult is TAlgosimReference then
      TVisualization.ShowVisCtl(TAlgosimReference(AResult).GUID)
    else if not LOutputted and Assigned(AResult) and not (AResult is TAlgosimNullObject) then
      CreateFrame(AResult, '');

  finally

    TIdentifierForm.RefreshLists;

    if FExitOnJobQuit then
    begin
      if FQuittingWindow <> 0 then
        PostMessage(FQuittingWindow, WM_CLOSE, 0, 0);
      FExitOnJobQuit := False;
      Close;
    end;

  end;

end;

procedure TAlgosimMainForm.KernelQueueChanged(AKernel: TASKernel;
  AState: TExecStateEx; AError: Boolean);
begin
  UpdateTasks(AState, AError);
  TUxForm.AppBarStatus := Kernel.State.ToString;
  TUxForm.UpdateAppBars;
end;

procedure TAlgosimMainForm.MakeKernelAsyncRequest(const ASender: TGUID;
  const AInput: string);
begin
  var LConsoleForm := TConsoleForm(nil);
  for var LConsoleFormInst in TConsoleForm.Instances do
    if LConsoleFormInst.GUID = ASender then
    begin
      LConsoleForm := LConsoleFormInst;
      Break;
    end;
  MakeKernelAsyncRequest(LConsoleForm, AInput);
end;

procedure TAlgosimMainForm.MakeKernelAsyncRequest(ASender: TConsoleForm;
  const AInput: string);
begin

  if FNoConsoleInput then
    Exit;

  if Self = nil then
    Exit;

  if Application.Terminated then
    Exit;

  if FKernel = nil then
    Exit;

  const LJob = FKernel.EvaluateAsync(AInput);

  if Assigned(FConsoleJobs) then
    FConsoleJobs.Add(LJob.UID, ASender);

end;

procedure TAlgosimMainForm.miCharacterBrowserClick(Sender: TObject);
begin
  GotoPanel(TCharacterBrowser);
end;

procedure TAlgosimMainForm.miDocumentationClick(Sender: TObject);
begin
  try
    TDocBrowser.ShowDoc('Algosim', True, sbIndex);
  except
    on E: EDocException do
      TD('Cannot open the documentation.')
      .Text('Try reinstalling Algosim with the full on-line documentation.')
      .Text('Alternatively, you can find the documentation on the WWW:')
      .Text('<a href="https://algosim.org/doc">https://algosim.org/doc</a>')
      .CustomIcon(Application.Icon)
      .Hypertext
      .OK
      .Details(E.Message + #13#10'Location: ' + TASDoc.Path, dpFooter, 'Technical details', False)
      .Execute;
  end;
end;

procedure TAlgosimMainForm.miIdentifiersClick(Sender: TObject);
begin
  GotoPanel(TIdentifierForm);
end;

procedure TAlgosimMainForm.miOtherWindowsClick(Sender: TObject);
begin
  TUxForm.CreateNewForm;
end;

procedure TAlgosimMainForm.miSettingsManagerClick(Sender: TObject);
begin
  GotoPanel(TSettingsForm);
end;

procedure TAlgosimMainForm.miTasksClick(Sender: TObject);
begin
  GotoPanel(TTaskListForm);
end;

procedure TAlgosimMainForm.miVisualObjectsManagerClick(Sender: TObject);
begin
  GotoPanel(TVisMgrForm);
end;

procedure TAlgosimMainForm.miWindowManagerClick(Sender: TObject);
begin
  GotoPanel(TWndMgrForm);
end;

procedure TAlgosimMainForm.ModalBegin(Sender: TObject);
begin
  TAppModalProtection.ModalBegun;
end;

procedure TAlgosimMainForm.ModalEnd(Sender: TObject);
begin
  TAppModalProtection.ModalEnded;
end;

procedure TAlgosimMainForm.ModalWarning(Sender: TObject);

  function SuppressWarningForThisJob: Boolean;
  begin
    Result := (Length(FKernel.WorkQueue) > 0) and (FKernel.WorkQueue[0].UID = FNoModalWarnings)
      and (FNoModalWarnings <> TGUID.Empty);
  end;

begin

  if Assigned(FKernel) and FKernel.AsyncRunning and not SuppressWarningForThisJob then
  begin

    var LDontAskAgain := False;

    if TD
      .WindowCaption('Program Modality Warning')
      .Text('It seems like the currently executing program displays many modal dialogs.')
      .Text('This may prevent you from terminating the program. Do you wish to terminate it now?')
      .Warning
      .YesNo
      .Verification('Don''t ask again while executing this program.', @LDontAskAgain)
      .Execute = mrYes
    then
      FKernel.AbortAll
    else if LDontAskAgain then
      if (Length(FKernel.WorkQueue) > 0) and (FKernel.WorkQueue[0].UID <> TGUID.Empty) then
        FNoModalWarnings := FKernel.WorkQueue[0].UID;

  end;

end;

procedure TAlgosimMainForm.TableEditorAddInstance(Sender: TObject);
begin
  if Sender is TASTableEditor then
  begin
    const LEditor = TASTableEditor(Sender);
    LEditor.UseRuxThemes := True;
  end;
end;

procedure TAlgosimMainForm.TextEditorAddInstance(Sender: TObject);
begin
  if Sender is TTextEditor then
  begin
    const LEditor = TTextEditor(Sender);
    LEditor.LineSpacing := 0;
    LEditor.UseRuxThemes := True;
    LEditor.RuxAccent := True;
    LEditor.AutoReplace := True;
  end;
end;

procedure TAlgosimMainForm.TextEditorDispBitmap(ABitmap: TBitmap);
begin
  var LImgViewer := TUxForm.CreateNewForm<TImageViewerForm>;
  LImgViewer.Bitmap := ABitmap;
end;

procedure TAlgosimMainForm.UpdateTasks(AState: TExecStateEx; AError: Boolean);
begin
  if Assigned(FKernel) then
  begin
    const WQ = FKernel.WorkQueue;
    aAbortAll.Enabled := Length(WQ) >= 1;
    TTaskListForm.ClassUpdateTasks(WQ, FKernel, AState, AError);
  end;
end;

var
  LOldFormCount: Integer = -1;
  LOldContainerCount: Integer = -1;
  LOldPanelCount: Integer = -1;

procedure TAlgosimMainForm.UxDebugTimerTimer(Sender: TObject);
begin
  if
    Assigned(TUxForm.Instances)
      and
    Assigned(TUxContainer.Instances)
      and
    Assigned(TUxPanel.Instances)
  then
  begin
    var LNewFormCount := TUxForm.Instances.Count;
    var LNewContainerCount := TUxContainer.Instances.Count;
    var LNewPanelCount := TUxPanel.Instances.Count;
    if
      (LNewFormCount <> LOldFormCount)
        or
      (LNewContainerCount <> LOldContainerCount)
        or
      (LNewPanelCount <> LOldPanelCount)
    then
    begin
      TDbgLogForm.DoLog(
        Format(
          '%6d %6d %6d',
          [
            LNewFormCount,
            LNewContainerCount,
            LNewPanelCount
          ]
        )
      );
      LOldFormCount := LNewFormCount;
      LOldContainerCount := LNewContainerCount;
      LOldPanelCount := LNewPanelCount;
    end;
  end;
end;

{ TLayoutMenuItem }

procedure TLayoutMenuItem.Click;
begin
  inherited;
  if Self = nil then
    Exit;
  const PM = GetParentMenu;
  if not (PM is TPopupMenu) then
    Exit;
  const PC = TPopupMenu(PM).PopupComponent;
  if not (PC is TControl) then
    Exit;
  const PF = GetParentForm(TControl(PC));
  if not (PF is TUxForm) then
    Exit;
  const PUF = TUxForm(PF);
  PUF.ApplyLayout(LoadLayoutFromFile(LayoutFile), True);
end;

initialization
  try
    GThemedBorders := TASSettings.GetSettingBool('ThemedBorders', True);
  except
  end;

end.
