unit AcMain;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ToolWin, AcInput, Menus, AcStruc, StdActns, acTblFrm,
  ExtCtrls, ImgList, ActnList, ComCtrls, acBase, MTEApi, mteTypes, StdCtrls,
  System.ImageList, System.Actions;

type
  TMTEConnectList = class(TList)
  private
    function GetConnect(AIndex: Integer): TMTEConnect;
  public
    function Add(AHandle: Integer; AIface: TMicexIface;
      const ACaption: string; ATree: TTreeView): TMTEConnect;
    procedure Clear; override;
    procedure Delete(AIndex: Integer);
    property Connects[AIndex: Integer]: TMTEConnect read GetConnect; default;
  end;

  TMainForm = class(TBaseForm)
    Actions: TActionList;
    TreeImages: TImageList;
    ObjSplitter: TSplitter;
    StatusBar: TStatusBar;
    tbMain: TToolBar;
    tb1: TToolButton;
    tb2: TToolButton;
    tb3: TToolButton;
    actConnect: TAction;
    actDisconnect: TAction;
    actRefreshTable: TAction;
    ToolButton10: TToolButton;
    MainMenu: TMainMenu;
    miFile: TMenuItem;
    Connect1: TMenuItem;
    Disconnect1: TMenuItem;
    N2: TMenuItem;
    actSessionExit: TAction;
    N3: TMenuItem;
    actAbout: TAction;
    WindowArrange: TWindowArrange;
    WindowCascade: TWindowCascade;
    WindowClose: TWindowClose;
    WindowMinimizeAll: TWindowMinimizeAll;
    WindowTileHorizontal: TWindowTileHorizontal;
    WindowTileVertical: TWindowTileVertical;
    miWindow: TMenuItem;
    Cascade1: TMenuItem;
    TileHorizontally1: TMenuItem;
    TileVertically1: TMenuItem;
    Arrange1: TMenuItem;
    MinimizeAll1: TMenuItem;
    N6: TMenuItem;
    Close1: TMenuItem;
    WindowCloseAll: TAction;
    N7: TMenuItem;
    miAction: TMenuItem;
    OpenTable1: TMenuItem;
    RefreshTable1: TMenuItem;
    tbRefresh: TToolButton;
    RefreshTimer: TTimer;
    N10: TMenuItem;
    miCompleteOpen: TMenuItem;
    miFreeBuf: TMenuItem;
    actSnapshot: TAction;
    N4: TMenuItem;
    actRestore: TAction;
    Snapshot1: TMenuItem;
    CoolBar1: TCoolBar;
    miHelp: TMenuItem;
    N1: TMenuItem;
    BigCmd: TImageList;
    BigCmdGray: TImageList;
    actExecute: TAction;
    pmRefreshRates: TPopupMenu;
    miStopRefresh: TMenuItem;
    miOneSecond: TMenuItem;
    mi3sec: TMenuItem;
    mi5sec: TMenuItem;
    mi10sec: TMenuItem;
    mi1min: TMenuItem;
    mi30sec: TMenuItem;
    Bevel1: TBevel;
    actGetExtData: TAction;
    N8: TMenuItem;
    SaveDlg: TSaveDialog;
    miSendIp: TMenuItem;
    KeepAliveTimer: TTimer;
    actExport: TAction;
    SaveHtmlDlg: TSaveDialog;
    N9: TMenuItem;
    BasePanel: TPanel;
    SessionPanel: TPanel;
    cbSessions: TComboBox;
    miView: TMenuItem;
    miShowIds: TMenuItem;
    miShowNames: TMenuItem;
    miShowAll: TMenuItem;
    ExportDlg: TSaveDialog;
    actDllVer: TAction;
    MTESRLDLL1: TMenuItem;
    N12: TMenuItem;
    actShowCert: TAction;
    N13: TMenuItem;
    actServInfo: TAction;
    N14: TMenuItem;
    actLogon: TAction;
    N15: TMenuItem;
    N16: TMenuItem;
    N17: TMenuItem;
    miExecTransEx: TMenuItem;
    miSendSysTime: TMenuItem;
    procedure actRefreshTableExecute(Sender: TObject);
    procedure actDisconnectExecute(Sender: TObject);
    procedure actConnectExecute(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure WindowCloseAllExecute(Sender: TObject);
    procedure actAboutExecute(Sender: TObject);
    procedure RefreshTimerTimer(Sender: TObject);
    procedure miActionClick(Sender: TObject);
    procedure miCompleteOpenClick(Sender: TObject);
    procedure miFreeBufClick(Sender: TObject);
    procedure actSnapshotExecute(Sender: TObject);
    procedure actRestoreExecute(Sender: TObject);
    procedure actSessionExitExecute(Sender: TObject);
    procedure ActionsUpdate(Action: TBasicAction;
      var Handled: Boolean);
    procedure FormCreate(Sender: TObject);
    procedure actExecuteExecute(Sender: TObject);
    procedure RefreshRateClick(Sender: TObject);
    procedure pmRefreshRatesPopup(Sender: TObject);
    procedure ObjTreeChange(Sender: TObject; Node: TTreeNode);
    procedure ObjTreeDblClick(Sender: TObject);
    procedure actGetExtDataExecute(Sender: TObject);
    procedure miSendIpClick(Sender: TObject);
    procedure KeepAliveTimerTimer(Sender: TObject);
    procedure actExportExecute(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure SessionChange(Sender: TObject);
    procedure SessionPanelResize(Sender: TObject);
    procedure cbSessionsDrawItem(Control: TWinControl; AIndex: Integer;
      Rect: TRect; State: TOwnerDrawState);
    procedure miShowAllClick(Sender: TObject);
    procedure actDllVerExecute(Sender: TObject);
    procedure StatusBarDblClick(Sender: TObject);
    procedure actShowCertExecute(Sender: TObject);
    procedure actServInfoExecute(Sender: TObject);
    procedure actLogonExecute(Sender: TObject);
    procedure miExecTransExClick(Sender: TObject);
    procedure miSendSysTimeClick(Sender: TObject);
  private
    FCompleteOpen: Boolean;
    FFreeBuffer: Boolean;
    FSendClientIp: Boolean;
    fExtendedTransApi: Boolean;
    fSendSysTime: Boolean;
    FConnects: TMTEConnectList;
    FTreeButtonClick: Boolean;
    FTreeButtonClickTime,
    FLastRequestTime: DWORD;
    FModifyTime: Boolean;
    FLanguage: string;
    procedure FreeBuffer(Connect: TMTEConnect);
    procedure SaveRequestParams(Connect: TMTEConnect; Msg: TMicexMessage; const Params: string);
    procedure PlantTrees(Connect: TMTEConnect);
    procedure ShowTreeHint(Node: TTreeNode);
    procedure SaveExtData(Data: TMemoryStream; const FileName: string);
    function GetActiveConnect: TMTEConnect;
    procedure CloseWindows(Connect: TMTEConnect);
    procedure ShowStats;
    procedure TreeViewExpanding(Sender: TObject; Node: TTreeNode;
      var AllowExpansion: Boolean);
    procedure TreeViewMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure InvalidateWindows;
    function ConnectToServer(const Arguments: TStrings; UseSoftConnect: Boolean): TMTEConnect;
    procedure ShowCertificates;
    function DownloadStructure(Idx: Integer; AConnect: TMTEConnect; const Language: string): TMicexIface;
    procedure DownloadDictionary(Connect: TMTECOnnect);
    procedure Logon;
    function IsCmdSwitch(const S: string): Boolean;
    procedure SetSendClientIp(const Value: Boolean);
    procedure SetExtendedTransApi(const Value: Boolean);
    property SendClientIp: Boolean read fSendClientIp write SetSendClientIp;
    property ExtendedTransApi: Boolean read fExtendedTransApi write SetExtendedTransApi;
  protected
    procedure Loaded; override;
  public
    procedure OpenTable(Connect: TMTEConnect; Tbl: TMicexMessage; FromMsg: PBaseMsgRec);
    procedure ExecuteTransaction(Connect: TMTEConnect; Msg: TMicexMessage; FromMsg: PBaseMsgRec);
    procedure CutTrees(Connect: TMTEConnect);
    procedure ShowStatus(const Msg: string);
    function ValidateConnect(Connect: TMTEConnect; ErrorCode: Integer = MTE_OK): Boolean;
  end;

var
  MainForm: TMainForm;

implementation

uses
  AcLogon, ShellApi, IniFiles, acExport, ExtUtils, acCertInfo,
  System.UITypes, System.Types;

{$R *.DFM}

const
  ntTables       = 0;
  ntTransactions = 1;
  ntTable        = 2;
  ntTransaction  = 3;
  ntInFields     = 4;
  ntOutFields    = 5;
  ntInField      = 6;
  ntOutField     = 7;
  ntEnumTypes    = 8;
  ntEnumType     = 9;
  ntIface        = 10;
  ntUpdateTable  = 20;
  ntKeyField     = 14;
  ntEnumConst    = 10;

resourcestring
  rsConnecting        = 'Connecting to ASTS...';
  rsLoadingStructure  = 'Loading market definition...';
  rsLoadingSecs       = 'Loading SECURITIES table...';
  rsLoadingFirms      = 'Loading FIRMS table...';

const
  sRefreshErrors: array [Boolean] of string = ('Соединение разорвано.', 'Обновление таблиц приостановлено.');

{ Common routines }

{ TMainForm }

procedure TMainForm.FreeBuffer(Connect: TMTEConnect);
begin
  if FFreeBuffer then MTEFreeBuffer(Connect.FIdx);
end;

procedure TMainForm.SaveRequestParams(Connect: TMTEConnect; Msg: TMicexMessage; const Params: string);
const
  Prefixes: array [Boolean] of string = ('trn_', 'tbl_');
var
  I: Integer;
  PlainParams: string;
begin
  with TIniFile.Create(IniFileName) do
  try
    PlainParams := '';
    for I := 1 to Length(Params) do
      case Params[I] of
        #0..#31: PlainParams := PlainParams + '%' + IntToHex(Byte(Params[I]), 2);
        '%': PlainParams := PlainParams + '%%';
        else PlainParams := PlainParams + Params[I];
      end;

    WriteString('ifc_' + Connect.FIface.Name, Prefixes[Msg.IsTable] + Msg.Name,
      AnsiQuotedStr(PlainParams, '"'));
  finally
    Free;
  end;
end;

procedure TMainForm.ExecuteTransaction(Connect: TMTEConnect; Msg: TMicexMessage; FromMsg: PBaseMsgRec);
var
  Params: string;
  ErrMsg: TMTEErrorMsg;
  Err, IpAddress: Integer;
  Reply: TMTEExecTransResult;
begin
  with Msg do
    if GetInputParams(Msg, GetDisplayName(Msg, True), InFields.List, Connect.FIface,
      Params, FromMsg, Connect.FSecList, Connect.FSecCodeList) then
  begin
    SaveRequestParams(Connect, Msg, Params);
    FLastRequestTime := GetTickCount;
    if SendClientIp then IpAddress := $0100007f {127.0.0.1} else IpAddress := 0;
    if (msaExecTransEx in GetMTESrlAddons) and ExtendedTransApi then begin
      Err := MTEExecTransEx(Connect.FIdx, PAnsiChar(AnsiString(Msg.Name)), PAnsiChar(AnsiString(Params)), IpAddress, Reply);
      ValidateConnect(Connect, Err);
      ShowTransExResult(Connect, Err, Reply, Format('MTEExecTrans(%s)', [Msg.Name]));
    end else begin
      if SendClientIp then
        Err := MTEExecTransIP(Connect.FIdx, PAnsiChar(AnsiString(Msg.Name)), PAnsiChar(AnsiString(Params)), @ErrMsg, IpAddress)
      else
        Err := MTEExecTrans(Connect.FIdx, PAnsiChar(AnsiString(Msg.Name)), PAnsiChar(AnsiString(Params)), @ErrMsg);
      ValidateConnect(Connect, Err);
      ShowError(Connect, Err, string(ErrMsg), Format('MTEExecTrans(%s)', [Msg.Name]));
    end;
    FreeBuffer(Connect);
  end;
end;

procedure TMainForm.OpenTable(Connect: TMTEConnect; Tbl: TMicexMessage; FromMsg: PBaseMsgRec);
var
  Params: string;
  Err, Index, SysIdx: Integer;
  Data: PMTEMsg;
begin
  if GetInputParams(Tbl, GetDisplayName(Tbl, True), Tbl.InFields.List, Connect.FIface,
    Params, FromMsg, Connect.FSecList, Connect.FSecCodeList) then
  begin
    SaveRequestParams(Connect, Tbl, Params);

    SysIdx := Tbl.SystemIndex;
    Screen.Cursor := crHourGlass;
    try
      FLastRequestTime := GetTickCount;
      Err := MTEOpenTable(Connect.FIdx, PAnsiChar(AnsiString(Tbl.Name)), PAnsiChar(AnsiString(Params)), FCompleteOpen, Data);

      ShowStatus(Format('MTEOpenTable(%s): %d ms', [Tbl.Name, GetTickCount - FLastRequestTime]));
      if Err < MTE_OK then
      begin
        ValidateConnect(Connect, Err);
        ShowError(Connect, Err, GetTsmrStr(Err, Data), Format('MTEOpenTable(%s)', [Tbl.Name]));
      end
      else
        with TTableForm.Create(Self) do
        begin
          if Trim(Params) <> '' then
          begin
            if Tbl.IsOrderbook then
              if Connect.FSecList.Find(Params, Index) then
                with Connect.FSecList[Index]^ do
                  FmtStr(Params, '%s (%s)', [ShortName, Trim(SecCode)]);

            Caption := GetDisplayName(Tbl, True) + ' - ' + Params
          end
          else
            Caption := GetDisplayName(Tbl, True);
          if Assigned(Connect) then
            SetupStatics(Connect, Tbl, @Data.Data, Err, Connect.FSecList, Connect.FCaption, SysIdx);
        end;
      FreeBuffer(Connect);
      ShowStats;
    finally
      Screen.Cursor := crDefault;
    end;
  end;
end;

procedure TMainForm.actRefreshTableExecute(Sender: TObject);
begin
  RefreshTimerTimer(Self);
end;

procedure TMainForm.PlantTrees(Connect: TMTEConnect);

  function AddNode(Root: TTreeNode; const ACaption: string; Obj: TObject; Image: Integer): TTreeNode;
  begin
    Result := Connect.FTree.Items.AddChildObject(Root, ACaption, Obj);
    Result.ImageIndex := Image;
    Result.SelectedIndex := Image;
  end;

  procedure PlantEnum(Root: TTreeNode; aType: TMicexEnumType);
  var
    i: Integer;
  begin
    for i := 0 to aType.GetConstCount - 1 do
      AddNode(Root, aType.GetConstAndName(i), nil, ntEnumConst);
  end;

  procedure PlantEnums(Root: TTreeNode; Types: TMicexEnumTypes);
  var
    I: Integer;
  begin
    for I := 0 to Types.Count - 1 do
      PlantEnum(AddNode(Root, GetDisplayName(Types[I], True), Types[I], ntEnumType), Types[i]);
  end;

  procedure PlantFields(Root: TTreeNode; Fields: TMicexFields);
  var
    I, Idx, Image: Integer;
  begin
    if Fields.IsInput then Idx := ntInField else Idx := ntOutField;
    for I := 0 to Fields.Count - 1 do
    begin
      if Fields[I].Key then Image := ntKeyField else Image := Idx;
      AddNode(Root, GetDisplayName(Fields[I], True), Fields[I], Image);
    end;
  end;

  procedure PlantMessages(Root: TTreeNode; Messages: TMicexMessages);
  var
    I, Idx: Integer;
    Node: TTreeNode;
    S: string;
  begin
    for I := 0 to Messages.Count - 1 do
    begin
      if Messages.IsTable then
        if Messages[I].Updateable then Idx := ntUpdateTable
        else Idx := ntTable
      else Idx := ntTransaction;

      if Messages[i].SystemIndex <> 0 then
        S := Format(' : %d', [Messages[i].SystemIndex])
      else S := '';
      S := GetDisplayName(Messages[I], True) + S;
      Node := AddNode(Root, S, Messages[I], Idx);
      with Messages[I] do
      begin
        if InFields.Count > 0 then
          PlantFields(AddNode(Node, 'Запрос', InFields, ntInFields), InFields);
        if IsTable then
          PlantFields(AddNode(Node, 'Ответ', OutFields, ntOutFields), OutFields);
      end;
    end;
  end;

var
  Tree: TTreeView;
begin
  Tree := Connect.FTree;
  Tree.Items.BeginUpdate;
  try
    Tree.Items.Clear;
    if Assigned(Connect.FIface) then
      with Connect.FTree, Connect.FIface do
      begin
        PlantEnums(AddNode(nil, 'Перечислимые типы', Types, ntEnumTypes), Types);
        PlantMessages(AddNode(nil, 'Таблицы', Tables, ntTables), Tables);
        PlantMessages(AddNode(nil, 'Транзакции', Transactions, ntTransactions), Transactions);
      end
    else
      AddNode(nil, 'Not logged on', nil, -1);
  finally
    Tree.Items.EndUpdate;
  end;
end;

procedure TMainForm.CutTrees(Connect: TMTEConnect);
begin
{  with Connect.FTree.Items do
  begin
    BeginUpdate;
    Clear;
    EndUpdate;
  end;}
  ShowStatus('');
end;

procedure TMainForm.CloseWindows(Connect: TMTEConnect);
var
  I: Integer;
begin
  for I := 0 to MDIChildCount - 1 do
    if MDIChildren[I] is TTableForm then
      with TTableForm(MDIChildren[I]) do
        if (Connect.FIdx = ConnectIdx) then Close;
end;

procedure TMainForm.actDisconnectExecute(Sender: TObject);
var
  Connect: TMTEConnect;
  Panel: TPanel;
  OldIndex: Integer;
begin
  Connect := GetActiveConnect;
  if Assigned(Connect) then
  begin
//    RefreshTimer.Enabled := False;
    Panel := TPanel(cbSessions.Items.Objects[cbSessions.ItemIndex]);
    CloseWindows(Connect);
    FConnects.Remove(Connect);

    if Assigned(Panel) then
    begin
      if cbSessions.Items.Count = 1 then
      begin
        BasePanel.Visible := False;
        ObjSplitter.Visible := False;
      end;
      Panel.Free;
      with cbSessions do
      begin
        OldIndex := ItemIndex;
        Items.Delete(OldIndex);
        if OldIndex >= Items.Count then OldIndex := Items.Count - 1;
        ItemIndex := OldIndex;
      end;
      SessionChange(cbSessions);
    end;
    Connect.Free;
  end;
end;

function GetIfaceCaption(Iface: TMicexIface; const Params: TStrings): string;
var
  P1, P2, IfaceCaption, IfaceName: string;
begin
  if not Assigned(Params) or (Params.Count = 0) then Result := 'New Iface'
  else
  begin
    if Params.IndexOfName('SERVER') >= 0 then
    begin
      P1 := Params.Values['SERVER'];
      P2 := Params.Values['SERVICE'];
    end
    else
      if Params.IndexOfName('PORT') >= 0 then
      begin
        P1 := Params.Values['PORT'];
        P2 := Params.Values['BAUDRATE'];
      end
      else
        begin
          P1 := Params.Values['HOST'];
          if Params.IndexOfName('SERVICE') >= 0 then
            P2 := Params.Values['SERVICE']
          else
            P2 := Params.Values['PIPE'];
        end;

    if Assigned(Iface) then
    begin
      IfaceName := Iface.Name;
      IfaceCaption := Iface.Caption;
    end
    else
    begin
      IfaceName := 'Не зарегистрирован';
      IfaceCaption := '';
    end;
    if IfaceCaption <> '' then IfaceCaption := ' - ' + IfaceCaption;
    if Params.Values['USERID'] <> '' then
      Result := Format('%s [%s%s]', [Params.Values['USERID'], IfaceName, IfaceCaption])
    else
      Result := Format('%s:%s [%s%s]', [P1, P2, IfaceName, IfaceCaption]);

    if Iface.MsgSet <> '' then
      Result := Format('%s, msgset %s', [Result, Iface.MsgSet]);
  end;
end;

function TMainForm.DownloadStructure(Idx: Integer; AConnect: TMTEConnect; const Language: string): TMicexIface;
var
  Err, StructureVersion: Integer;
  StructErrMsg: string;
  Data: PMTEMsg;
  Buf: PByte;
  {$ifopt C+}
  StructureEnd: PByte;
  {$endif}
begin
  Result := nil;
  ShowStatus(rsLoadingStructure);
  Data := nil;

  // попробуем сначала получить самую современную структуру самой продвинутой функцией
  if msaStructureEx in GetMTESrlAddons then begin
    StructureVersion := STRUCTURE_VERSION_4 or STRUCTURE_LOCALIZATION;
    Err := MTEStructureEx(Idx, StructureVersion, Data);

    if Err = MTE_NOTIMPLEMENTED then begin
      StructureVersion := STRUCTURE_VERSION_3 or STRUCTURE_LOCALIZATION;
      Err := MTEStructureEx(Idx, StructureVersion, Data);
    end;

    if Err = MTE_NOTIMPLEMENTED then begin
      StructureVersion := STRUCTURE_VERSION_3;
      Err := MTEStructureEx(Idx, StructureVersion, Data);
    end;
  end else begin
    StructureVersion := STRUCTURE_VERSION_0;
    Err := MTE_NOTIMPLEMENTED;
  end;

  // если продвинутая функция не поддерживается, попробуем более старую функцию
  if (Err = MTE_NOTIMPLEMENTED) and (msaStructure2 in GetMTESrlAddons) then begin
    StructureVersion := STRUCTURE_VERSION_2;
    Err := MTEStructure2(Idx, Data);
  end;

  // если работает совсем со старой DLL, используем легаси функцию
  if Err = MTE_NOTIMPLEMENTED then begin
    StructureVersion := STRUCTURE_VERSION_1;
    Err := MTEStructure(Idx, Data);
  end;
  if Err <> MTE_OK then
  begin
    StructErrMsg := GetTsmrStr(Err, Data);
    if Assigned(AConnect) then AConnect.FDisconnected := True
    else MTEDisconnect(Idx);
    cbSessions.Invalidate;
    ShowError(AConnect, Err, StructErrMsg, 'MTEStructure');
  end
  else
    begin
      Result := TMicexIface.Create;
      Buf := @Data.Data;
      {$ifopt C+}
      StructureEnd := Buf + Data.DataLen;
      {$endif}
      Result.LoadFromBuf(Buf, StructureVersion, LowerCase(Copy(Language, 1, 2)) + ':');
      {$ifopt C+}
      Assert(Buf = StructureEnd, 'MTEStructure parsed incorrectly');
      {$endif}
    end;
end;

procedure TMainForm.DownloadDictionary(Connect: TMTECOnnect);
begin
  ShowStatus(rsLoadingSecs);
  DownloadSecurities(Connect, FLanguage);
  ShowStatus(rsLoadingFirms);
  DownloadFirms(Connect, FLanguage);
  FLastRequestTime := GetTickCount;
end;

procedure ReplaceParam(Params: TStringList; const ParamName, ParamValue: string; AllowNulls: Boolean = False);
var
  I: Integer;
begin
  I := Params.IndexOfName(ParamName);
  if I >= 0 then Params.Delete(I);
  if AllowNulls or (ParamValue <> '') then Params.Add(ParamName + '=' + ParamValue);
end;

function TMainForm.ConnectToServer(const Arguments: TStrings; UseSoftConnect: Boolean): TMTEConnect;
var
//  Err: Integer;
  ErrMsg: TMTEErrorMsg;
  Reply: TMTEExecTransResult;
  Idx: TMTEResult;
  Iface: TMicexIface;
  ACaption, VerStr, Language: string;
  Tree: TTreeView;
  Panel: TPanel;
  Params: TStringList;
  ExtendedConnect: Boolean;
begin
  Result := nil;
  Iface := nil;
  Screen.Cursor := crHourGlass;
  ShowStatus(rsConnecting);
  try
    ErrMsg := '';
    Params := TStringList.Create;
    try
      Params.Assign(Arguments);
      Language := Params.Values['Language'];
      if Language = '' then
        ReplaceParam(Params, 'Language', FLanguage);
      if not (msaStructure2 in GetMTESrlAddons) then
        ReplaceParam(Params, 'StructureFormat', '2');

      ExtendedConnect := False;
      if UseSoftConnect then
        Idx := MTESoftConnect(PAnsiChar(AnsiString(Params.Text)), @ErrMsg)
      else begin
        if (msaConnectEx in GetMTESrlAddons) and ExtendedTransApi then begin
          ExtendedConnect := True;
          Idx := MTEConnectEx(PAnsiChar(AnsiString(Params.Text)), Reply);
        end else
          Idx := MTEConnect(PAnsiChar(AnsiString(Params.Text)), @ErrMsg);
      end;
    finally
      FreeAndNil(Params);
    end;

    if Idx < MTE_OK then
    begin
      if ExtendedConnect then
        ShowTransExResult(nil, Idx, Reply, 'MTEConnectEx')
      else
        MessageDlg(Format('Ошибка %d: %s' + sLineBreak + sLineBreak + '%s',
          [Idx, MTEErrorMsgEx(Idx, PAnsiChar(AnsiString(Language))), ErrMsg]), mtWarning, [mbOk], 0);
      Exit;
    end
    else
    begin
      if not UseSoftConnect then
      begin
        Iface := DownloadStructure(Idx, nil, Language);
        if not Assigned(Iface) then Exit;
      end;
      ACaption := GetIfaceCaption(Iface, Arguments);

      Panel := TPanel.Create(Self);
      Panel.Align := alClient;
      Panel.BevelOuter := bvNone;
      Panel.Parent := BasePanel;
      Panel.Caption := ACaption;

      Tree := TTreeView.Create(Panel);
      Tree.Parent := Panel;
      Tree.Align := alClient;
      Tree.Images := TreeImages;
      Tree.HideSelection := False;
      Tree.ReadOnly := True;
      Tree.OnChange := ObjTreeChange;
      Tree.OnDblClick := ObjTreeDblClick;
      Tree.OnExpanding := TreeViewExpanding;
      Tree.OnCollapsing := TreeViewExpanding;
      Tree.OnMouseDown := TreeViewMouseDown;

      Result := FConnects.Add(Idx, Iface, ACaption, Tree);
      Result.fLanguage := Language;
      Result.FLoggedon := not UseSoftConnect;
      Result.FParams.Assign(Arguments);

      if msaGetVersion in GetMteSrlAddons then
      begin
        VerStr := string(MTEGetVersion);
        // for MICEX MTESRL.DLL we can open dictionary tables with parameter Complete = False (for speed)
        // fo CMA MTESRL.DLL we must open dictionary table with Complete = True, otherwise we'll get only first 30K of each table
        if (Pos('MICEX', UpperCase(VerStr)) > 0) or (Pos('MOEX', UpperCase(VerStr)) > 0) then 
          Result.FInitialOpenMode := False;
      end;

      Tree.Tag := IntPtr(Result);
      cbSessions.AddItem(ACaption, Panel);
      cbSessions.ItemIndex := cbSessions.Items.IndexOf(ACaption);
      PlantTrees(Result);
  //        RefreshTimer.Enabled := True;
      BasePanel.Visible := True;
      ObjSplitter.Visible := True;
      ObjSplitter.Left := MaxInt;

      if not UseSoftConnect then
        DownloadDictionary(Result);
      SessionChange(cbSessions);
      FreeBuffer(Result);
      ShowStats;
    end;
  finally
    ShowStatus('');
    Screen.Cursor := crDefault;
  end;
end;

procedure TMainForm.actConnectExecute(Sender: TObject);
var
  Connect: TMTEConnect;
  SyncTime: string;
  Stats: TMTEConnStats;
begin
  with TLogonForm.Create(Self) do
  try
    if ShowModal = mrOk then
    begin
      SyncTime := UpperCase(Params.Lines.Values['SYNCTIME']);
      FModifyTime := not ((SyncTime = '0') or (SyncTime = 'N') or (SyncTime = 'F'));
      Connect := ConnectToServer(Params.Lines, cbSoftConnect.Checked);

      if Assigned(Connect) and ([msaConnStats, msaConnCerts] <= GetMTESrlAddons) then
      begin
        FillChar(Stats, SizeOf(Stats), 0);
        Stats.Size := SizeOf(Stats);
        if (MTEConnectionStats(Connect.FIdx, Stats) = MTE_OK)
          and (Stats.Properties and (FLAG_ENCRYPTED or FLAG_SIGNING_ON) <> 0) then
            ShowCertificates;
      end;
    end;
  finally
    Free;
  end;
end;

procedure TMainForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  while cbSessions.Items.Count > 0 do
    actDisconnectExecute(Self);
end;

procedure TMainForm.WindowCloseAllExecute(Sender: TObject);
var
  Index: Integer;
begin
  { Must be done backwards through the MDIChildren array }
  for Index := MDIChildCount - 1 downto 0 do MDIChildren[Index].Close;
end;

procedure TMainForm.actAboutExecute(Sender: TObject);
begin
  ShellAbout(Handle, 'ASTSClient',
    'ASTS Interface Client'^M'Copyright (c) 1999-2018 Moscow Exchange', Icon.Handle);
end;

function GetConnectPropsStr(Props: DWORD; Reconnects: Integer): string;
begin
  if Props and ZLIB_COMPRESSED <> 0 then Result := 'СЖАТ' else Result := '';
  if Props and FLAG_ENCRYPTED <> 0 then
  begin
    if Result <> '' then Result := Result + ', ';
    Result := Result + 'ШИФР';
  end;
  if Props and FLAG_SIGNING_ON <> 0 then
  begin
    if Result <> '' then Result := Result + ', ';
    Result := Result + 'ЭЦП';
  end;
  if Reconnects > 0 then
  begin
    if Result <> '' then Result := Result + ', ';
    Result := Result + Format('%d обрывов связи', [Reconnects]);
  end;
end;

procedure TMainForm.ShowStats;

  procedure QueryHeapStatus(var TotalAddrSpace, TotalAllocated: NativeUInt);
  {$if CompilerVersion<18.0}
  var
    HeapStatus: THeapStatus;
  begin
    HeapStatus := FastGetHeapStatus;
    TotalAddrSpace := HeapStatus.TotalAddrSpace;
    TotalAllocated := HeapStatus.TotalAllocated;
  end;
  {$else}
  var
    MemoryState: TMemoryManagerState;
    i: Integer;
  begin
    GetMemoryManagerState(MemoryState);
    TotalAddrSpace := MemoryState.ReservedMediumBlockAddressSpace + MemoryState.ReservedLargeBlockAddressSpace;
    TotalAllocated := MemoryState.TotalAllocatedMediumBlockSize + MemoryState.TotalAllocatedLargeBlockSize;
    for i := 0 to High(MemoryState.SmallBlockTypeStates) do
      with MemoryState.SmallBlockTypeStates[i] do begin
        Inc(TotalAddrSpace, ReservedAddressSpace);
        Inc(TotalAllocated, AllocatedBlockCount * UseableBlockSize);
      end;
  end;
  {$ifend}

var
  C: TMTEConnect;
  Stats: TMTEConnStats;
  TotalAddrSpace, TotalAllocated: NativeUInt;
begin
  C := GetActiveConnect;
  if Assigned(C) then
  begin
    FillChar(Stats, SizeOf(Stats), 0);
    Stats.Size := SizeOf(Stats);
    if MTEConnectionStats(C.FIdx, Stats) = MTE_OK then
    begin
      StatusBar.Panels[1].Text := Format('-> %d / %d (%d pkts)', [Stats.SentBytes, Stats.SentUncompressed, Stats.SentPackets]);
      StatusBar.Panels[2].Text := Format('<- %d / %d (%d pkts)', [Stats.RecvBytes, Stats.RecvUncompressed, Stats.RecvPackets]);
      QueryHeapStatus(TotalAddrSpace, TotalAllocated);
      StatusBar.Panels[3].Text := Format('%s Kb', [EDisplayInt(IntToStr(TotalAddrSpace div 1024))]);
      if Stats.ServerIpAddress <> 0 then
        StatusBar.Panels[4].Text := Einet_ntoa(Stats.ServerIpAddress);
      StatusBar.Panels[5].Text := GetConnectPropsStr(Stats.Properties, Stats.ReconnectCount);
    end;
  end;
end;

function Min(A, B: Integer): Integer;
begin
  if A < B then Result := A else Result := B;
end;

procedure TMainForm.RefreshTimerTimer(Sender: TObject);
var
  I, Err, RefCount, Cidx, iSystem, NumMsg: Integer;
  Msg: PMTEMsg;
  Tbl: PMTETable;
  Form: TForm;
  Connect: TMTEConnect;
  Connected: Boolean;
begin
  for Cidx := 0 to FConnects.Count - 1 do
  begin
    Connect := FConnects[Cidx];
    for iSystem := 0 to pred(Connect.FIface.SystemCount) do
    begin
      if Connect.FDisconnected then Continue; // connection was lost - do not update tables
      RefCount := 0;

      for I := 0 to MDIChildCount - 1 do
        if MDIChildren[I] is TTableForm then
          with TTableForm(MDIChildren[I]) do
            if (Connect.FIdx = ConnectIdx) and (iSystem = SystemIndex) and Refreshable then
            begin
              {$IFDEF CLIENTLOG}
              Writeln(DebugFile, 'Requesting ' + Caption);
              {$ENDIF}
              Err := AddToPacket(ConnectIdx, NumMsg);
              Inc(RefCount, NumMsg);
              if Err <> MTE_OK then
              begin
                ValidateConnect(Connect, Err);
                ShowError(Connect, Err, '', 'MTEAddTable');
                Break;
              end;
            end;

      if RefCount > 0 then
      begin
        FLastRequestTime := GetTickCount;
        Err := MTERefresh(Connect.FIdx, Msg);
        if Err <> MTE_OK then
        begin
          Connected := ValidateConnect(Connect, Err);
          if Connected then RefreshRateClick(miStopRefresh);
          ShowError(Connect, Err, GetTsmrStr(Err, Msg) +
            sLineBreak + sLineBreak + sRefreshErrors[Connected], 'MTERefresh')
        end
        else
        begin
          // Cycle throu all refresh groups
          RefCount := PMTETables(@Msg.Data).TblCount;

          {$IFDEF CLIENTLOG}
          Writeln(DebugFile, Format('%d table(s) refreshed', [RefCount]));
          Flush(DebugFile);
          {$ENDIF}

          if RefCount > 0 then
          begin
            Tbl := @PMTETables(@Msg.Data).Tables;
            for I := 0 to RefCount - 1 do
            begin
              Form := FindFormByID(Tbl.Ref);
              if Assigned(Form) then
                TTableForm(Form).ParseData(Tbl)
              else begin
                RefreshRateClick(miStopRefresh);
                ShowError(Connect, MTE_UNKNOWN, Format('Invalid Ref in MTERefresh: %d', [Tbl.Ref]) +
                  sLineBreak + sLineBreak + sRefreshErrors[True], 'MTERefresh');
              end;
            end;
          end;
        end;
        FreeBuffer(Connect);
      end;
    end;
  end;
  ShowStats;
end;

procedure TMainForm.miActionClick(Sender: TObject);
begin
  miCompleteOpen.Checked := FCompleteOpen;
  miFreeBuf.Checked := FFreeBuffer;
  miSendIp.Checked := FSendClientIp;
  miExecTransEx.Checked := ExtendedTransApi;
  miSendSysTime.Checked := fSendSysTime;
end;

procedure TMainForm.miCompleteOpenClick(Sender: TObject);
begin
  FCompleteOpen := not FCompleteOpen;
end;

procedure TMainForm.miFreeBufClick(Sender: TObject);
begin
  FFreeBuffer := not FFreeBuffer;
end;

procedure TMainForm.actSnapshotExecute(Sender: TObject);
var
  Snapshot: PByte;
  Err, Len, i: Integer;
  S: string;
  Connect: TMTEConnect;
  SnapTables: PMTESnapTables;
begin
  Connect := GetActiveConnect;
  if Assigned(Connect) then
  begin
    Err := MTEGetSnapshot(Connect.FIdx, Snapshot, Len);
    ValidateConnect(Connect, Err);
    case Err of
      MTE_OK:
        begin
          if msaGetTablesFromSnapshot in GetMTESrlAddons then begin
            Err := MTEGetTablesFromSnapshot(Connect.FIdx, Snapshot, Len, SnapTables);
            if Err >= 0 then begin
              FmtStr(S, '%d table(s) in snapshot' + sLineBreak, [Err]);
              for i := 0 to Pred(Err) do
                S := S + Format('%d. hTable=%d, TableName="%s", Params="%s"' + sLineBreak,
                  [i, SnapTables^[i].HTable, SnapTables^[i].TableName, SnapTables^[i].Params]);
            end else
              FmtStr(S, 'MTEGetTablesFromSnapshot: %d (%s)' + sLineBreak,
                [Err, Connect.GetErrorMsg(Err)]);

          end else
            S := '';

          with TFileStream.Create('Snapshot.bin', fmCreate) do
          try
            WriteBuffer(Snapshot^, Len);
          finally
            Free;
          end;
          MessageDlg(S + sLineBreak + 'Snapshot saved in ".\Snapshot.bin"', mtInformation, [mbOk], 0);
        end;
      MTE_TSMR:
        begin
          SetString(S, PAnsiChar(Snapshot), Len);
          MessageDlg(S, mtError, [mbOk], 0);
        end;
      else MessageDlg(Connect.GetErrorMsg(Err), mtError, [mbOk], 0);
    end;
    FreeBuffer(Connect);
  end;
end;

procedure TMainForm.actRestoreExecute(Sender: TObject);
var
  Snapshot: PByte;
  Err, Len: Integer;
  ErrMsg: TMTEErrorMsg;
  Connect: TMTEConnect;
begin
  Connect := GetActiveConnect;
  if Assigned(Connect) then
  begin
    with TFileStream.Create('Snapshot.bin', fmOpenRead) do
    try
      Len := Size;
      GetMem(Snapshot, Len);
      try
        ReadBuffer(Snapshot^, Len);
        Err := MTESetSnapshot(Connect.FIdx, Snapshot, Len, @ErrMsg);
        ValidateConnect(Connect, Err);
        ShowError(Connect, Err, string(ErrMsg), 'MTESetSnapshot');
      finally
        FreeMem(Snapshot);
      end;
    finally
      Free;
    end;
  end;
end;

procedure TMainForm.actSessionExitExecute(Sender: TObject);
begin
  Close;
end;

procedure TMainForm.ActionsUpdate(Action: TBasicAction;
  var Handled: Boolean);
var
  Obj: TObject;
  Connect: TMTEConnect;
  Idx: Integer;
begin
  inherited;
  Connect := GetActiveConnect;
  if Assigned(Connect) and Assigned(Connect.FTree.Selected) then
    Obj := TObject(Connect.FTree.Selected.Data) else Obj := nil;
  if Assigned(Connect) then Idx := Connect.FIdx else Idx := -1;

//  actConnect.Enabled := Idx < 0;
  actDisconnect.Enabled := Idx >= 0;
  WindowCloseAll.Enabled := MDIChildCount > 0;
  actRefreshTable.Enabled := (MDIChildCount > 0) and not Connect.FDisconnected and Connect.FLoggedon
    and (MDIChildren[0] is TTableForm) and TTableForm(MDIChildren[0]).Refreshable;
  actExecute.Enabled := (Idx >= 0) and (Obj is TMicexMessage) and not Connect.FDisconnected and Connect.FLoggedon;
  actGetExtData.Enabled := (Idx >= 0) and (msaGetExtData in GetMTESrlAddOns) and not Connect.FDisconnected and Connect.FLoggedon;

  actSnapshot.Enabled := (Idx >= 0) and not Connect.FDisconnected and Connect.FLoggedon;
  actRestore.Enabled := (Idx >= 0) and not Connect.FDisconnected and Connect.FLoggedon;
  actExport.Enabled := (Idx >= 0) and Connect.FLoggedon;

  miSendIp.Enabled := msaExecTransIP in GetMTESrlAddOns;
  miExecTransEx.Enabled := msaExecTransEx in GetMTESrlAddons;
  miShowIds.Checked := IdViewMode = ivmIds;
  miShowNames.Checked := IdViewMode = ivmNames;
  miShowAll.Checked := IdViewMode = ivmAll;

  actDllver.Enabled := msaGetVersion in GetMTESrlAddons;
  actShowCert.Enabled := (Idx >= 0) and ([msaConnStats, msaConnCerts] <= GetMTESrlAddons);
  actServInfo.Enabled := (Idx >= 0) and (msaGetServInfo in GetMTESrlAddons);
  actLogon.Enabled := (Idx >= 0) and not Connect.FDisconnected and (msaSoftConnect in GetMTESrlAddons);
end;

procedure TMainForm.FormCreate(Sender: TObject);
begin
  inherited;
  FConnects := TMTEConnectList.Create;
  fSendSysTime := True;
  RefreshRateClick(miOneSecond);
end;

procedure TMainForm.Loaded;

  function GetEnglishName(const LCID: Integer): String;
  var
    Buffer: array[0..64] of Char;
    BufLen: Integer;
  begin
    BufLen := GetLocaleInfo(LCID, LOCALE_SENGLANGUAGE, Buffer, SizeOf(Buffer) div SizeOf(Char));
    if BufLen > 0 then
      Result := StrPas(Buffer)
    else
      Result := '';
  end;

  function SystemLanguage: String;
  begin
    // GetSystemDefaultLCID = system default code page for non-unicode programs
    // GetUserDefaultLCID  = GetThreadLocale = SysLocale.DefaultLCID = date/time format
    Result := GetEnglishName(GetSystemDefaultLCID);
  end;

begin
  with TIniFile.Create(IniFileName) do
  try
    FLanguage := SystemLanguage;
    IdViewMode := TIdViewMode(ReadInteger('General', 'IdViewMode', Integer(ivmNames)));
    ExtendedTransApi := ReadBool('General', 'ExtendedAPI', False);
  finally
    Free;
  end;
  inherited;
end;

function TMainForm.GetActiveConnect: TMTEConnect;
begin
  if (cbSessions.Items.Count > 0) and (cbSessions.ItemIndex >= 0) then
    Result := TMTEConnect(TPanel(cbSessions.Items.Objects[cbSessions.ItemIndex]).Components[0].Tag)
  else
    Result := nil;
end;

procedure TMainForm.actExecuteExecute(Sender: TObject);
var
  Connect: TMTEConnect;
  Msg: TMicexMessage;
begin
  inherited;
  Connect := GetActiveConnect;
  if Assigned(Connect) then
    if Assigned(Connect.FTree.Selected) and (TObject(Connect.FTree.Selected.Data) is TMicexMessage) then
    begin
      Msg := TMicexMessage(Connect.FTree.Selected.Data);
      if Msg.IsTable then OpenTable(Connect, Msg, nil)
      else ExecuteTransaction(Connect, Msg, nil);
    end;
end;

procedure TMainForm.RefreshRateClick(Sender: TObject);
begin
  inherited;
  RefreshTimer.Interval := (Sender as TComponent).Tag * 1000;
  if (Sender as TComponent).Tag <> 0 then
    tbRefresh.Caption := (Sender as TMenuItem).Caption
  else
    tbRefresh.Caption := 'Обновить';
end;

procedure TMainForm.pmRefreshRatesPopup(Sender: TObject);
var
  I: Integer;
begin
  inherited;
  with (Sender as TPopupMenu) do
    for I := 0 to Items.Count - 1 do
      with Items[I] do
        Checked := Cardinal(Tag * 1000) = RefreshTimer.Interval;
end;

procedure TMainForm.ShowStatus(const Msg: string);
begin
  with StatusBar do
  begin
    if (Msg <> '') or (Copy(Panels[0].Text, 1, 7) <> 'Warning') then
      Panels[0].Text := Msg;

    if Msg = '' then
    begin
      Panels[1].Text := '';
      Panels[2].Text := '';
      Panels[3].Text := '';
      Panels[4].Text := '';
      Panels[5].Text := '';
    end;
    Repaint;
  end;
end;

procedure TMainForm.ShowTreeHint(Node: TTreeNode);

  function GetProp(const Name, Value: string): string;
  begin
    if Value <> '' then FmtStr(Result, ', %s=''%s''', [Name, Value])
    else Result := '';
  end;

  function BraceStr(const S: string): string;
  begin
    if S = '' then
      Result := ''
    else
      Result := ' (' + S + ')';
  end;

const
  STypes: array [TTEFieldType] of string =
    ('Char', 'Integer', 'Fixed', 'Price', 'Date', 'Time', 'Float');
  SBools: array [Boolean] of string = ('Нет', 'Да');
  sTheKey: array [Boolean] of string = ('', ', Ключ');
  sSecCode: array [Boolean] of string = ('', ', КодИнстр.');
  sNotNull: array [Boolean] of string = ('', ', НеПусто');
  sVarBlock: array [Boolean] of string = ('', ', Повтор');
var
  Obj: TMicexObject;
begin
  if Assigned(Node) then
  begin
    Obj := TObject(Node.Data) as TMicexObject;
    if Assigned(Obj) then
    begin
      if Obj is TMicexField then
        with TMicexField(Obj) do
          ShowStatus(
            Format('%s: %s(%s)%s%s%s%s%s%s',
              [GetDisplayName(Obj, False), STypes[FieldType], TMicexField(Obj).GetDecimalsStr,
              sTheKey[Key], sSecCode[SecCode], sNotNull[Flags and ffNotNull <> 0],
              sVarBlock[Flags and ffVarBlock <> 0], GetProp('Enum', EnumType), GetProp('Default', DefValue)]))
      else
        if Obj is TMicexMessage then
          with TMicexMessage(Obj) do
        begin
          if IsTable then
            ShowStatus(
              Format('%s, Обновлять = %s, Очищать = %s', [GetDisplayName(Obj, False), SBools[Updateable],
                SBools[ClearOnUpdate]]))
          else
            ShowStatus(GetDisplayName(Obj, False));
        end
        else
          if Obj is TMicexEnumType then
            with TMicexEnumType(Obj) do
          begin
            ShowStatus(Format('%s(%d): %s', [GetDisplayName(Obj, False), Size, GetAsText]))
          end
          else
            if Obj is TMicexList then
              ShowStatus(Format('Объектов: %d', [TMicexList(Obj).Count]))
            else
              ShowStatus(Format('%s', [GetDisplayName(Obj, False)]))
    end
    else begin
      if Assigned(Node.Parent) then
        Obj := TObject(Node.Parent.Data) as TMicexObject;
      if Obj is TMicexEnumType then
        ShowStatus(TMicexEnumType(Obj).GetConstAndName(Node.Index) + BraceStr(TMicexEnumType(Obj).GetShortName(Node.Index)))
      else
        ShowStatus('');
    end;
  end
  else ShowStatus('');
end;

procedure TMainForm.ObjTreeChange(Sender: TObject; Node: TTreeNode);
begin
  inherited;
  ShowTreeHint(Node);
end;

procedure TMainForm.ObjTreeDblClick(Sender: TObject);
var
  Connect: TMTEConnect;
begin
  Connect := GetActiveConnect;
  if Assigned(Connect) and not Connect.FLoggedon then Logon
  else
    if actExecute.Enabled then actExecuteExecute(nil);
end;

procedure TMainForm.SaveExtData(Data: TMemoryStream; const FileName: string);
begin
  Data.SaveToFile(FileName);
end;

procedure TMainForm.actGetExtDataExecute(Sender: TObject);
var
  Data: PMTEMsg;
  Err, I: Integer;
  Value, DataSet, FileName: string;
  Connect: TMTEConnect;
  M: TMemoryStream;
begin
  inherited;
  Connect := GetActiveConnect;
  if Assigned(Connect) then
  begin
    with TIniFile.Create(IniFileName) do
    try
      Value := ReadString('General', 'GetExtData', 'DataSet\FileName');
    finally
      Free;
    end;

    if InputQuery('Загрузка файла из ASTS',
      'Введите имя набора данных и файла:', Value) then
    begin
      I := LastDelimiter('\/:?@,.', Value);
      if I <= 0 then
      begin
        MessageDlg('Вы неверно задали имя набора данных\файла',
          mtInformation, [mbOk], 0);
        Exit;
      end;

      with TIniFile.Create(IniFileName) do
      try
        WriteString('General', 'GetExtData', Value);
      finally
        Free;
      end;

      DataSet := Copy(Value, 1, I - 1);
      FileName := Copy(Value, I + 1, MaxInt);
      Err := MTEGetExtData(Connect.FIdx, PAnsiChar(AnsiString(DataSet)), PAnsiChar(AnsiString(FileName)), Data);
      if Err <> MTE_OK then
      begin
        ValidateConnect(Connect, Err);
        ShowError(Connect, Err, GetTsmrStr(Err, Data), 'MTEGetExtData')
      end
      else
      begin
        M := TMemoryStream.Create;
        try
          M.WriteBuffer(Data.Data, Data.DataLen);
          if SaveDlg.Execute then SaveExtData(M, SaveDlg.FileName);
        finally
          M.Free;
        end;
      end;
    end;
  end;
end;

procedure TMainForm.miSendIpClick(Sender: TObject);
begin
  SendClientIp := not SendClientIp;
end;

procedure TMainForm.miSendSysTimeClick(Sender: TObject);
begin
  fSendSysTime := not fSendSysTime;
end;

procedure TMainForm.miExecTransExClick(Sender: TObject);
begin
  ExtendedTransApi := not ExtendedTransApi;
end;

procedure TMainForm.SetSendClientIp(const Value: Boolean);
begin
  if fSendClientIp <> Value then begin
    fSendClientIp := Value;
    if not Value then
      ExtendedTransApi := False;
  end;
end;

procedure TMainForm.SetExtendedTransApi(const Value: Boolean);
begin
  if fExtendedTransApi <> Value then begin
    fExtendedTransApi := Value;
    if Value then
      SendClientIp := True;
  end;
end;

procedure TMainForm.KeepAliveTimerTimer(Sender: TObject);
const
  HEARTBEAT_INTERVAL = 10000;
var
  Msg: PMTEMsg;
  I, Err: Integer;
  Connected: Boolean;
begin
  inherited;
  for I := 0 to FConnects.Count - 1 do
    with FConnects[I] do
    if (FIdx >= 0) and not FDisconnected and (FSysTimeTable >= MTE_OK) and (GetTickCount - FLastRequestTime > HEARTBEAT_INTERVAL) then
    begin
      FLastRequestTime := GetTickCount;
      Err := MTEAddTable(FIdx, FSysTimeTable, 0);
      if Err = MTE_OK then Err := MTERefresh(FIdx, Msg) else Msg := nil;
      if Err <> MTE_OK then
      begin
        Connected := ValidateConnect(FConnects[I], Err);
        if Connected then RefreshRateClick(miStopRefresh);
        ShowError(FConnects[i], Err, GetTsmrStr(Err, Msg) +
          sLineBreak + sLineBreak + sRefreshErrors[Connected], 'MTERefresh(SYSTIME) - keep alive timer')
      end;
      FreeBuffer(FConnects[I]);
    end;
end;

procedure TMainForm.actExportExecute(Sender: TObject);
var
  Connect: TMTEConnect;
begin
  Connect := GetActiveConnect;
  if Assigned(Connect) then
    if SaveHtmlDlg.Execute then
    begin
      ExportDescToFile(Connect.FIface, SaveHtmlDlg.FileName, Connect.FIface.Name);
      ShellExecute(0, nil, PChar(SaveHtmlDlg.FileName), nil, nil, SW_SHOW);
    end;
end;

function TMainForm.IsCmdSwitch(const S: string): Boolean;
begin
  Result := (S <> '') and CharInSet(S[1], ['-', '/']);
end;

procedure TMainForm.FormShow(Sender: TObject);
var
  Params: TStringList;
  I: Integer;
begin
  inherited;
//  ObjPanel.Width := 50;
  I := 1;
  while (I <= ParamCount) and IsCmdSwitch(ParamStr(I)) do Inc(I);
  if I > ParamCount then Exit;

  with TIniFile.Create(ExtractFilePath(ParamStr(0)) + ParamStr(I)) do
  try
    Params := TStringList.Create;
    try
      ReadSectionValues('Default', Params);
      if Params.Count > 0 then
        ConnectToServer(Params, False)
      else
        MessageDlg('Invalid INI-file specified:' + sLineBreak + ParamStr(I), mtWarning, [mbOk], 0);
    finally
      Params.Free;
    end;
  finally
    Free;
  end;
end;

procedure TMainForm.FormDestroy(Sender: TObject);
begin
  inherited;
  FConnects.Free;
  with TIniFile.Create(IniFileName) do
  try
    WriteInteger('General', 'IdViewMode', Integer(IdViewMode));
    WriteString('General', 'Language', FLanguage);
    WriteBool('General', 'ExtendedAPI', ExtendedTransApi);
  finally
    Free;
  end;
end;

{ TMTEConnectList }

function TMTEConnectList.Add(AHandle: Integer; AIface: TMicexIface;
  const ACaption: string; ATree: TTreeView): TMTEConnect;
begin
  Result := TMTEConnect.Create;
  Result.FIdx := AHandle;
  Result.FIface := AIface;
  Result.FCaption := ACaption;
  Result.FTree := ATree;
  Result.FSysTimeTable := -1;
  if Assigned(AIface) then Result.SetupKeepAlive;
  inherited Add(Result);
end;

procedure TMTEConnectList.Clear;
var
  I: Integer;
begin
  for I := 0 to Count - 1 do
    Connects[I].Free;
  inherited Clear;
end;

procedure TMTEConnectList.Delete(AIndex: Integer);
begin
  Connects[AIndex].Free;
  inherited Delete(AIndex);
end;

function TMTEConnectList.GetConnect(AIndex: Integer): TMTEConnect;
begin
  Result := Items[AIndex];
end;

procedure TMainForm.SessionChange(Sender: TObject);
begin
  inherited;
  if (cbSessions.Items.Count > 0) and (cbSessions.ItemIndex >= 0) then
  begin
    Caption := 'ASTSClient - ' + cbSessions.Text;
    TPanel(cbSessions.Items.Objects[cbSessions.ItemIndex]).BringToFront;
  end
  else
    Caption := 'ASTSClient';
  ShowStats;
end;

procedure TMainForm.SessionPanelResize(Sender: TObject);
begin
  inherited;
  cbSessions.Width := SessionPanel.Width;
end;

procedure TMainForm.TreeViewExpanding(Sender: TObject; Node: TTreeNode;
  var AllowExpansion: Boolean);
begin
  AllowExpansion := (GetTickCount - FTreeButtonClickTime > GetDoubleClickTime)
    or FTreeButtonClick;
end;

procedure TMainForm.TreeViewMouseDown(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
  Tree: TTreeView;
begin
  Tree := Sender as TTreeView;
  with Tree.ScreenToClient(Mouse.CursorPos) do
    FTreeButtonClick := htOnButton in Tree.GetHitTestInfoAt(X, Y);
  FTreeButtonClickTime := GetTickCount;
end;

procedure TMainForm.cbSessionsDrawItem(Control: TWinControl;
  AIndex: Integer; Rect: TRect; State: TOwnerDrawState);
var
  Connect: TMTEConnect;
begin
  inherited;
  with cbSessions, Canvas do
  begin
    if (AIndex < Items.Count) and (AIndex >= 0) then
      Connect := TMTEConnect(TPanel(Items.Objects[AIndex]).Components[0].Tag)
    else
      Connect := nil;

    if Assigned(Connect) and Connect.FDisconnected then Font.Color := clRed;
    TextRect(Rect, Rect.Left + 3, Rect.Top + 1, cbSessions.Items[AIndex]);
  end;
end;

procedure TMainForm.InvalidateWindows;
var
  I: Integer;
begin
  for I := 0 to MDIChildCount - 1 do
    if MDIChildren[I] is TTableForm then
      with TTableForm(MDIChildren[I]) do LV.Invalidate;
end;

procedure TMainForm.miShowAllClick(Sender: TObject);
begin
  inherited;
  IdViewMode := TIdViewMode((Sender as TMenuItem).Tag);
  InvalidateWindows;
end;

function TMainForm.ValidateConnect(Connect: TMTEConnect; ErrorCode: Integer = MTE_OK): Boolean;
begin
  if msaConnStatus in GetMTESrlAddons then
    Result := MTEConnectionStatus(Connect.FIdx) = MTE_OK
  else
    Result := (ErrorCode >= MTE_OK) or (ErrorCode = MTE_TSMR) or (ErrorCode = MTE_TRANSREJECTED);
  if not Result then
  begin
    Connect.FDisconnected := True;
    cbSessions.Invalidate;
  end;
end;

procedure TMainForm.actDllVerExecute(Sender: TObject);
begin
  inherited;
  if msaGetVersion in GetMTESrlAddons then
    MessageDlg('Версия используемой библиотеки MTESRL.DLL:' +
      sLineBreak + sLineBreak + string(MTEGetVersion), mtInformation, [mbOk], 0)
  else MessageDlg('Not implemented', mtWarning, [mbOk], 0);
end;

function GetCertAltName(AltName: TMTECertAltName): string;
begin
  FmtStr(Result, #9'Организация: %s' + sLineBreak + #9'Фамилия: %s' + sLineBreak + #9'Описание: %s' + sLineBreak + #9'(%s)',
    [AltName.OrganizationName, AltName.Surname, AltName.Description, AltName.Subject]);
end;

function GetCertInfo(Cert: TMTEConnCertificate): string;
begin
  Result := 'Информация о владельце сертификата:' + sLineBreak +
    GetCertAltName(Cert.Owner);

  if Result <> '' then Result := Result + sLineBreak;
  Result := Result + 'Информация об издателе сертификата:' + sLineBreak +
    GetCertAltName(Cert.Issuer);

  if Result <> '' then Result := Result + sLineBreak;
  Result := Result + Format('Действителен с %s по %s',
    [FormatDateTime('dddddd', SystemTimeToDateTime(Cert.ValidFrom)),
      FormatDateTime('dddddd', SystemTimeToDateTime(Cert.ValidTo))]);
end;

function NotNullStr(const S, NullStr: string): string;
begin
  if Trim(S) = '' then Result := NullStr else Result := S;
end;

procedure TMainForm.ShowCertificates;
var
  Connect: TMTEConnect;
  MyCert, ServerCert: TMTEConnCertificate;
  Err: Integer;
  Stats: TMTEConnStats;
//  S: string;
begin
  Connect := GetActiveConnect;
  if Assigned(Connect) then
  begin
    Err := MTEConnectionCerts(Connect.FIdx, @MyCert, @ServerCert);
    if Err = MTE_OK then
    begin
      FillChar(Stats, SizeOf(Stats), 0);
      Stats.Size := SizeOf(Stats);
      Err := MTEConnectionStats(Connect.FIdx, Stats);
      if Err = MTE_OK then
      begin
{        if Assigned(ServerCert.Owner.Subject) then
          S := AnsiUpperCase('Сертификат сервера:' + sLineBreak) + GetCertInfo(ServerCert)
        else
          if Stats.Properties and (FLAG_ENCRYPTED or FLAG_SIGNING_ON) <> 0 then
            S := AnsiUpperCase('ОШИБКА: Шифрование или ЭЦП включены, но сертификат сервера получить не удается!')
          else
            S := 'Шифрование и ЭЦП не используется, сертификат сервера неизвестен';

        if S <> '' then S := S + sLineBreak + sLineBreak;
        if Assigned(MyCert.Owner.Subject) then
          S := S + AnsiUpperCase('Собственный сертификат:' + sLineBreak) + GetCertInfo(MyCert)
        else
          if Stats.Properties and (FLAG_ENCRYPTED or FLAG_SIGNING_ON) <> 0 then
            S := S + AnsiUpperCase('ОШИБКА: Шифрование или ЭЦП включены, но собственный сертификат получить не удается!')
          else
            S := S + 'Шифрование и ЭЦП не используется, собственный сертификат неизвестен';

        MessageDlg(Connect.FCaption + sLineBreak + StringOfChar('-', 80) + sLineBreak + S,
          mtInformation, [mbOk], 0);}

        with TCertInfoForm.Create(Self) do
        try
          Caption := Caption + NotNullStr(string(Stats.ServerName), 'TEServer') + ' - ' + Connect.FIface.Name;
          SetCertificates(@MyCert, @ServerCert, @Stats);
          ShowModal;
        finally
          Free;
        end;
      end
      else ShowError(Connect, Err, '', 'MTEConnectionStats');
    end
    else ShowError(Connect, Err, '', 'MTEConnectionCerts');
  end;
end;

procedure TMainForm.StatusBarDblClick(Sender: TObject);
begin
  if actShowCert.Enabled then ShowCertificates;
end;

procedure TMainForm.actShowCertExecute(Sender: TObject);
begin
  ShowCertificates;
end;

function TimeAsStr(Value: Integer): string;
var
  H, M, S: Integer;
begin
  H := EDivMod(Value, 10000, M);
  M := EDivMod(M, 100, S);
  FmtStr(Result, '%d:%.2d:%.2d', [H, M, S]);
end;

function DateAsStr(Value: Integer): string;
var
  Y, M, D: Integer;
begin
  D := EDivMod(Value, 1000000, M);
  M := EDivMod(M, 10000, Y);
  FmtStr(Result, '%.2d.%.2d.%.4d', [D, M, Y]);
end;

procedure TMainForm.actServInfoExecute(Sender: TObject);
var
  Connect: TMTEConnect;
  Err, Len: Integer;
  ServInfo: PByte;
  Ptr: PAnsiChar;
  S, VerStr, Boards, MicexIp, UserId: string;
  P: PMTEServInfo;
begin
  Connect := GetActiveConnect;
  if Assigned(Connect) then
  begin
    Err := MTEGetServInfo(Connect.FIdx, ServInfo, Len);
    if Err = MTE_OK then
    begin
      if Len >= SizeOf(TMTEServInfo) then
      begin
        P := PMTEServInfo(ServInfo);
        FmtStr(VerStr, '%d.%d.%d (beta=%d, debug=%d, test=%d)',
          [P.Version_Major, P.Version_Minor, P.Version_Build, P.Beta_version, P.Debug_flag, P.Test_flag]);

        MicexIp := 'N/A';
        Dec(Len, SizeOf(TMTEServInfo));
        Ptr := @P.Boards;
        if Len > 0 then
        begin
          Boards := string(Ptr);
          Dec(Len, Length(Boards) + 1);
          Inc(Ptr, Length(Boards) + 1);
          if Len >= 13 then
          begin
            // extract UserId that comes after variable length Board list
            UserId := string(Ptr);
            Dec(Len, 13);
            Inc(Ptr, 13);
            // skip SystemID: Char(1)
            if Len > 1 then begin
              MicexIp := string(Ptr + 1);
            end;
          end;
        end;

        FmtStr(S, 'Connected to ASTS: %d' + sLineBreak +
          'Session_Id: %u (started %s)' + sLineBreak +
          'Sever_Name: %s' + sLineBreak +
          'Server_IP: %s' + sLineBreak +
          'Bridge version: %s' + sLineBreak +
          'Время работы: %s - %s (%s)' + sLineBreak +
          'Next_Event: %u' + sLineBreak +
          'Event_Date: %s',
          [P.Connected_To_ASTS, P.Session_Id, DateTimeToStr(EUnixStampToDateTime(P.Session_Id)), P.ASTS_Server_Name, MicexIp, VerStr,
            TimeAsStr(P.Start_Time), TimeAsStr(P.Stop_Time_Min), TimeAsStr(P.Stop_Time_Max),
              P.Next_Event, DateAsStr(P.Event_Date)]);

        S := S + sLineBreak + 'Список режимов: ' + Boards;
        S := S + sLineBreak + 'UserId: ' + UserId;

        MessageDlg(S, mtinformation, [mbOk], 0);
      end
      else MessageDlg('MTEGetServInfo() вернула слишком короткий буфер', mtError, [mbOk], 0);
    end
    else
    begin
      ValidateConnect(Connect, Err);
      SetString(S, PAnsiChar(ServInfo), Len);
      ShowError(Connect, Err, S, 'MTEGetServInfo');
    end;
  end;
end;

procedure TMainForm.Logon;
var
  Connect: TMTEConnect;
  Err, SessionIndex: Integer;
  ACaption: string;
  Panel: TPanel;
begin
  Connect := GetActiveConnect;
  if Assigned(Connect) then
  begin
    CloseWindows(Connect);

    Err := MTELogon(Connect.FIdx, Integer(FModifyTime));
    if Err <> MTE_OK then
    begin
      if Err <> MTE_TEUNAVAIL then ValidateConnect(Connect, Err);
      ShowError(Connect, Err, '', 'MTELogon');
      Exit;
    end;

    Connect.FLoggedon := True;
    Connect.FIface := DownloadStructure(Connect.FIdx, Connect, Connect.fLanguage);
    if Assigned(Connect.FIface) then
    begin
      ACaption := GetIfaceCaption(Connect.FIface, Connect.FParams);
      SessionIndex := cbSessions.ItemIndex;
      Panel := TPanel(cbSessions.Items.Objects[SessionIndex]);
      if Assigned(Panel) then Panel.Caption := ACaption;
      cbSessions.Items[SessionIndex] := ACaption;
      Connect.FCaption := ACaption;
      cbSessions.ItemIndex := SessionIndex;
      PlantTrees(Connect);
      Connect.SetupKeepAlive;
      DownloadDictionary(Connect);
      SessionChange(cbSessions);
      FreeBuffer(Connect);
    end;
    ShowStatus('');
    ShowStats;
  end;
end;

procedure TMainForm.actLogonExecute(Sender: TObject);
begin
  Logon;
end;

initialization
  SetMultiByteConversionCodePage(1251);

end.

