Unit Dispatcher_iface;

{ Copyright (C) 2006-2016 by Shleps
  This library is a part of ChaosWarehouse project.

  It is free software; you can redistribute it and/or modify it
  under the terms of the GNU Library General Public License v2

  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 Library General Public License
  for more details.

  Usage example:

  Format of network commands
  any number of commands separated by #10 or #13#10
  disconnect#10




applicable commands are
<worker name>::QueueCommand:<command>
<worker name>::QueueFunction:<command>
<worker name>::Loop:<command>
<worker name>::Announce worker:<name>:<role>:<connection>:<connection addr>|<comma separated command list>
<worker name>::Denounce worker:<worker name>
<worker name>::Stat:<sender>:<comma separated stat list>
<worker name>::system:<shell command>
<worker name>::shell:<shell command>');


ToDo:
  implement parallel dispatch+
  implement RqDir connection method
  fix high cpu load while waiting of exec in Linux
  implement "pause-continue" with saving current queue to disk and restart application

  add sDB functions
  Add "dispatcher+" role

}

{$mode objfpc}{$H+}


Interface

Uses
  Classes, SysUtils, FileUtil, Process, Pipes, LResources, Forms, Controls,
  Graphics, Dialogs, StdCtrls, ExtCtrls, Buttons, ShellCtrls, ComCtrls,
  MaskEdit, Spin, Grids, LCLType, ValEdit, ClipBrd,
  {$IFDEF LINUX}CThreads,{$ENDIF}  ShlepsUtils, Dispatcher, Types, blcksock, synsock, sDB, eventlog;

Type

  TGUIMode=(gmLocal,gmRemote);

  { TDispatcherForm }

  TDispatcherForm = Class(TForm)
    BriefFullToggle: TToggleBox;
    ConnectButton: TButton;
    PasteCmdsButton: TSpeedButton;
    ModuleFilterEdit: TEdit;
    LockListBox: TListBox;
    Panel13: TPanel;
    Splitter10: TSplitter;
    UpdateBitBtn: TBitBtn;
    CommandEdit: TEdit;
    CommandResult: TEdit;
    commandList: TListBox;
    CTXCacheListBox: TListBox;
    CTXDirtyCacheListBox: TListBox;
    CTXListBox: TListBox;
    CurrentCommandEdit: TEdit;
    DisplayConfigBitBtn: TBitBtn;
    ConfigMemo: TMemo;
    CommandFilterEdit: TEdit;
    WaiterListBox: TListBox;
    ModifyConfigBitBtn: TBitBtn;
    FilterKeepButton: TSpeedButton;
    KeyEdit: TEdit;
    FilterOutButton: TSpeedButton;
    GroupBox3: TGroupBox;
    InternalNameLabel: TLabel;
    InternalNameLabel1: TLabel;
    AddrLabel: TLabel;
    FOpenDialog: TOpenDialog;
    EvalMemo: TMemo;
    PageControl3: TPageControl;
    Panel11: TPanel;
    Panel14: TPanel;
    Panel15: TPanel;
    Panel16: TPanel;
    GroupBox11: TGroupBox;
    GroupBox4: TGroupBox;
    GroupBox5: TGroupBox;
    GroupBox6: TGroupBox;
    HistoryStringGrid: TStringGrid;
    Label1: TLabel;
    DescrLabel: TLabel;
    CommandTimeLabel: TLabel;
    Panel7: TPanel;
    Memo1: TMemo;
    Memo2: TMemo;
    PageControl2: TPageControl;
    Panel12: TPanel;
    HistoryLengthEdit: TSpinEdit;
    Panel9: TPanel;
    SaveCmdsButton: TSpeedButton;
    SearchEdit: TEdit;
    FindByAddrButton: TSpeedButton;
    SearchByChainButton: TSpeedButton;
    SetCursorButton: TSpeedButton;
    ImportObjectButton: TSpeedButton;
    DropQueueButton: TSpeedButton;
    EvalButton: TSpeedButton;
    EnumAttrsButton: TSpeedButton;
    PureTimeButton: TSpeedButton;
    QueueOpenButton: TSpeedButton;
    StatTimer: TTimer;
    WaitersTabSheet: TTabSheet;
    TurboButton: TSpeedButton;
    TreeView1: TTreeView;
    WriteValueButton: TSpeedButton;
    Splitter11: TSplitter;
    Splitter5: TSplitter;
    SearchResultStringGrid: TStringGrid;
    Splitter9: TSplitter;
    CursorStringGrid: TStringGrid;
    StringGrid2: TStringGrid;
    SubmitFButton: TSpeedButton;
    Splitter6: TSplitter;
    RawStringGrid: TStringGrid;
    TabSheet1: TTabSheet;
    RawViewTabSheet: TTabSheet;
    LogDepthEdit: TSpinEdit;
    LogMemo: TMemo;
    sDBListBox: TListBox;
    OpenDialog: TOpenDialog;
    Panel1: TPanel;
    Panel10: TPanel;
    Panel5: TPanel;
    LoadCmdsButton: TSpeedButton;
     Panel6: TPanel;
     Panel8: TPanel;
    LogLevelEdit: TSpinEdit;
    SaveDialog: TSaveDialog;
    Splitter2: TSplitter;
    sDBSheet: TTabSheet;
    Splitter3: TSplitter;
    Splitter4: TSplitter;
    Splitter7: TSplitter;
    Splitter8: TSplitter;
    SubmitButton: TButton;
    NamedViewTabSheet: TTabSheet;
    TabSheet2: TTabSheet;
    TabSheet3: TTabSheet;
    WorkersGrid: TStringGrid;
    GroupBox1: TGroupBox;
    GroupBox2: TGroupBox;
    CmdListBox: TListBox;
    PageControl1: TPageControl;
    Panel2: TPanel;
    Panel3: TPanel;
    Panel4: TPanel;
    RunButton: TSpeedButton;
    Splitter1: TSplitter;
    StepButton: TSpeedButton;
    InterpreterSheet: TTabSheet;
    ConfigSheet: TTabSheet;
    LogSheet: TTabSheet;
    StopButton: TSpeedButton;
    procedure BriefFullToggleChange(Sender: TObject);
    procedure ConnectButtonClick(Sender: TObject);
    procedure ClassesListBoxSelectionChange(Sender: TObject; User: boolean);
    procedure CommandEditKeyDown(Sender: TObject; var Key: Word;Shift: TShiftState);
    procedure CommandEditKeyPress(Sender: TObject; var Key: char);
    procedure CommandFilterEditChange(Sender: TObject);
    Procedure ConnectSpeedButtonClick(Sender: TObject);
    procedure CTXCacheListBoxClick(Sender: TObject);
    Procedure CTXCacheListBoxDblClick(Sender: TObject);
    procedure CTXCacheListBoxSelectionChange(Sender: TObject; User: boolean);
    Procedure CTXListBoxDblClick(Sender: TObject);
    procedure CTXListBoxSelectionChange(Sender: TObject; User: boolean);
    procedure CursorStringGridDblClick(Sender: TObject);
    Procedure CursorStringGridSelection(Sender: TObject; aCol, aRow: Integer);
    procedure DisplayConfigBitBtnClick(Sender: TObject);
    procedure CmdListBoxClick(Sender: TObject);
    procedure DropQueueButtonClick(Sender: TObject);
    procedure EnumAttrsButtonClick(Sender: TObject);
    procedure EvalButtonClick(Sender: TObject);
    procedure FilterKeepButtonClick(Sender: TObject);
    procedure FilterOutButtonClick(Sender: TObject);
    procedure ModifyConfigBitBtnClick(Sender: TObject);
    procedure Panel4Resize(Sender: TObject);
    procedure PasteCmdsButtonClick(Sender: TObject);
    procedure PureTimeButtonClick(Sender: TObject);
    procedure QueueOpenButtonClick(Sender: TObject);
    procedure SearchByChainButtonClick(Sender: TObject);
    procedure FindByIDButtonClick(Sender: TObject);
    procedure FindByAddrButtonClick(Sender: TObject);
    procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
    procedure FormCreate(Sender: TObject);
    procedure GroupBox5Click(Sender: TObject);
    procedure HistoryLengthEditChange(Sender: TObject);
    procedure HistoryStringGridResize(Sender: TObject);
    procedure InternalNameLabelClick(Sender: TObject);
    Procedure LoadCmdsButtonClick(Sender: TObject);
    Procedure ImportObjectButtonClick(Sender: TObject);
    procedure PageControl2Change(Sender: TObject);
    procedure RawStringGridDblClick(Sender: TObject);
    procedure sDBListBoxClick(Sender: TObject);
    procedure sDBListBoxSelectionChange(Sender: TObject; User: boolean);
    procedure Panel3Resize(Sender: TObject);
    Procedure ProcessCommand(Data:PtrInt);
    Procedure RunButtonClick(Sender: TObject);
    Procedure LogLevelEditChange(Sender: TObject);
    procedure SaveCmdsButtonClick(Sender: TObject);
    procedure qSearchResultStringGridClick(Sender: TObject);
    Procedure SearchResultStringGridClick(Sender: TObject);
    procedure SearchResultStringGridResize(Sender: TObject);
    procedure SearchResultStringGridSelection(Sender: TObject; aCol, aRow: Integer);
    procedure SetCursorButtonClick(Sender: TObject);
    procedure StatTimerTimer(Sender: TObject);
    procedure StringGrid2DblClick(Sender: TObject);
    procedure StringGrid2HeaderClick(Sender: TObject; IsColumn: Boolean;
      Index: Integer);
    procedure SubmitFButtonClick(Sender: TObject);
    Procedure StepButtonClick(Sender: TObject);
    Procedure StopButtonClick(Sender: TObject);
    procedure SubmitButtonClick(Sender: TObject);
    procedure TurboButtonClick(Sender: TObject);
    procedure UpdateBitBtnClick(Sender: TObject);
    Procedure WorkersGridDblClick(Sender: TObject);
    procedure WorkersGridResize(Sender: TObject);
    procedure WorkersGridSelectCell(Sender: TObject; aCol, aRow: Integer;
      var CanSelect: Boolean);
    procedure WriteValueButtonClick(Sender: TObject);
  Private
    { Private declarations }
  Public
    { Public declarations }
  End;


Var
  DispatcherForm: TDispatcherForm;
  GUIMode:TGUIMode;
  RemoteWorker:String;
  RemoteWorkers:TStringList;
  RemoteRoles:TStringList;
  RemoteConnections:TStringList;
  RemoteConnectionAddrs:TStringList;
  RemoteQueueOpens:TStringList;
  RemoteQueueRuns:TStringList;
  RemoteQueueLengths:TStringList;
  RemoteAvgSPCs:TStringList;
  RemoteETEs:TStringList;

  RemoteCommandQueue:TStringList;
  RemoteHistoryC:TStringList;
  RemoteHistoryT:TStringList;
  RemoteHistoryR:TStringList;
  RemoteOther:TStringList;

Implementation
Uses unit1,Config;


//==============================================================================================
//============================== S e r v i c e   r o u t i n e s ===============================
//==============================================================================================

Procedure LogMessage(Level:Longint;Msg:String);
Begin
  If Level>DispatcherForm.LogLevelEdit.Value Then Exit;
  DispatcherForm.LogMemo.Lines.Add(Msg);
  If DispatcherForm.LogMemo.Lines.Count>DispatcherForm.LogDepthEdit.Value Then
     DispatcherForm.LogMemo.Lines.Delete(0);
End;

Function RunHiddenCommand(Cmd:String):Longint;
Var Process:TProcess; ProcessOut:TInputPipeStream;
Begin
  Process:=TProcess.Create(Nil);
  Try
    Process.Options:=[poUsePipes,poNoConsole,poStderrToOutPut];
    Process.ShowWindow:=swoNone;
    Process.CommandLine:=OSPath(Cmd);
    Try
      Process.Execute;
      Repeat Application.ProcessMessages;Until Not Process.Active;
 //     Process.WaitOnExit;
      ProcessOut:=Process.Output;
      Result:=Process.ExitStatus;
    Finally Process.Free; End;
  Except
  End;
End;

Function RunWinCommand(Cmd:String):Longint;
Var Process:TProcess; ProcessOut:TInputPipeStream;
Begin
  Process:=TProcess.Create(Nil);
  Try
    Process.Options:=[poNewConsole,poNewProcessGroup,poStderrToOutPut];
    Process.ShowWindow:=swoShow;
    Process.CommandLine:=OSPath(Cmd);
    Try
      Process.Execute;
      ProcessOut:=Process.Output;
      Result:=Process.ExitStatus;
    Finally Process.Free; End;
  Except

  End;
End;

Function ExecShellCmd(CLI:String):String;
Var I,Avail:Longint;
  Process:TProcess; ProcessOut:TInputPipeStream;
  CB:String;b,pb,Buf,RequestedOP,AttributeName,AttributeType:String;
Begin
        LogMessage(3,CLI);{StopWaitingResponce:=False;}
        Process:=TProcess.Create(Nil);
        Process.Options:=[poUsePipes,poNoConsole,poStderrToOutPut];
        Process.ShowWindow:=swoShow;
        Process.CommandLine:=CLI;
        Try
          Buf:='';
          Process.Execute;
          ProcessOut:=Process.Output;
          Repeat
            Avail:=ProcessOut.NumBytesAvailable;
            If Avail>0 Then Begin
              If Avail>254 Then Repeat
                SetLength(cb,254);
                ProcessOut.Read(CB[1],254);
                Buf:=Buf+CB;
                Avail:=Avail-254;
              Until Avail<=254;
              SetLength(CB,Avail);
              ProcessOut.Read(CB[1],Avail);
              Buf:=Buf+CB;
            End;
            Sleep(10);
            Application.ProcessMessages;
          Until ((Not Process.Active) And (ProcessOut.NumBytesAvailable=0)) {Or (StopWaitingResponce)};
        Except LogMessage(0,'Error running '+CLI);End;
        Process.CloseOutput;Process.Free;
        LogMessage(3,FilterEnter(Buf));
    {    If StopWaitingResponce Then Result:='cancelled' Else} Result:=FilterEnter(Buf);
End;

//==============================================================================================
//============================== Q u e u e    e x e c u t i o n ================================
//==============================================================================================

Procedure UpdateGUI;
Var I,aCol,aRow,Delta,NWorkers,NDispatchers,NViewers:Longint;Cmds:TStringList;Clip:String;
    CanSelect:Boolean;Sel:TGridRect;
    DWorkers,DRoles,DConnections,DConnectionAddrs,DQueueOpens,DQueueRuns,DQueueLengths,DAvgSPCs,DETEs,
    DCommandQueue,DHistoryC,DHistoryT,DHistoryR:TStringList;
    Function GetListItem(List:TStringList;I:Longint):String;
    Begin
      If (I<List.Count)And(I>=0) Then Result:=List.Strings[I]
      Else Result:='';
    End;

Begin
    If GUIMode=gmLocal Then Begin
      DispatcherForm.Caption:=ConfigFile.Values['MyName'];
      DWorkers:=Workers;
      DRoles:=Roles;
      DConnections:=Connections;
      DConnectionAddrs:=ConnectionAddrs;
      DQueueOpens:=QueueOpens;
      DQueueRuns:=QueueRuns;
      DQueueLengths:=QueueLengths;
      DAvgSPCs:=AvgSPCs;
      DETEs:=ETEs;
      DCommandQueue:=Dispatcher.CommandQueue;
      DHistoryC:=HistoryC;
      DHistoryT:=HistoryT;
      DHistoryR:=HistoryR;
    end Else Begin
      DispatcherForm.Caption:=ConfigFile.Values['MyName']+' connected to '+RemoteWorker;
      DWorkers:=RemoteWorkers;
      DRoles:=RemoteRoles;
      DConnections:=RemoteConnections;
      DConnectionAddrs:=RemoteConnectionAddrs;
      DQueueOpens:=RemoteQueueOpens;
      DQueueRuns:=RemoteQueueRuns;
      DQueueLengths:=RemoteQueueLengths;
      DAvgSPCs:=RemoteAvgSPCs;
      DETEs:=RemoteETEs;
      DCommandQueue:=RemoteCommandQueue;
      DHistoryC:=RemoteHistoryC;
      DHistoryT:=RemoteHistoryT;
      DHistoryR:=RemoteHistoryR;
      If RemoteOther.IndexOfName('historyindex')>=0 Then
        HistoryIndex:=StrToInt(RemoteOther.Values['historyindex']);
    End;
      NWorkers:=0;NDispatchers:=0;NViewers:=0;
      For I:=0 To DWorkers.Count-1 Do Begin
        If GetListItem(DRoles,I)='worker' Then Inc(NWorkers);
        If GetListItem(DRoles,I)='dispatcher' Then Inc(NDispatchers);
        If GetListItem(DRoles,I)='viewer' Then Inc(NViewers);
      end;
      DispatcherForm.GroupBox1.Caption:='Modules: D='+IntToStr(NDispatchers)+' W='+IntToStr(NWorkers)+' V='+IntToStr(NViewers);
      DispatcherForm.WorkersGrid.RowCount:=DWorkers.Count+1;
      DispatcherForm.WorkersGrid.Cells[0,0]:='Worker name';
      DispatcherForm.WorkersGrid.Cells[1,0]:='Role';
      DispatcherForm.WorkersGrid.Cells[2,0]:='Connection';
      DispatcherForm.WorkersGrid.Cells[3,0]:='Addr';
      For I:=0 To DWorkers.Count-1 Do Begin
        DispatcherForm.WorkersGrid.Cells[0,I+1]:=GetListItem(DWorkers,I);
        DispatcherForm.WorkersGrid.Cells[1,I+1]:=GetListItem(DRoles,I);
        DispatcherForm.WorkersGrid.Cells[2,I+1]:=GetListItem(DConnections,I);
        DispatcherForm.WorkersGrid.Cells[3,I+1]:=GetListItem(DConnectionAddrs,I);
      End;
      If DispatcherForm.BriefFullToggle.Checked Then Begin //more columns
        DispatcherForm.WorkersGrid.Cells[4,0]:='QO';
        DispatcherForm.WorkersGrid.Cells[5,0]:='Run';
        DispatcherForm.WorkersGrid.Cells[6,0]:='Queue Len';
        DispatcherForm.WorkersGrid.Cells[7,0]:='Avg SPC';
        DispatcherForm.WorkersGrid.Cells[8,0]:='ETE';
        For I:=0 To DWorkers.Count-1 Do Begin
          DispatcherForm.WorkersGrid.Cells[4,I+1]:=GetListItem(DQueueOpens,I);
          DispatcherForm.WorkersGrid.Cells[5,I+1]:=GetListItem(DQueueRuns,I);
          DispatcherForm.WorkersGrid.Cells[6,I+1]:=GetListItem(DQueueLengths,I);
          DispatcherForm.WorkersGrid.Cells[7,I+1]:=GetListItem(DAvgSPCs,I);
          DispatcherForm.WorkersGrid.Cells[8,I+1]:=GetListItem(DETEs,I);
        End;
      end;
      aRow:=DispatcherForm.WorkersGrid.Row;
      aCol:=DispatcherForm.WorkersGrid.Col;

      If WorkersChanged Then Begin
        DispatcherForm.WorkersGridSelectCell(nil, aCol,  aRow, CanSelect);
        WorkersChanged:=False;
      End;

    If Not(TurboButtonDown) Then Begin
      Application.ProcessMessages;
      DispatcherForm.CommandList.Items.Assign(DCommandQueue);
    end;//
    I:=DispatcherForm.HistoryStringGrid.RowCount-1;
    DispatcherForm.HistoryStringGrid.Cells[0,I]:=DispatcherForm.CurrentCommandEdit.Text;
    DispatcherForm.HistoryStringGrid.Cells[1,I]:='';
    DispatcherForm.HistoryStringGrid.Cells[2,I]:='';
    If DHistoryR.Count=0 Then Exit;
    DispatcherForm.CommandResult.Text:=DHistoryR[HistoryIndex];
    DispatcherForm.CommandTimeLabel.Caption:=DHistoryT[HistoryIndex];
    Delta:=0;
    For I:=DispatcherForm.HistoryStringGrid.RowCount-1 Downto 1 Do Begin
      DispatcherForm.HistoryStringGrid.Cells[0,I]:=DHistoryC[(HistoryIndex+DHistoryC.Count+Delta) Mod DHistoryC.Count];
      DispatcherForm.HistoryStringGrid.Cells[1,I]:=DHistoryR[(HistoryIndex+DHistoryC.Count+Delta) Mod DHistoryC.Count];
      DispatcherForm.HistoryStringGrid.Cells[2,I]:=DHistoryT[(HistoryIndex+DHistoryC.Count+Delta) Mod DHistoryC.Count];
      Dec(Delta);
    end;

{    Index:=Workers.IndexOf(ConfigFile.Values['MyName']);
    If Index>-1 Then
      If DispatcherForm.BriefFullToggle.Checked Then Begin
        DispatcherForm.WorkersGrid.Cells[4,Index+1]:=QueueOpens[Index];
        DispatcherForm.WorkersGrid.Cells[5,Index+1]:=QueueRuns[Index];
        DispatcherForm.WorkersGrid.Cells[6,Index+1]:=QueueLengths[Index];
        DispatcherForm.WorkersGrid.Cells[7,Index+1]:=AvgSPCs[Index];
        DispatcherForm.WorkersGrid.Cells[8,Index+1]:=ETEs[Index];
      end;}
    Sel.Top:=DispatcherForm.HistoryStringGrid.RowCount-1;
    Sel.Bottom:=DispatcherForm.HistoryStringGrid.RowCount-1;
    Sel.Left:=0;
    Sel.Right:=DispatcherForm.HistoryStringGrid.ColCount-1;
    DispatcherForm.HistoryStringGrid.Selection:=Sel;
    DispatcherForm.CurrentCommandEdit.Text:='';

      {
      If DispatcherForm.WorkersGrid.RowCount=1 Then Exit;
      If Workers.Count=0 Then Exit;

      If ARow-1<Workers.Count-1 Then
        If (aRow<0) Or (aRow-1>=Workers.Count-1) Then Begin LogMessage(1,'Unexisting worker selected '+IntToStr(aRow-1));Exit;End;
      If Workers.Objects[aRow-1]<>Nil Then Begin
        LogMessage(5,'Displaying command list #'+IntToStr(aRow-1));
        Cmds:=Workers.Objects[aRow-1] As TStringList;
        DispatcherForm.CmdListBox.Clear;
        If Cmds.Count>0 Then
        For I:=0 To Cmds.Count-1 Do Begin
          If (DispatcherForm.CommandFilterEdit.Text='')Or(Pos(DispatcherForm.CommandFilterEdit.Text,Cmds[I])>0) Then Begin
            Clip:=Cmds[I];
            If Aliases.IndexOf(Cmds[I])<0 Then Begin
              Delete(Clip,1,4);
              DispatcherForm.CmdListBox.Items.Add(Workers[aRow-1]+Clip);
            End Else
              DispatcherForm.CmdListBox.Items.Add(Clip);
            end;
          end;
        end;}

End;

Procedure TDispatcherForm.ProcessCommand(Data:PtrInt);
Var DirtyTime,PureTime,UsedTime:TDateTime; H,M,S,ms:Word;
    I,J,K,N,Index,Delta:Longint;
    CurrentCommand,ComResult:String;StatList:TStringList;
Begin

  If AlreadyRun Then Exit;
   AlreadyRun:=True;
   If CommandQueue.Count=0 Then CommandList.Clear Else
   CommandList.Items.Assign(Dispatcher.CommandQueue);
   CurrentCommandEdit.Text:=Dispatcher.CurrentCommand;

  // If there are commands in the queue - do command processing
  // otherwise - do stat only
    While RTQueue.Count>0 Do Begin
      Dispatcher.ProcessRTCommand;
      Application.QueueAsyncCall(@ProcessCommand,0);
      UpdateGUI;
    End;

    If CommandQueue.Count=0 Then Begin
      If Quitting Then Begin
        Dispatcher.GracefulClose;
        Application.Terminate;
      End;
      AlreadyRun:=False;
      If (mSecsPerDay*(Now-T3)>StrToFloat(ConfigFile.Values['StatInterval'])) Then Begin QWasEmpty:=True;SendStat;T3:=Now;End;
      Exit;
    end;
    Dispatcher.ProcessCommand;
    If (mSecsPerDay*(Now-T3)>StrToFloat(ConfigFile.Values['StatInterval'])) Then Begin QWasEmpty:=True;SendStat;T3:=Now;End;
    //moving drawing to UpdateGUI to enable remote GUI
{    CommandList.Items.Assign(Dispatcher.CommandQueue);
    If Not(TurboButtonDown) Then Application.ProcessMessages;
    I:=HistoryStringGrid.RowCount-1;
    HistoryStringGrid.Cells[0,I]:=CurrentCommandEdit.Text;
    HistoryStringGrid.Cells[1,I]:='';
    HistoryStringGrid.Cells[2,I]:='';}

//update interface values from dispatcher unit
{    DispatcherForm.CommandResult.Text:=HistoryR[HistoryIndex];//should it be moved to updateGUI?
    CommandTimeLabel.Caption:=HistoryT[HistoryIndex];
    Delta:=0;
    For I:=HistoryStringGrid.RowCount-1 Downto 1 Do Begin
      HistoryStringGrid.Cells[0,I]:=HistoryC[(HistoryIndex+HistoryC.Count+Delta) Mod HistoryC.Count];
      HistoryStringGrid.Cells[1,I]:=HistoryR[(HistoryIndex+HistoryC.Count+Delta) Mod HistoryC.Count];
      HistoryStringGrid.Cells[2,I]:=HistoryT[(HistoryIndex+HistoryC.Count+Delta) Mod HistoryC.Count];
      Dec(Delta);
    end;}
    UpdateGUI;
    If RemoteGUIs.Count>0 Then
      For I:=0 To RemoteGUIs.Count-1 Do Begin
        QueueCommand(RemoteGUIs[I]+'::Update GUI:Workers:'+Workers.CommaText);
        QueueCommand(RemoteGUIs[I]+'::Update GUI:Roles:'+Roles.CommaText);
        QueueCommand(RemoteGUIs[I]+'::Update GUI:Connections:'+Connections.CommaText);
        QueueCommand(RemoteGUIs[I]+'::Update GUI:ConnectionAddrs:'+ConnectionAddrs.CommaText);
        QueueCommand(RemoteGUIs[I]+'::Update GUI:QueueOpens:'+QueueOpens.CommaText);
        QueueCommand(RemoteGUIs[I]+'::Update GUI:QueueRuns:'+QueueRuns.CommaText);
        QueueCommand(RemoteGUIs[I]+'::Update GUI:QueueLengths:'+QueueLengths.CommaText);
        QueueCommand(RemoteGUIs[I]+'::Update GUI:AvgSPCs:'+AvgSpcs.CommaText);
        QueueCommand(RemoteGUIs[I]+'::Update GUI:ETEs:'+ETEs.CommaText);
        QueueCommand(RemoteGUIs[I]+'::Update GUI:Queue:'+CommandQueue.CommaText);
        QueueCommand(RemoteGUIs[I]+'::Update GUI:HistoryC:'+HistoryC.CommaText);
        QueueCommand(RemoteGUIs[I]+'::Update GUI:HistoryT:'+HistoryT.CommaText);
        QueueCommand(RemoteGUIs[I]+'::Update GUI:HistoryR:'+HistoryR.CommaText);
        OtherGUI.Clear;
        OtherGUI.Add('HistoryIndex='+IntToStr(HistoryIndex));
        QueueCommand(RemoteGUIs[I]+'::Update GUI:OtherGUI:'+OtherGUI.CommaText);
      End;
    If (mSecsPerDay*(Now-T3)>StrToFloat(ConfigFile.Values['StatInterval'])) Then Begin QWasEmpty:=True;PC:=0;T3:=Now;End;
    If (PC=0)Or((mSecsPerDay*(UsedTime))>=StrToFloat(ConfigFile.Values['StatInterval'])) Then
      SendStat;

{    Index:=Workers.IndexOf(ConfigFile.Values['MyName']);
    If Index>-1 Then
      If BriefFullToggle.Checked Then Begin
        WorkersGrid.Cells[4,Index+1]:=QueueOpens[Index];
        WorkersGrid.Cells[5,Index+1]:=QueueRuns[Index];
        WorkersGrid.Cells[6,Index+1]:=QueueLengths[Index];
        WorkersGrid.Cells[7,Index+1]:=AvgSPCs[Index];
        WorkersGrid.Cells[8,Index+1]:=ETEs[Index];
      end;
    Sel.Top:=HistoryStringGrid.RowCount-1;
    Sel.Bottom:=HistoryStringGrid.RowCount-1;
    Sel.Left:=0;
    Sel.Right:=HistoryStringGrid.ColCount-1;
    HistoryStringGrid.Selection:=Sel;
    If Not(TurboButtonDown) Then Application.ProcessMessages;
    CurrentCommandEdit.Text:='';  }
    If Not DoMultipleSteps Then Begin
      Run:=False;
    End Else Begin
//      If (CommandQueue.Count>0) Or (RTQueue.Count>0) Then Begin
    Application.QueueAsyncCall(@ProcessCommand,0);
//      end;
    End;
    AlreadyRun:=False;
End;

procedure TDispatcherForm.EvalButtonClick(Sender: TObject);
Var EvalRes:TStringList;
    I,M,N:Longint;CTX:TsDBCtx; DB:TsDB;
    CursorName,SourceID,Addr,Chain:String;
    CacheObject:TCacheObject;
begin
  CursorName:=CursorStringGrid.Cells[0,CursorStringGrid.Selection.Top];
  EvalRes:=TStringList.Create;
  Addr:=GetNextToken(SearchEdit.Text,Chain,'.');
  sDB_CAEvalByChain(CursorName,Addr,Chain,EvalRes);
  CursorStringGridSelection(Nil, CursorStringGrid.Col, CursorStringGrid.Row);
  EvalMemo.Lines.Assign(EvalRes);
  EvalRes.Free;
end;

procedure TDispatcherForm.FilterKeepButtonClick(Sender: TObject);
Var SearchRes:TStringList;
    I,M,N:Longint;CTX:TsDBCtx; DB:TsDB;
    CursorName,SourceID:String;
    CacheObject:TCacheObject;
begin
  CursorName:=CursorStringGrid.Cells[0,CursorStringGrid.Selection.Top];
  sDB_CRFilterKeepByChain(CursorName,SearchEdit.Text);
  CursorStringGridSelection(Nil, CursorStringGrid.Col, CursorStringGrid.Row);
end;

procedure TDispatcherForm.FilterOutButtonClick(Sender: TObject);
Var SearchRes:TStringList;
    I,M,N:Longint;CTX:TsDBCtx; DB:TsDB;
    CursorName,SourceID:String;
    CacheObject:TCacheObject;
begin
  CursorName:=CursorStringGrid.Cells[0,CursorStringGrid.Selection.Top];
  sDB_CRFilterOutByChain(CursorName,SearchEdit.Text);
  CursorStringGridSelection(Nil, CursorStringGrid.Col, CursorStringGrid.Row);
end;

procedure TDispatcherForm.ModifyConfigBitBtnClick(Sender: TObject);
begin
  ConfigFile.Assign(ConfigMemo.Lines);
end;

procedure TDispatcherForm.Panel4Resize(Sender: TObject);
begin
  CurrentCommandEdit.Width:=Panel4.Width-CurrentCommandEdit.Left-8;
end;

procedure TDispatcherForm.PasteCmdsButtonClick(Sender: TObject);
Var Cmds:TStringList;
    I:Longint;
Begin
  Cmds:=TStringList.Create;
  Cmds.Text:=ClipBoard.AsText;
  If Cmds.Count>0 Then
    QueueMultipleCommands(Cmds);
end;

procedure TDispatcherForm.PureTimeButtonClick(Sender: TObject);
begin
  PureTimeButtonDown:=PureTimeButton.Down;
end;

procedure TDispatcherForm.QueueOpenButtonClick(Sender: TObject);
Var Index:Longint;
begin
  Index:=Workers.IndexOf(ConfigFile.Values['MyName']);
  If QueueOpenButton.Down Then
    QueueOpens[Index]:='Open'
  Else
    QueueOpens[Index]:='Closed';
  // послать свежий stat
  updategui;
  sendstat;
end;

procedure TDispatcherForm.SearchByChainButtonClick(Sender: TObject);
Var SearchRes:TStringList;
    I,M,N:Longint;CTX:TsDBCtx; DB:TsDB;
    CursorName,SourceID:String;
    CacheObject:TCacheObject;
begin
  CursorName:=CursorStringGrid.Cells[0,CursorStringGrid.Selection.Top];
  sDB_CRSearchByChain(CursorName,SearchEdit.Text);
  CursorStringGridSelection(Nil, CursorStringGrid.Col, CursorStringGrid.Row);
end;

Procedure DisplayCacheObject(DBName:String;Source:TCacheObject);
Var CacheObject,Link1,Link2:TCacheObject;
    DB:TsDB;Ctx:TsDBCtx;
    B,PB,ConceptID:String;
    M,N,O,I,J,K,ID:Longint;ClassDesc,Desc,LongValue:TStringList;
    ReadSuccess:Boolean;Addr,CTXName:String;
Begin
  If DBName='' Then Begin
    With DispatcherForm Do Begin
      InternalNameLabel.Caption:='';
      RawStringGrid.RowCount:=1;
      RawStringGrid.Cells[0,0]:='Key';
      RawStringGrid.Cells[1,0]:='T';
      RawStringGrid.Cells[2,0]:='At';
      RawStringGrid.Cells[3,0]:='Addr';
      StringGrid2.RowCount:=1;
      StringGrid2.Cells[0,0]:='Key';
      StringGrid2.Cells[1,0]:='RelIntName';
      StringGrid2.Cells[2,0]:='IntName';
//      StringGrid2.Cells[3,0]:='';
    End;
    Exit;
  End;
  CacheObject:=Source;
  CTXName:=CacheObject.Ctx.Name;
  With DispatcherForm Do Begin
    AddrLabel.Caption:=CacheObject.Ctx.DB.Name+' '+CacheObject.Ctx.Name+'/'+IntToStr(CacheObject.ObjectID);
    InternalNameLabel.Caption:=CacheObject.InternalName;
    RawStringGrid.RowCount:=CacheObject.LinkKeys.Count+1;
    RawStringGrid.Cells[0,0]:='Key';
    RawStringGrid.Cells[1,0]:='T';
    RawStringGrid.Cells[2,0]:='At';
    RawStringGrid.Cells[3,0]:='Addr';
    For I:=0 To CacheObject.LinkKeys.Count-1 Do Begin
      RawStringGrid.Cells[0,I+1]:=CacheObject.LinkKeys[I];
      RawStringGrid.Cells[1,I+1]:=CacheObject.LinkTypes[I];
      RawStringGrid.Cells[2,I+1]:=CacheObject.LinkAddrTypes[I];
      If CacheObject.LinkAddrs[I]='_oDBList_' Then Begin
        LongValue:=CacheObject.LinkAddrs.Objects[I] As TStringList;
        RawStringGrid.Cells[3,I+1]:=LongValue.Text;
        Continue;
      End;
      RawStringGrid.Cells[3,I+1]:=CacheObject.LinkAddrs[I];
    end;
    StringGrid2.RowCount:=CacheObject.LinkKeys.Count+1;
    StringGrid2.Cells[0,0]:='Key';
    StringGrid2.Cells[1,0]:='RelIntName';
    StringGrid2.Cells[2,0]:='IntName';
//    StringGrid2.Cells[3,0]:='';
    For I:=0 To CacheObject.LinkKeys.Count-1 Do Begin
      StringGrid2.Cells[0,I+1]:=CacheObject.LinkKeys[I];
      If CacheObject.LinkAddrTypes[I]='.' Then Begin
        Addr:=CacheObject.LinkAddrs[I];
        If Pos('/',Addr)<>0 Then Begin // адрес указывает в другой контекст
          CtxName:=Copy(Addr,1,Pos('/',Addr)-1);
          ConceptID:=Copy(Addr,Pos('/',Addr)+1,Length(Addr)-Pos('/',Addr));
        End Else Begin
          CTXName:=CacheObject.Ctx.Name;
          ConceptID:=Addr;
        End;
        Link1:=sDB_FQGetConceptHandleByID(DBName,CtxName,ConceptID);
        If Link1=Nil Then Begin StringGrid2.Cells[1,I+1]:='Error: integer ConceptID expected '+ConceptID;Exit;End;
        StringGrid2.Cells[1,I+1]:=Link1.InternalName;
        J:=Link1.LinkKeys.IndexOf(CacheObject.LinkKeys[I]);
        If J<0 Then Begin
          StringGrid2.Cells[2,I+1]:='no 2nd level same name key';
        End Else Begin
          Addr:=Link1.LinkAddrs[J];
          If Pos('/',Addr)<>0 Then Begin // адрес указывает в другой контекст
            CtxName:=Copy(Addr,1,Pos('/',Addr)-1);
            ConceptID:=Copy(Addr,Pos('/',Addr)+1,Length(Addr)-Pos('/',Addr));
          End Else Begin
            CTXName:=Link1.Ctx.Name;
            ConceptID:=Addr;
          end;
          If Pos('@',ConceptID)<>0 Then Begin
            //здесь должно быть разрешение адреса без использования курсора
            StringGrid2.Cells[2,I+1]:=ConceptID;
          End Else Begin
            Link2:=sDB_FQGetConceptHandleByID(DBName,CtxName,ConceptID);
            If Link2=Nil Then Begin StringGrid2.Cells[2,I+1]:='Error: integer ConceptID expected '+ConceptID;Exit;End;
            If Link2.Ctx.Name=CacheObject.Ctx.Name Then StringGrid2.Cells[2,I+1]:='@'+Link2.InternalName
            Else StringGrid2.Cells[2,I+1]:=Link2.Ctx.Name+'/@'+Link2.InternalName
          end;
        end;
      end;
      If CacheObject.LinkTypes[I]='#' Then Begin
        StringGrid2.Cells[1,I+1]:='';
        StringGrid2.Cells[2,I+1]:=CacheObject.LinkAddrs[I];
      end;
      If CacheObject.LinkTypes[I]='@' Then Begin
        StringGrid2.Cells[1,I+1]:='@ not implemented yet';
        StringGrid2.Cells[2,I+1]:='';
      end;
  {    If CacheObject.LinkAddrs[I]='_oDBList_' Then Begin
         LongValue:=CacheObject.LinkAddrs.Objects[I] As TStringList;
//         StringGrid2.Cells[3,I+1]:=LongValue.Text;
         Continue;
      End;}
//      StringGrid2.Cells[3,I+1]:='';
    end;
  End;
End;

procedure TDispatcherForm.FindByIDButtonClick(Sender: TObject);
Var CacheObject:TCacheObject;
    DB:TsDB;Ctx:TsDBCtx;DBName,CtxName,AttrName,AttrType,AttrValue:String;
    CacheIndex,M,N,I,ID:Longint;ClassDesc,Desc:TStringList;
Begin
  M:=sDBListBox.ItemIndex;
  If M<0 Then Begin LogMessage(1,'Error: sDB not selected');Exit; End;
  DBName:=sDBListBox.Items[M];
  DB:=(sDBList.Objects[M]) As TsDB;
  N:=CTXListBox.ItemIndex;
  If N<0 Then Begin LogMessage(1,'Error: Ctx not selected');Exit; End;
  CTXName:=CTXListBox.Items[N];
  Ctx:=(DB.CtxList.Objects[N]) As TsDBCtx;
  ID:=StrToInt(SearchEdit.Text);
  If (ID>=Ctx.ConceptTree.NextFree)Or(Ctx.ConceptTree.FreeList.IndexOf(SearchEdit.Text)>=0) Then Begin LogMessage(1,'Error: concept with ID "'+SearchEdit.Text+'" not found');Exit; End;
  I:=Ctx.ConceptTree.Cache.IndexOf(SearchEdit.Text);
  Ctx.ConceptTree.CacheProtected:=True;
//  ExecCommand('self::sDB_FQReadConcept:'+sDBListBox.Items[M]+':'+CTXListBox.Items[N]+':'+SearchEdit.Text);
//  CacheIndex:=StrToInt(sDB_FQFindConceptByID(DBName,CtxName,SearchEdit.Text));
  CacheObject:=Ctx.ConceptTree.Cache.Objects[CacheIndex] As TCacheObject;
  SearchResultStringGrid.RowCount:=1;
  SearchResultStringGrid.ColCount:=2;
  SearchResultStringGrid.Cells[0,0]:=CTX.Name+'/'+SearchEdit.Text;
  SearchResultStringGrid.Cells[1,0]:=CacheObject.InternalName;
  DisplayCacheObject(DBName,CacheObject);
  Ctx.ConceptTree.CacheProtected:=False;
end;

procedure TDispatcherForm.FindByAddrButtonClick(Sender: TObject);
Var CursorName,Error:String;M,N:Longint;
    DBCursor:TsDBCursor;
begin
  CursorName:=CursorStringGrid.Cells[0,CursorStringGrid.Selection.Top];
  DBCursor:=GetCursorByName(CursorName);
  Error:=sDB_CAFindConcept(CursorName,SearchEdit.Text);
  If (False) Or (DBCursor.SearchResults.Count=0) Then Begin
    SearchResultStringGrid.RowCount:=0;
  End Else Begin
    SearchResultStringGrid.RowCount:=1;
    SearchResultStringGrid.ColCount:=2;
    SearchResultStringGrid.Cells[0,0]:=(DBCursor.SearchResults.Objects[0] As TCacheObject).Ctx.Name+'/'+IntToStr((DBCursor.SearchResults.Objects[0] As TCacheObject).ObjectID);
    SearchResultStringGrid.Cells[1,0]:=(DBCursor.SearchResults.Objects[0] As TCacheObject).InternalName;
  end;
end;

procedure TDispatcherForm.FormClose(Sender: TObject;  var CloseAction: TCloseAction);
//Var I:Longint;Socket:TTCPBlockSocket;
begin
  {//Send "I'm leaving" notification if I'm worker
  If LowerCase(ConfigFile.Values['Role'])='worker' Then Begin
    Dispatcher.SendDataTo(ConfigFile.Values['Dispatcher Name'],ConfigFile.Values['Dispatcher Name']+'::Denounce Worker:'+ConfigFile.Values['MyName']);
    Dispatcher.SendDataTo(ConfigFile.Values['Dispatcher Name'],'disconnect');
  End;
  For I:=0 To Workers.Count-1 Do Begin
    If LowerCase(Connections[I])='tcp' Then Begin
      Socket:=Connections.Objects[I] As TTCPBlockSocket;
      Socket.Free;
    end;
  End;}
  Dispatcher.GracefulClose;
end;

procedure TDispatcherForm.CmdListBoxClick(Sender: TObject);
begin
  If CmdListBox.ItemIndex>-1 Then
    CommandEdit.Text:=CmdListBox.Items[CmdListBox.ItemIndex];
end;

procedure TDispatcherForm.DropQueueButtonClick(Sender: TObject);
begin
  CommandQueue.Clear;
  CommandList.Items.Assign(Dispatcher.CommandQueue);
end;

procedure TDispatcherForm.EnumAttrsButtonClick(Sender: TObject);
Var AttrList:TStringList;
    CursorName,Error:String;
    DBCursor:TsDBCursor;
    I:Longint;
begin
  CursorName:=CursorStringGrid.Cells[0,CursorStringGrid.Selection.Top];
  DBCursor:=GetCursorByName(CursorName);
  sDB_CreateCursor('interface');
  sDB_CopyCursor(CursorName,'interface');
  Error:=sDB_CAFindConcept('interface',SearchEdit.Text);
  AttrList:=TStringList.Create;

  sDB_CAEnumAttrs('interface',SearchEdit.Text,AttrList);
//  Memo1.Lines.Assign(AttrList);
  If AttrList.Count>0 Then
    For I:=0 To AttrList.count-1 Do Begin
      AttrList[I]
    end;
//  sDB_DeleteCursor('interface');
  AttrList.Free;
  CursorStringGridDblClick(Sender);
end;

procedure TDispatcherForm.DisplayConfigBitBtnClick(Sender: TObject);
begin
  ConfigMemo.Lines.Assign(ConfigFile);

end;


procedure TDispatcherForm.CTXCacheListBoxSelectionChange(Sender: TObject;  User: boolean);
Var CacheObject,Link1,Link2:TCacheObject;
    DB:TsDB;Ctx:TsDBCtx;
    B,PB,ConceptID:String;
    M,N,I,J,K,ID:Longint;ClassDesc,Desc,LongValue:TStringList;
    ReadSuccess:Boolean;Addr,DBName,CTXName:String;
begin
  M:=sDBListBox.ItemIndex;
  N:=CTXListBox.ItemIndex;
  If (M<0) Or (N<0) Then Begin DisplayCacheObject('',Nil);Exit;End;
  DB:=(sDBList.Objects[M]) As TsDB;
  DBName:=DB.Name;
  CTX:=(DB.CtxList.Objects[N]) As TsDBCtx;
  CtxName:=CTX.Name;
  If CTXCacheListBox.ItemIndex<0 Then Begin DisplayCacheObject('',Nil);Exit;End;
  ConceptID:=CTXCacheListBox.Items[CTXCacheListBox.ItemIndex];
  ID:=StrToInt(ConceptID);
  If (ID>=Ctx.ConceptTree.NextFree)Or(Ctx.ConceptTree.FreeList.IndexOf(ConceptID)>=0) Then Begin LogMessage(1,'Error: object with ID "'+ConceptID+'" not found');Exit; End;
  CacheObject:=Ctx.ConceptTree.Cache.Objects[CTXCacheListBox.ItemIndex] As TCacheObject;
  DisplayCacheObject(DBName,CacheObject);
end;

Procedure TDispatcherForm.CTXListBoxDblClick(Sender: TObject);
Var N:Longint;DB:TsDB;
    CurrentCtx:String;
begin
  If CtxListBox.ItemIndex<0 Then CurrentCtx:=''
  Else CurrentCTX:=CtxListBox.Items[CtxListBox.ItemIndex];
  N:=sDBListBox.ItemIndex;
  If N<0 Then Begin {и еще стираем отображение кэша} Exit;End;
  DB:=(sDBList.Objects[N]) As TsDB;
  CTXListBox.Items.Assign(DB.CtxList);
  If DB.CtxList.Count=0 Then Exit;
  //восстанавливаем выделение
  N:=CTXListBox.Items.IndexOf(CurrentCTX);
  If N<>-1 Then CTXListBox.ItemIndex:=N Else CTXListBox.ItemIndex:=0;
end;

procedure TDispatcherForm.CTXListBoxSelectionChange(Sender: TObject;  User: boolean);
Var M,N:Longint;CTX:TsDBCtx; DB:TsDB;
begin
  M:=sDBListBox.ItemIndex;
  N:=CTXListBox.ItemIndex;
  If (M<0) Or (N<0) Then Exit;
  DB:=(sDBList.Objects[M]) As TsDB;
  CTX:=(DB.CtxList.Objects[N]) As TsDBCtx;
  CTXCacheListBox.Items.Assign(Ctx.ConceptTree.Cache);
  CTXDirtyCacheListBox.Items.Assign(Ctx.ConceptTree.DirtyCache);
  CtxListBox.Hint:=Ctx.ConceptTree.CachePolicy;
  CtxListBox.ShowHint:=True;
end;

procedure TDispatcherForm.CursorStringGridDblClick(Sender: TObject);
Var I:Longint;
    DBCursor:TsDBCursor;
begin
  CursorStringGrid.RowCount:=CursorList.Count;
  For I:=0 To CursorList.Count-1 Do Begin
    CursorStringGrid.Cells[0,I]:=CursorList[I];
    DBCursor:=CursorList.Objects[I] As TsDBCursor;
    CursorStringGrid.Cells[1,I]:=DBCursor.DBName+' '+DBCursor.CtxName+'/'+DBCursor.ConceptID;
  end;
  CursorStringGrid.AutoSizeColumns;
end;

Procedure TDispatcherForm.CursorStringGridSelection(Sender: TObject; aCol,  aRow: Integer);
Var I:Longint;
    DBCursor:TsDBCursor;
Begin
  DBCursor:=GetCursorByName(CursorStringGrid.Cells[0,aRow]);
  If DBCursor=Nil Then Exit;
  SearchResultStringGrid.RowCount:=DBCursor.SearchResults.Count;
  If SearchResultStringGrid.RowCount>0 Then SearchResultStringGrid.ColCount:=2;
  If DBCursor.SearchResults.Count>0 Then
    For I:=0 To DBCursor.SearchResults.Count-1 Do Begin
      SearchResultStringGrid.Cells[0,I]:=DBCursor.SearchResults[I];
      SearchResultStringGrid.Cells[1,I]:=(DBCursor.SearchResults.Objects[I] As TCacheObject).InternalName;
    End;
end;

procedure TDispatcherForm.CommandFilterEditChange(Sender: TObject);
Var I:Longint;
    Cmds:TStringList;
begin
  Cmds:=Workers.Objects[WorkersGrid.Row-1] As TStringList;
  CmdListBox.Clear;
  If Cmds.Count>0 Then
    For I:=0 To Cmds.Count-1 Do Begin
      If (CommandFilterEdit.Text='') Or (Pos(CommandFilterEdit.Text,Cmds[I])>0) Then
      CmdListBox.Items.Add(Cmds[I]);  //добавить фильтрацию
    end;
end;

procedure TDispatcherForm.ConnectSpeedButtonClick(Sender: TObject);
begin

end;


procedure TDispatcherForm.CTXCacheListBoxClick(Sender: TObject);
begin

end;

Procedure TDispatcherForm.CTXCacheListBoxDblClick(Sender: TObject);
Var M,N:Longint;CTX:TsDBCtx; DB:TsDB;
begin
  M:=sDBListBox.ItemIndex;
  N:=CTXListBox.ItemIndex;
  If (M<0) Or (N<0) Then Exit;
  DB:=(sDBList.Objects[M]) As TsDB;
  CTX:=(DB.CtxList.Objects[N]) As TsDBCtx;
//  ClassesListBox.Items.Assign(Ctx.Hier);
  CTXCacheListBox.Items.Assign(Ctx.ConceptTree.Cache);
  CTXDirtyCacheListBox.Items.Assign(Ctx.ConceptTree.DirtyCache);
end;

procedure TDispatcherForm.ClassesListBoxSelectionChange(Sender: TObject;  User: boolean);
begin

end;

procedure TDispatcherForm.BriefFullToggleChange(Sender: TObject);
Var I:Longint;
begin
  If BriefFullToggle.Checked Then Begin
    BriefFullToggle.Caption:='Brief view';
    WorkersGrid.ColCount:=9;
    WorkersGrid.Cells[4,0]:='QO';
    WorkersGrid.Cells[5,0]:='Run';
    WorkersGrid.Cells[6,0]:='Queue Len';
    WorkersGrid.Cells[7,0]:='Avg s/op';
    WorkersGrid.Cells[8,0]:='ETE';
    For I:=0 To Workers.Count-1 Do Begin
      DispatcherForm.WorkersGrid.Cells[4,I+1]:=QueueOpens.Strings[I];
      DispatcherForm.WorkersGrid.Cells[5,I+1]:=QueueRuns.Strings[I];
      DispatcherForm.WorkersGrid.Cells[6,I+1]:=QueueLengths.Strings[I];
      DispatcherForm.WorkersGrid.Cells[7,I+1]:=AvgSPCs.Strings[I];
      DispatcherForm.WorkersGrid.Cells[8,I+1]:=ETEs.Strings[I];
    End;
  End Else Begin
    BriefFullToggle.Caption:='Full view';
    WorkersGrid.ColCount:=4;
  end;
  DispatcherForm.WorkersGridResize(Sender);
end;

procedure TDispatcherForm.ConnectButtonClick(Sender: TObject);
Var I:Longint;
Begin
  If WorkersGrid.RowCount=1 Then Exit;
  If Workers.Count=0 Then Exit;
  I:=WorkersGrid.Selection.Top;
  If Workers[I-1]=ConfigFile.Values['MyName'] Then Begin
    //disconnect this GUI from remote worker and connect to itself- unregister myself in the list of remote guis on remoteworker
    QueueCommand(RemoteWorker+'::Disconnect GUI:'+ConfigFile.Values['MyName']);
    GUIMode:=gmLocal;
    RemoteWorker:='';
  end Else Begin
    // Connecting to another worker as RemoteGUI
    RemoteWorker:=Workers[I-1];
    DispatcherForm.Caption:=ConfigFile.Values['MyName']+' connected to '+RemoteWorker;
    GUIMode:=gmRemote;
    QueueCommand(RemoteWorker+'::Connect GUI:'+ConfigFile.Values['MyName']);
    //how to connect - register myself in the list of remote guis on remoteworker
  End;
end;

procedure TDispatcherForm.CommandEditKeyDown(Sender: TObject; var Key: Word;  Shift: TShiftState);
Var Sel:TGridRect;
begin
  If HistoryStringGrid.RowCount=1 Then Exit;
  If Key=VK_UP Then Begin
    If CommandEdit.Text='' Then Begin
      Sel.Top:=((HistoryStringGrid.Selection.Top+HistoryStringGrid.RowCount-2) Mod (HistoryStringGrid.RowCount-1))+1;
      Sel.Bottom:=Sel.Top;
      Sel.Left:=0;Sel.Right:=2;
      HistoryStringGrid.Selection:=Sel;
      If HistoryStringGrid.Selection.Top>-1 Then
        CommandEdit.Text:=HistoryStringGrid.Cells[0,HistoryStringGrid.Selection.Top];
    End Else Begin
      Sel.Top:=((HistoryStringGrid.Selection.Top+HistoryStringGrid.RowCount-3) Mod (HistoryStringGrid.RowCount-1))+1;
      Sel.Bottom:=Sel.Top;
      Sel.Left:=0;Sel.Right:=2;
      HistoryStringGrid.Selection:=Sel;
      If HistoryStringGrid.Selection.Top>-1 Then
        CommandEdit.Text:=HistoryStringGrid.Cells[0,HistoryStringGrid.Selection.Top];
    end;
    Key:=0;
  end;
  If Key=VK_Down Then Begin
    Sel.Top:=(HistoryStringGrid.Selection.Top+HistoryStringGrid.RowCount-1) Mod (HistoryStringGrid.RowCount-1)+1;
    Sel.Bottom:=Sel.Top;
    Sel.Left:=0;Sel.Right:=2;
    HistoryStringGrid.Selection:=Sel;
    If HistoryStringGrid.Selection.Top>-1 Then
      CommandEdit.Text:=HistoryStringGrid.Cells[0,HistoryStringGrid.Selection.Top];
    Key:=0;
  end;

end;

procedure TDispatcherForm.CommandEditKeyPress(Sender: TObject; var Key: char);
begin
  If Key=#13 Then Begin
    SubmitButtonClick(Sender);
{    If CommandEdit.Text='' Then Exit;
    If SubmitFButton.Down Then QueueFunction(CommandEdit.Text) Else QueueCommand(CommandEdit.Text);
    CommandEdit.Text:='';}
  end;
end;

procedure TDispatcherForm.FormCreate(Sender: TObject);
begin
 // DispatcherForm.CmdListBox.Items:=Workers.Objects[Workers.IndexOf(ConfigFile.Values['MyName'])] As TStringList;//добавить фильтрацию
 // Application.QueueAsyncCall(@ProcessCommand,0);
  UpdateGUI;
end;

procedure TDispatcherForm.GroupBox5Click(Sender: TObject);
begin

end;

procedure TDispatcherForm.HistoryLengthEditChange(Sender: TObject);
begin
  HistoryStringGrid.RowCount:=HistoryLengthEdit.Value+1;
  HistoryLength:=HistoryLengthEdit.Value;
end;

procedure TDispatcherForm.HistoryStringGridResize(Sender: TObject);
begin
  HistoryStringGrid.AutoSizeColumn(2);
  HistoryStringGrid.ColWidths[0]:=(HistoryStringGrid.ClientWidth-HistoryStringGrid.ColWidths[2]) Div 2;
  HistoryStringGrid.ColWidths[1]:=HistoryStringGrid.ClientWidth-HistoryStringGrid.ColWidths[2]-HistoryStringGrid.ColWidths[0];
end;

procedure TDispatcherForm.InternalNameLabelClick(Sender: TObject);
begin

end;

Procedure TDispatcherForm.LoadCmdsButtonClick(Sender: TObject);
Var Cmds:TStringList;
Begin
  If OpenDialog.Execute Then Begin
    Cmds:=TStringList.Create;
    Cmds.LoadFromFile(OpenDialog.FileName);
    QueueMultipleCommands(Cmds);
  End;
end;

Procedure TDispatcherForm.ImportObjectButtonClick(Sender: TObject);
Var I:Longint;
    DBCursor:TsDBCursor;
    CursorName:String;
Begin
  CursorName:=CursorStringGrid.Cells[0,CursorStringGrid.Selection.Top];
  DBCursor:=GetCursorByName(CursorName);
  If DBCursor=Nil Then Exit;
  If Not FOpenDialog.Execute Then Exit;
  If FOpenDialog.Files.Count>0 Then
    For I:=0 To FOpenDialog.Files.Count-1 Do Begin
      QueueCommand('self::sdb_crimportobject:'+CursorName+'::'+FOpenDialog.Files[I]);
    End;
end;

procedure TDispatcherForm.PageControl2Change(Sender: TObject);
begin

end;

procedure TDispatcherForm.RawStringGridDblClick(Sender: TObject);
Var I,J:Longint;CtxName:String;
begin
  I:=RawStringGrid.Row;
  If I=0 Then Begin RawStringGrid.AutoSizeColumns; Exit; End;
  J:=RawStringGrid.Col;
  If J=3 Then
    If RawStringGrid.Cells[2,I]='.' Then Begin
      CtxName:=Copy(AddrLabel.Caption,Pos(' ',AddrLabel.Caption)+1,Pos('/',AddrLabel.Caption)-Pos(' ',AddrLabel.Caption));
      If Pos('/',RawStringGrid.Cells[3,I])=0 Then
        SearchEdit.Text:=CtxName+RawStringGrid.Cells[3,I]
      Else SearchEdit.Text:=RawStringGrid.Cells[3,I];
      FindByAddrButtonClick(Sender);
      SearchResultStringGridSelection(Sender, 0, 0);
    end;
end;

procedure TDispatcherForm.sDBListBoxClick(Sender: TObject);
Var CurrentDB,CurrentCtx:String;N:Longint;
begin
  If sDBList.Count=0 Then Begin
    CtxCacheListBox.Clear;
    CtxDirtyCacheListBox.Clear;
    CtxListBox.Clear;
    Exit;
  end;
  If sDBListBox.ItemIndex<0 Then CurrentDB:=''
  Else CurrentDB:=sDBListBox.Items[sDBListBox.ItemIndex];
  If CTXListBox.ItemIndex>=0 Then
    CurrentCtx:=CTXListBox.Items[CTXListBox.ItemIndex]
  Else CurrentCtx:='';
  sDBListBox.Items.Assign(sDBList);
  //восстанавливаем выделение
  If CurrentDB='' Then N:=-1
  Else N:=sDBListBox.Items.IndexOf(CurrentDB);
  If N<>-1 Then sDBListBox.ItemIndex:=N Else sDBListBox.ItemIndex:=0;
  If CurrentCTX<>'' Then Begin
    N:=CTXListBox.Items.IndexOf(CurrentCTX);
    If N<>-1 Then CTXListBox.ItemIndex:=N Else CTXListBox.ItemIndex:=0;
  End;

end;

procedure TDispatcherForm.sDBListBoxSelectionChange(Sender: TObject;  User: boolean);
Var N:Longint;DB:TsDB;
begin
  N:=sDBListBox.ItemIndex;
  If N<0 Then Exit;
  DB:=(sDBList.Objects[N]) As TsDB;
  CTXListBox.Items.Assign(DB.CtxList);
end;

procedure TDispatcherForm.Panel3Resize(Sender: TObject);
begin
  CommandResult.Height:=Panel3.ClientHeight Div 2;
  CommandEdit.Height:=Panel3.ClientHeight Div 2;
end;

Procedure TDispatcherForm.RunButtonClick(Sender: TObject);
Var Index:Longint;
Begin
  DoMultipleSteps:=True;
  Run:=True;
  Application.QueueAsyncCall(@ProcessCommand,0);
  Index:=Workers.IndexOf(ConfigFile.Values['MyName']);
  QueueRuns[Index]:='Run';
  //послать свежий stat
  updategui;
  sendstat;
  Application.QueueAsyncCall(@ProcessCommand,0);
end;

Procedure TDispatcherForm.LogLevelEditChange(Sender: TObject);
Begin
  Case LogLevelEdit.Value of
  0: DescrLabel.Caption:='exceptions';
  1: DescrLabel.Caption:='exceptions and errors';
  2: DescrLabel.Caption:='exceptions, errors and logical bugs';
  3: DescrLabel.Caption:='exceptions, errors, logical bugs, debug messages';
  4: DescrLabel.Caption:='exceptions, errors, logical bugs, debug messages, routine';
  End;
  LogLevel:=LogLevelEdit.Value;
end;

procedure TDispatcherForm.SaveCmdsButtonClick(Sender: TObject);
begin
  If SaveDialog.Execute Then HistoryStringGrid.Cols[0].SaveToFile(SaveDialog.FileName);
end;

Procedure TDispatcherForm.qSearchResultStringGridClick(Sender: TObject);
Begin

End;

procedure TDispatcherForm.SearchResultStringGridClick(Sender: TObject);
begin
  SearchResultStringGridSelection(Sender, 0, 0);
end;

procedure TDispatcherForm.SearchResultStringGridResize(Sender: TObject);
begin
end;

procedure TDispatcherForm.SearchResultStringGridSelection(Sender: TObject; aCol, aRow: Integer);
Var CacheObject,Link1,Link2:TCacheObject;
    DB:TsDB;Ctx:TsDBCtx;DBCursor:TsDBCursor;
    B,PB,ConceptID,Error:String;
    M,N,I,J,K,ID:Longint;ClassDesc,Desc,LongValue:TStringList;
    ReadSuccess:Boolean;Addr,CursorName,DBName,CTXName:String;
begin
  CursorName:=CursorStringGrid.Cells[0,CursorStringGrid.Selection.Top];
  DBCursor:=GetCursorByName(CursorName);
  If DBCursor=Nil Then Exit;
  DB:=DBCursor.DB;DBName:=DB.Name;
  CTX:=DBCursor.Ctx;CtxName:=CTX.Name;

  If SearchResultStringGrid.Selection.Top<0 Then Begin DisplayCacheObject('',Nil);Exit;End;
  ConceptID:=SearchResultStringGrid.Cells[0,SearchResultStringGrid.Selection.Top];
  If ConceptID='' Then Exit;
  CTXName:=Copy(ConceptId,1,Pos('/',ConceptId)-1);
  ConceptID:=Copy(ConceptId,Pos('/',ConceptId)+1,Length(ConceptID)-Pos('/',ConceptID));
  Error:=LookupFQ(DBName,CtxName,DB,Ctx);
  If Error<>'Ok' Then Begin LogMessage(1,Error);Exit;End;
  ID:=StrToInt(ConCeptID);
  If (ID>=Ctx.ConceptTree.NextFree)Or(Ctx.ConceptTree.FreeList.IndexOf(ConceptID)>=0) Then Begin LogMessage(1,'Error: object with ID "'+ConceptID+'" not found');Exit; End;
  CacheObject:=sDB_FQGetConceptHandleByID(DBName,CtxName,ConceptID);
  DisplayCacheObject(DBName,CacheObject);
end;

procedure TDispatcherForm.SetCursorButtonClick(Sender: TObject);
Var CursorName,DBName,CtxName:String;
begin
  If CtxListBox.ItemIndex<0 Then CtxName:='' Else CTXName:=CtxListBox.Items[CtxListBox.ItemIndex];
  If sDBListBox.ItemIndex<0 Then DBName:='' Else DBName:=sDBListBox.Items[sDBListBox.ItemIndex];
  CursorName:=CursorStringGrid.Cells[0,CursorStringGrid.Selection.Top];
  Dispatcher.ExecCommand('self::sDB_SetCursor:'+CursorName+':'+DBName+':'+CtxName+'/');
end;

procedure TDispatcherForm.StatTimerTimer(Sender: TObject);
begin
  Application.QueueAsyncCall(@DispatcherForm.ProcessCommand,0);
end;



procedure TDispatcherForm.StringGrid2DblClick(Sender: TObject);
Var I,J:Longint;CtxName:String;
begin
  I:=StringGrid2.Row;
  If I=0 Then Begin StringGrid2.AutoSizeColumns; Exit; End;
  J:=StringGrid2.Col;
   If J=2 Then Begin
     CtxName:=Copy(AddrLabel.Caption,Pos(' ',AddrLabel.Caption)+1,Pos('/',AddrLabel.Caption)-Pos(' ',AddrLabel.Caption));
{    Link1:=SlideByKey(SourceCacheObject,AttrName);
  If Link1<>Nil Then Begin
    Link2:=SlideByKey(Link1,AttrName);
    Link2.InternalName:=Value;
    FlushCacheObject(Link2);
    Result:='Ok';}

//     If Pos('/',)
     SearchEdit.Text:=StringGrid2.Cells[2,I];
     FindByAddrButtonClick(Sender);
     SearchResultStringGridSelection(Sender, 0, 0);
   end;
end;

procedure TDispatcherForm.StringGrid2HeaderClick(Sender: TObject;  IsColumn: Boolean; Index: Integer);
begin
  StringGrid2.AutoSizeColumns;
end;

procedure TDispatcherForm.SubmitFButtonClick(Sender: TObject);
begin
  If CommandEdit.Text='' Then Exit;
  QueueFunction(CommandEdit.Text);
  CommandEdit.Text:='';
end;

Procedure TDispatcherForm.StepButtonClick(Sender: TObject);
Var Index:Longint;
Begin
  DoMultipleSteps:=False;
  Run:=True;
  RunButton.Down:=False;
  StopButton.Down:=True;
  Index:=Workers.IndexOf(ConfigFile.Values['MyName']);
  QueueRuns[Index]:='Stop';
  updategui;
  sendstat;
  Application.QueueAsyncCall(@ProcessCommand,0);
end;

Procedure TDispatcherForm.StopButtonClick(Sender: TObject);
Var Index:Longint;
Begin
  DoMultipleSteps:=False;
  Run:=False;
  Index:=Workers.IndexOf(ConfigFile.Values['MyName']);
  QueueRuns[Index]:='Stop';
  //послать свежий stat
  updategui;
  sendstat;
  Application.QueueAsyncCall(@ProcessCommand,0);
end;

procedure TDispatcherForm.SubmitButtonClick(Sender: TObject);
begin
  If CommandEdit.Text='' Then Exit;
  If SubmitFButton.Down Then QueueFunction(CommandEdit.Text) Else QueueCommand(CommandEdit.Text);
  CommandList.Items.Assign(Dispatcher.CommandQueue);
  CommandResult.Text:='...';
  CommandTimeLabel.Caption:='...';
  CommandEdit.Text:='';
end;

procedure TDispatcherForm.TurboButtonClick(Sender: TObject);
begin
  TurboButtonDown:=TurboButton.Down;
end;

procedure TDispatcherForm.UpdateBitBtnClick(Sender: TObject);
Var I:Longint;
    WaiterList,LockList:TStringList;
    LL:String;
begin
  WaiterListBox.Clear;
  LockListBox.Clear;
  If Waiters.Count>0 Then
    For I:=0 To Waiters.Count-1 Do Begin
      If Waiters.Objects[I]<>Nil Then Begin
        LockList:=Waiters.Objects[I] As TStringList;
        LL:=LockList.CommaText;
      end Else LL:='';
      WaiterListBox.Items.Add(Waiters[I]+'         '+LL);
    end;
  If Locks.Count>0 Then
    For I:=0 To Locks.Count-1 Do Begin
      If Locks.Objects[I]<>Nil Then Begin
        WaiterList:=Locks.Objects[I] As TStringList;
        LL:=WaiterList.CommaText;
      end Else LL:='';
      LockListBox.Items.Add(Locks[I]+'         '+LL);
   end;

end;

Procedure TDispatcherForm.WorkersGridDblClick(Sender: TObject);
Begin
  ConnectButtonClick(Sender);
end;

procedure TDispatcherForm.WorkersGridResize(Sender: TObject);
begin
  If BriefFullToggle.Checked Then Begin
    WorkersGrid.AutoSizeColumn(0);
    WorkersGrid.AutoSizeColumn(3);
    WorkersGrid.AutoSizeColumn(4);
    WorkersGrid.AutoSizeColumn(5);
    WorkersGrid.AutoSizeColumn(6);
    WorkersGrid.AutoSizeColumn(7);
    WorkersGrid.AutoSizeColumn(8);
    WorkersGrid.ColWidths[1]:=(WorkersGrid.ClientWidth-WorkersGrid.ColWidths[0]-WorkersGrid.ColWidths[3]-WorkersGrid.ColWidths[4]-WorkersGrid.ColWidths[5]-WorkersGrid.ColWidths[6]-WorkersGrid.ColWidths[7]-WorkersGrid.ColWidths[8]) Div 2;
    WorkersGrid.ColWidths[2]:=WorkersGrid.ColWidths[1];
  end else Begin
    WorkersGrid.AutoSizeColumn(0);
    WorkersGrid.AutoSizeColumn(3);
    WorkersGrid.ColWidths[1]:=(WorkersGrid.ClientWidth-WorkersGrid.ColWidths[0]-WorkersGrid.ColWidths[3]) Div 2;
    WorkersGrid.ColWidths[2]:=WorkersGrid.ColWidths[1];
  end;
end;

procedure TDispatcherForm.WorkersGridSelectCell(Sender: TObject; aCol,  aRow: Integer; var CanSelect: Boolean);
Var I:Longint;Cmds:TStringList;Clip:String;
begin
  If WorkersGrid.RowCount=1 Then Exit;
  If Workers.Count=0 Then Exit;
  If ARow-1<Workers.Count-1 Then
    If (aRow<0) Or (aRow-1>=Workers.Count-1) Then Begin LogMessage(1,'Unexisting worker selected '+IntToStr(aRow-1));Exit;End;
    If Workers.Objects[aRow-1]<>Nil Then Begin
      LogMessage(5,'Displaying command list #'+IntToStr(aRow-1));
      Cmds:=Workers.Objects[aRow-1] As TStringList;
      CmdListBox.Clear;
      If Cmds.Count>0 Then
        For I:=0 To Cmds.Count-1 Do Begin
        If (CommandFilterEdit.Text='')Or(Pos(CommandFilterEdit.Text,Cmds[I])>0) Then Begin
          Clip:=Cmds[I];
          If Aliases.IndexOf(Cmds[I])<0 Then Begin
            Delete(Clip,1,4);
            CmdListBox.Items.Add(Workers[aRow-1]+Clip);
          End Else
            CmdListBox.Items.Add(Clip);
        end;
      end;
    end;
end;

procedure TDispatcherForm.WriteValueButtonClick(Sender: TObject);
Var SearchRes:TStringList;
    I,M,N:Longint;
    CursorName,Value:String;
    CacheObject:TCacheObject;
    Buf:TStringList;
begin
  If EvalMemo.Lines.Count=0 Then Exit;
  CursorName:=CursorStringGrid.Cells[0,CursorStringGrid.Selection.Top];
//  If EvalMemo.Lines.Count=1 Then Value:=EvalMemo.Lines[0];
  Buf:=TStringList.Create;
  Buf.Assign(EvalMemo.Lines);
  sDB_CAWriteValue(CursorName,SearchEdit.Text,KeyEdit.Text,'',Buf);
  Buf.Free;
  CursorStringGridSelection(Nil, CursorStringGrid.Col, CursorStringGrid.Row);
end;


Initialization
  {$I dispatcher_iface.lrs}
  GUIMode:=gmLocal;
  RemoteWorkers:=TStringList.Create;
  RemoteRoles:=TStringList.Create;
  RemoteConnections:=TStringList.Create;
  RemoteConnectionAddrs:=TStringList.Create;
  RemoteQueueOpens:=TStringList.Create;
  RemoteQueueRuns:=TStringList.Create;
  RemoteQueueLengths:=TStringList.Create;
  RemoteAvgSPCs:=TStringList.Create;
  RemoteETEs:=TStringList.Create;
  RemoteCommandQueue:=TStringList.Create;
  RemoteHistoryC:=TStringList.Create;
  RemoteHistoryT:=TStringList.Create;
  RemoteHistoryR:=TStringList.Create;
  RemoteOther:=TStringList.Create;
  DecimalSeparator:='.';
End.

