unit _frmMain;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, dxSkinsCore, dxSkinOffice2019Colorful,
  Vcl.StdCtrls, cxClasses, cxLookAndFeels, dxSkinsForm, Vcl.ExtCtrls,
  dxGDIPlusClasses, cxGraphics, cxControls, cxLookAndFeelPainters,
  dxSkinsdxStatusBarPainter, dxStatusBar, IdIOHandler, IdIOHandlerSocket,
  IdIOHandlerStack, IdSSL, IdSSLOpenSSL, IdBaseComponent, IdComponent,
  IdTCPConnection, IdTCPClient, IdHTTP, _JSONConfig, dxSkinsdxBarPainter, dxBar,
  cxContainer, cxEdit, cxTextEdit, cxMaskEdit, cxDropDownEdit, Vcl.Menus,
  cxButtons, dxSkinscxPCPainter,
  dxBarBuiltInMenu, cxPC, System.ImageList, Vcl.ImgList, cxCheckBox, cxImageList,
  SynEdit, SynHighlighterJSON, SynEditHighlighter, SynHighlighterIni,
  SynHighlighterXML, SynHighlighterHtml, SynEditRegexSearch, SynEditMiscClasses,
  SynEditSearch, _SearchTextHightlighterSynEditPlugin, SynEditTypes, cxSplitter,
  SynEditCodeFolding, dxCore, Data.DB, dxmdaset, cxStyles, cxCustomData,
  cxFilter, cxData, cxDataStorage, cxNavigator, dxDateRanges,
  dxScrollbarAnnotations, cxDBData, cxGridLevel, cxGridCustomView,
  cxGridCustomTableView, cxGridTableView, cxGridDBTableView, cxGrid, cxMemo,
  htmlcomp, htmledit;

type
  TMethodType = (mtGET, mtPOST, mtPATCH, mtPUT, mtDELETE);

type
  TfrmMain = class(TForm)
    OpenDialog: TOpenDialog;
    SaveDialog: TSaveDialog;
    Panel1: TPanel;
    dxSkinController: TdxSkinController;
    Label1: TLabel;
    Image: TImage;
    SSLIOHandlerSocketOpenSSL: TIdSSLIOHandlerSocketOpenSSL;
    dxStatusBarResponse: TdxStatusBar;
    dxBarManager: TdxBarManager;
    dxBarManagerBar1: TdxBar;
    dxBarSubItem3: TdxBarSubItem;
    dxBarButton3: TdxBarButton;
    btnSave: TdxBarButton;
    btnSaveAs: TdxBarButton;
    dxBarButton6: TdxBarButton;
    dxBarSubItem1: TdxBarSubItem;
    dxBarButton7: TdxBarButton;
    dxBarSubItem2: TdxBarSubItem;
    tbbtnRun: TdxBarButton;
    PageControlResponse: TcxPageControl;
    tabContent: TcxTabSheet;
    tabHeaders: TcxTabSheet;
    pnlHeader: TPanel;
    dxBarSubItem4: TdxBarSubItem;
    tbbtnStayOnTop: TdxBarButton;
    cxImageList: TcxImageList;
    tbbtnAutoOpenLastUsed: TdxBarButton;
    dxBarButton2: TdxBarButton;
    tbbtnAutoSaveProject: TdxBarButton;
    PopupEditor: TdxBarPopupMenu;
    pmnuCutJsonContent: TdxBarButton;
    pmnuCopyJsonContent: TdxBarButton;
    pmnuPasteJsonContent: TdxBarButton;
    pmnuFormatJsonContent: TdxBarButton;
    pmnuUndoJsonContent: TdxBarButton;
    pmnuRedoJsonContent: TdxBarButton;
    pmnuSelectAll: TdxBarButton;
    pmnuCompactJson: TdxBarButton;
    dxBarDockControl1: TdxBarDockControl;
    SynIniSyn: TSynIniSyn;
    SynJSONSyn: TSynJSONSyn;
    EditorContent: TSynEdit;
    EditorResponseHeaders: TSynEdit;
    SynXMLSyn: TSynXMLSyn;
    pmnuFormatXML: TdxBarButton;
    pmnuCompactXML: TdxBarButton;
    pmnuBiggerEditor: TdxBarButton;
    SynHTMLSyn: TSynHTMLSyn;
    SynEditSearch: TSynEditSearch;
    SynEditRegexSearch: TSynEditRegexSearch;
    cxSplitter1: TcxSplitter;
    dxStatusBarApp: TdxStatusBar;
    Panel2: TPanel;
    chkResponseAutoformat: TcxCheckBox;
    dxBarSubItem5: TdxBarSubItem;
    dxBarButton1: TdxBarButton;
    dxBarButton4: TdxBarButton;
    dxBarSubItem6: TdxBarSubItem;
    dxBarSubItem7: TdxBarSubItem;
    dxBarButton5: TdxBarButton;
    dxBarButton8: TdxBarButton;
    dxBarSubItem8: TdxBarSubItem;
    tbbtnVariables: TdxBarButton;
    memDataVariables: TdxMemData;
    memDataVariablesfldName: TStringField;
    memDataVariablesfldValue: TStringField;
    dsVariables: TDataSource;
    memDataVariablesfldType: TStringField;
    memDataPresets: TdxMemData;
    dsPresets: TDataSource;
    memDataPresetsfldName: TStringField;
    memDataPresetsfldCategory: TStringField;
    memDataPresetsfldGuid: TStringField;
    cxStyleRepository1: TcxStyleRepository;
    pnlPresetDetail: TPanel;
    cxSplitter2: TcxSplitter;
    Label6: TLabel;
    comboMethod: TcxComboBox;
    edtBaseURL: TcxTextEdit;
    Label3: TLabel;
    PageControlParams: TcxPageControl;
    tabURLParams: TcxTabSheet;
    Panel3: TPanel;
    Label5: TLabel;
    EditorURLParams: TSynEdit;
    cxTabSheet2: TcxTabSheet;
    Panel4: TPanel;
    Label7: TLabel;
    EditorExtraHeaders: TSynEdit;
    cxTabSheet3: TcxTabSheet;
    Label9: TLabel;
    Label10: TLabel;
    chkUseBasicAuth: TcxCheckBox;
    edtAuthUsername: TcxTextEdit;
    edtAuthPassword: TcxTextEdit;
    tabPostData: TcxTabSheet;
    Panel5: TPanel;
    Label8: TLabel;
    comboPostContentType: TcxComboBox;
    EditorPostData: TSynEdit;
    tabNotes: TcxTabSheet;
    Panel6: TPanel;
    Label11: TLabel;
    HtmlEditor: THtmlEditor;
    Label4: TLabel;
    edtFullURL: TcxTextEdit;
    btnPasteFullURL: TcxButton;
    btnCopyFullURL: TcxButton;
    btnRun: TcxButton;
    pnlPresets: TPanel;
    cxGrid: TcxGrid;
    cxGridDBTableView: TcxGridDBTableView;
    cxGridDBTableViewfldName: TcxGridDBColumn;
    cxGridDBTableViewfldCategory: TcxGridDBColumn;
    cxGridLevel1: TcxGridLevel;
    Label2: TLabel;
    btnAdd: TcxButton;
    btnEdit: TcxButton;
    btnCopyPreset: TcxButton;
    btnDelete: TcxButton;
    memDataVariablesfldXPath: TStringField;
    dxBarSubItem9: TdxBarSubItem;
    tbbtnWebServicePerformance: TdxBarButton;
    memDataVariablesfldContent: TStringField;
    memDataVariablesfldURLFilter: TStringField;
    memDataVariablesfldPresetFilter: TStringField;
    procedure FormShow(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure dxBarButton6Click(Sender: TObject);
    procedure dxBarButton3Click(Sender: TObject);
    procedure btnSaveAsClick(Sender: TObject);
    procedure dxBarButton7Click(Sender: TObject);
    procedure tbbtnRunClick(Sender: TObject);
    procedure dxBarSubItem3Popup(Sender: TObject);
    procedure btnRunClick(Sender: TObject);
    procedure FormResize(Sender: TObject);
    procedure tbbtnStayOnTopClick(Sender: TObject);
    procedure pmnuCutJsonContentClick(Sender: TObject);
    procedure pmnuPasteJsonContentClick(Sender: TObject);
    procedure pmnuCopyJsonContentClick(Sender: TObject);
    procedure btnAddClick(Sender: TObject);
    procedure btnDeleteClick(Sender: TObject);
    procedure btnSaveClick(Sender: TObject);
    procedure btnEditClick(Sender: TObject);
    procedure btnCopyPresetClick(Sender: TObject);
    procedure edtBaseURLPropertiesChange(Sender: TObject);
    procedure EditorURLParamsChange(Sender: TObject);
    procedure comboMethodPropertiesChange(Sender: TObject);
    procedure tbbtnAutoOpenLastUsedClick(Sender: TObject);
    procedure dxBarButton2Click(Sender: TObject);
    procedure tbbtnAutoSaveProjectClick(Sender: TObject);
    procedure EditorContentMouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure EditorPostDataMouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure PopupEditorPopup(Sender: TObject);
    procedure pmnuUndoJsonContentClick(Sender: TObject);
    procedure pmnuRedoJsonContentClick(Sender: TObject);
    procedure pmnuSelectAllClick(Sender: TObject);
    procedure pmnuCompactJsonClick(Sender: TObject);
    procedure EditorResponseHeadersMouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure EditorExtraHeadersMouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure btnCopyFullURLClick(Sender: TObject);
    procedure EditorURLParamsMouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure pmnuCompactXMLClick(Sender: TObject);
    procedure comboPostContentTypePropertiesChange(Sender: TObject);
    procedure pmnuBiggerEditorClick(Sender: TObject);
    procedure EditorContentKeyUp(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    procedure edtFullURLPropertiesChange(Sender: TObject);
    procedure btnPasteFullURLClick(Sender: TObject);
    procedure EditorResponseHeadersKeyUp(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    procedure EditorURLParamsKeyUp(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    procedure EditorExtraHeadersKeyUp(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    procedure EditorPostDataKeyUp(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    procedure EditorEnter(Sender: TObject);
    procedure EditorExit(Sender: TObject);
    procedure tbbtnVariablesClick(Sender: TObject);
    procedure memDataPresetsAfterScroll(DataSet: TDataSet);
    procedure memDataPresetsAfterEdit(DataSet: TDataSet);
    procedure memDataPresetsBeforeScroll(DataSet: TDataSet);
    procedure cxGridDBTableViewDblClick(Sender: TObject);
    procedure tbbtnWebServicePerformanceClick(Sender: TObject);
    procedure pmnuFormatJsonContentClick(Sender: TObject);
  private
    FProjectConfig: TJSONConfig;
    FActiveProject: string;
    FIsTempProjectLoaded: Boolean;
    FTempProjectFile: string;
    FRoamingSavePath: string;
    FSearchFromCaret: Boolean;
    FLastResponseContent: string;
    FHTTP: TIdHTTP;
    function GetTempProjectFile: string;
    procedure UpdateFullURL;
    procedure LoadProject;
    procedure SaveProject;
    procedure SaveProjectAs(const LoadAfterSave: Boolean);
    procedure Run;
    procedure AddPreset(const AGuid: string; const APresetName: string; const APresetCategory: string);
    procedure LoadPreset;
    procedure SavePreset;
    procedure ShowError(const AErrorText: string);
    procedure DoGETRequest(const AMethodType: TMethodType);
    procedure DoPOSTRequest(const AMethodType: TMethodType);
    procedure UpdateEditor(const AText: string);
    procedure CheckForUpdate;
    procedure DeactivatePresetEvents;
    procedure ActivatePresetEvents;
    function GenerateGuid: string;
    function Encode(const AText: string): string;
    function Decode(const AText: string): string;
    function ReplaceGlobalVar(const AText: string): string;
    procedure ParseAndFormatJsonAndXML(const AFormatted: Boolean);
    function GetAppVersion: string;
  public
    MainConfig: TJSONConfig;
    ActiveEditor: TSynEdit;
    gbSearchBackwards: Boolean;
    gbSearchCaseSensitive: Boolean;
    gbSearchFromCaret: Boolean;
    gbSearchSelectionOnly: Boolean;
    gbSearchTextAtCaret: Boolean;
    gbSearchWholeWords: Boolean;
    gbSearchRegex: Boolean;

    gsSearchText: string;
    gsSearchTextHistory: string;
    gsReplaceText: string;
    gsReplaceTextHistory: string;
    procedure DoSearchReplaceText(AReplace: Boolean; ABackwards: Boolean);
    procedure ShowSearchReplaceDialog(AReplace: Boolean);
  end;

const
  iCurrentConfigVersion: Integer = 3;


var
  frmMain: TfrmMain;

implementation

{$R *.dfm}

uses System.NetEncoding, System.UITypes, _EncryptStr, JsonDataObjects, IdGlobal, System.IOUtils, uRwXmlDOM,
  _frmBiggerEditor, IdUri, _frmSearch, ShellApi, _frmVariables, _frmAddEditPreset,
  _frmWebServicePerformance, _frmAbout;

procedure TfrmMain.tbbtnRunClick(Sender: TObject);
begin
  Run;
end;

procedure TfrmMain.tbbtnStayOnTopClick(Sender: TObject);
begin
  if tbbtnStayOnTop.Down then
  begin
    FormStyle := fsStayOnTop;
  end
  else
  begin
    FormStyle := fsNormal;
  end;
end;

procedure TfrmMain.tbbtnVariablesClick(Sender: TObject);
begin
  frmVariables.ShowModal;
  SaveProject;
  UpdateFullURL;
end;

procedure TfrmMain.tbbtnWebServicePerformanceClick(Sender: TObject);
begin
  frmWebServicePerformance.ShowModal;
end;

procedure TfrmMain.UpdateEditor(const AText: string);
begin
  ActiveEditor.Lines.Text := AText;
end;

procedure TfrmMain.UpdateFullURL;
var
  i: Integer;
  dmy: string;
  strlist: TStringList;
begin
  edtFullURL.Text := ReplaceGlobalVar(edtBaseURL.Text);
  if EditorURLParams.Lines.Count > 0 then
  begin
    dmy := '?';
    for i := 0 to EditorURLParams.Lines.Count - 1 do
    begin
      if dmy <> '?' then dmy := dmy + '&';
      strlist := TStringList.Create;
      try
        strlist.Text := StringReplace(ReplaceGlobalVar(EditorURLParams.Lines[i]), '=', #13#10, [rfReplaceAll]);
        if strlist.Count = 2 then
        begin
          dmy := dmy + TNetEncoding.URL.Encode(strlist[0]) + '=' + TNetEncoding.URL.Encode(strlist[1]);
        end;
      finally
        strlist.Free;
      end;
    end;

    edtFullURL.Text := edtFullURL.Text + dmy;
  end;
end;

procedure TfrmMain.tbbtnAutoOpenLastUsedClick(Sender: TObject);
begin
  MainConfig.WriteBool('Project', 'AutoOpenLastUsed', tbbtnAutoOpenLastUsed.Down);
end;

procedure TfrmMain.tbbtnAutoSaveProjectClick(Sender: TObject);
begin
  MainConfig.WriteBool('Project', 'AutoSaveProject', tbbtnAutoSaveProject.Down);
end;

procedure TfrmMain.dxBarButton2Click(Sender: TObject);
begin
  FActiveProject := GetTempProjectFile;
  FIsTempProjectLoaded := True;
  LoadProject;
end;

procedure TfrmMain.dxBarButton3Click(Sender: TObject);
begin
  OpenDialog.InitialDir := MainConfig.ReadString('Dialogs', 'LastOpenDir', ExtractFilePath(ParamStr(0)));
  if OpenDialog.Execute then
  begin
    MainConfig.WriteString('Dialogs', 'LastOpenDir', ExtractFilePath(OpenDialog.FileName));
    FActiveProject := OpenDialog.FileName;
    FIsTempProjectLoaded := False;
    LoadProject;
  end;
end;

procedure TfrmMain.ActivatePresetEvents;
begin
  memDataPresets.BeforeScroll := memDataPresetsBeforeScroll;
  memDataPresets.AfterScroll := memDataPresetsAfterScroll;
  memDataPresets.AfterEdit := memDataPresetsAfterEdit;
end;

procedure TfrmMain.AddPreset(const AGuid: string; const APresetName: string; const APresetCategory: string);
begin
  DeactivatePresetEvents;

  memDataPresets.Append;
  memDataPresetsfldName.Value := APresetName;
  memDataPresetsfldCategory.Value := APresetCategory;
  memDataPresetsfldGuid.Value := AGuid;
  memDataPresets.Post;

  LoadPreset;

  ActivatePresetEvents;

  SavePreset;
end;

procedure TfrmMain.btnAddClick(Sender: TObject);
begin
  frmAddEditPreset.edtPresetName.Text := '';
  frmAddEditPreset.edtCategory.Text := '';

  if frmAddEditPreset.ShowModal = mrOk then
  begin
    SavePreset;
    AddPreset(GenerateGuid, frmAddEditPreset.edtPresetName.Text, frmAddEditPreset.edtCategory.Text);
  end;
end;

procedure TfrmMain.btnDeleteClick(Sender: TObject);
begin
  if memDataPresets.RecordCount = 1 then
  begin
    MessageDlg('Thet last preset can''t be deleted. Please create a new preset if you want to delete this.', mtError, [mbOK], 0);
  end
  else
  begin
    if MessageDlg('Really delete preset "' + memDataPresetsfldName.Value + '"?', mtConfirmation, [mbYes, mbNo], 0) = mrYes then
    begin
      DeactivatePresetEvents;
      FProjectConfig.DeleteSection('Preset_' + memDataPresetsfldGuid.Value);
      memDataPresets.Delete;
      FProjectConfig.Save;
      LoadPreset;
      ActivatePresetEvents;
    end;
  end;
end;

procedure TfrmMain.btnEditClick(Sender: TObject);
var
  OldName: string;
begin
  OldName := memDataPresetsfldName.Value;
  frmAddEditPreset.edtPresetName.Text := memDataPresetsfldName.Value;
  frmAddEditPreset.edtCategory.Text := memDataPresetsfldCategory.Value;
  if frmAddEditPreset.ShowModal = mrOk then
  begin
    SavePreset;
    memDataPresets.Edit;
    memDataPresetsfldName.Value := frmAddEditPreset.edtPresetName.Text;
    memDataPresetsfldCategory.Value := frmAddEditPreset.edtCategory.Text;
    memDataPresets.Post;

    memDataVariables.First;
    while not memDataVariables.Eof do
    begin
      if memDataVariablesfldPresetFilter.Value = OldName then
      begin
        memDataVariables.Edit;
        memDataVariablesfldPresetFilter.Value := frmAddEditPreset.edtPresetName.Text;
        memDataVariables.Post;
      end;

      memDataVariables.Next;
    end;

    SaveProject;
  end;
end;

procedure TfrmMain.btnPasteFullURLClick(Sender: TObject);
begin
  edtFullURL.Clear;
  edtFullURL.SetFocus;
  edtFullURL.PasteFromClipboard;
end;

procedure TfrmMain.btnCopyFullURLClick(Sender: TObject);
begin
  edtFullURL.SelectAll;
  edtFullURL.CopyToClipboard;
end;

procedure TfrmMain.btnCopyPresetClick(Sender: TObject);
var
  guid: string;
  name, category: string;
begin
  guid := GenerateGuid;
  FProjectConfig.CopySection('Preset_' + memDataPresetsfldGuid.Value, 'Preset_' + guid);
  FProjectConfig.WriteString('Preset_' + guid, 'Guid', guid);
  SaveProject;
  name := 'Copy of ' + memDataPresetsfldName.Value;
  category := memDataPresetsfldCategory.Value;
  AddPreset(guid, name, category);
end;

procedure TfrmMain.btnRunClick(Sender: TObject);
begin
  Run;
end;

procedure TfrmMain.btnSaveAsClick(Sender: TObject);
begin
  SaveProjectAs(True);
end;

procedure TfrmMain.btnSaveClick(Sender: TObject);
begin
  SaveProject;
end;

procedure TfrmMain.CheckForUpdate;
begin
  TThread.CreateAnonymousThread(
  procedure
  var
    HTTP: TIdHTTP;
    bNewVersion: Boolean;
    strlist: TStringList;
    fs: TFileStream;
    UpdateFile: TStringList;
  begin
    bNewVersion := False;
    HTTP := TIdHTTP.Create(nil);
    HTTP.IOHandler := SSLIOHandlerSocketOpenSSL;
    HTTP.HTTPOptions := [hoKeepOrigProtocol];
    HTTP.HandleRedirects := True;
    HTTP.AllowCookies := True;
    HTTP.Request.UserAgent := 'Simnet REST Debugger/' + GetAppVersion;
    strlist := TStringList.Create;
    try
      try
        strlist.Text := HTTP.Get('https://www.simnet.cx/WebUpdate/RESTDebugger/Update.txt');
      except
        bNewVersion := False;
        raise;
      end;

      if strlist.Count = 2 then
      begin
        if strlist[0] = GetAppVersion then
        begin
          bNewVersion := False;
        end
        else
        begin
          DeleteFile(IncludeTrailingPathDelimiter(TPath.GetTempPath) + 'Update.exe');
          fs := TFileStream.Create(IncludeTrailingPathDelimiter(TPath.GetTempPath) + 'Update.exe', fmCreate);
          try
            try
              HTTP.Get(strlist[1], fs);
            except
              bNewVersion := False;
              raise;
            end;
          finally
            fs.Free;
          end;

          bNewVersion := True;
        end;
      end;
    finally
      strlist.Free;
      HTTP.Free;

      TThread.Synchronize(TThread.CurrentThread,
      procedure
      begin
        if bNewVersion then
        begin
          //Create Update file and start it
          UpdateFile := TStringList.Create;
          try
            UpdateFile.Add('timeout 1');
            UpdateFile.Add('taskkill /F /IM RESTDebugger.exe');
            UpdateFile.Add('"%~dp0Update.exe" -o"' + ExtractFilePath(ParamStr(0)) + '" -y');
            UpdateFile.Add('start "" "' + ParamStr(0) + '"');
            UpdateFile.Add('del "%~dp0Update.exe"');
            UpdateFile.Add('del "%~dp0Update.cmd"');
            UpdateFile.SaveToFile(IncludeTrailingPathDelimiter(TPath.GetTempPath) + 'Update.cmd');
          finally
            UpdateFile.Free;
          end;

          if MessageDlg('Es ist eine neue Software Version verfügbar. Möchten Sie diese nun installieren?', mtConfirmation, [mbYes, mbNo], 0) = mrYes then
          begin
            ShellExecute(handle, 'open', PChar(IncludeTrailingPathDelimiter(TPath.GetTempPath) + 'Update.cmd'), nil, nil, SW_HIDE);
            Close;
          end
          else
          begin
            //CleanUp temp files
            DeleteFile(IncludeTrailingPathDelimiter(TPath.GetTempPath) + 'Update.cmd');
            DeleteFile(IncludeTrailingPathDelimiter(TPath.GetTempPath) + 'Update.exe');
          end;
        end;
      end);
    end;
  end).Start;
end;

procedure TfrmMain.comboMethodPropertiesChange(Sender: TObject);
begin
  tabPostData.TabVisible := (comboMethod.ItemIndex = 1) or (comboMethod.ItemIndex = 2) or (comboMethod.ItemIndex = 3);

  case comboMethod.ItemIndex of
    1: tabPostData.Caption := 'Post Data';
    2: tabPostData.Caption := 'Patch Data';
    3: tabPostData.Caption := 'Put Data';
  end;

  if (comboMethod.ItemIndex = 0) and (PageControlParams.ActivePage = nil) then
  begin
    PageControlParams.ActivePage := tabURLParams;
  end;
end;

procedure TfrmMain.comboPostContentTypePropertiesChange(Sender: TObject);
begin
  if Pos('application/json', comboPostContentType.Text) > 0 then
  begin
    EditorPostData.Highlighter := SynJSONSyn;
  end
  else
  if Pos('text/xml', comboPostContentType.Text) > 0 then
  begin
    EditorPostData.Highlighter := SynXMLSyn;
  end
  else
  if Pos('text/html', comboPostContentType.Text) > 0 then
  begin
    EditorPostData.Highlighter := SynHTMLSyn;
  end
  else
  begin
    EditorPostData.Highlighter := nil;
  end;
end;

procedure TfrmMain.cxGridDBTableViewDblClick(Sender: TObject);
begin
  btnEditClick(Sender);
end;

procedure TfrmMain.pmnuBiggerEditorClick(Sender: TObject);
begin
  frmBiggerEditor.ShowModal;
  UpdateFullURL;
end;

procedure TfrmMain.pmnuCompactJsonClick(Sender: TObject);
var
  ja: TJsonArray;
  jo: TJsonObject;
begin
  if ActiveEditor.Lines.Count > 0 then
  begin
    if Length(ActiveEditor.Lines[0]) > 0 then
    begin
      if ActiveEditor.Lines[0][1] = '{' then
      begin
        jo := TJsonObject.Create;
        Screen.Cursor := crHourGlass;
        try
          try
            jo.FromJSON(ActiveEditor.Lines.Text);
          except
            on E: Exception do
            begin
              ShowError(E.Message);
            end;
          end;
          UpdateEditor(jo.ToJSON(True));
        finally
          Screen.Cursor := crDefault;
          jo.Free;
        end;
      end
      else
      if ActiveEditor.Lines[0][1] = '[' then
      begin
        ja := TJsonArray.Create;
        Screen.Cursor := crHourGlass;
        try
          try
            ja.FromJSON(ActiveEditor.Lines.Text);
          except
            on E: Exception do
            begin
              ShowError(E.Message);
            end;
          end;
          UpdateEditor(ja.ToJSON(True));
        finally
          Screen.Cursor := crDefault;
          ja.Free;
        end;
      end;
    end;
  end;
end;

procedure TfrmMain.pmnuCompactXMLClick(Sender: TObject);
var
  xml: TRwDomDocument;
begin
  xml := TRwDOMDocument.Create;
  Screen.Cursor := crHourGlass;
  try
    xml.LoadXML(ActiveEditor.Lines.Text);
    UpdateEditor(xml.XML);
  finally
    xml.Free;
    Screen.Cursor := crDefault;
  end;
end;

procedure TfrmMain.pmnuCopyJsonContentClick(Sender: TObject);
begin
  ActiveEditor.CopyToClipboard;
end;

procedure TfrmMain.pmnuCutJsonContentClick(Sender: TObject);
begin
  ActiveEditor.CutToClipboard;
end;

procedure TfrmMain.pmnuFormatJsonContentClick(Sender: TObject);
begin
  ParseAndFormatJsonAndXML(True);
end;

procedure TfrmMain.DeactivatePresetEvents;
begin
  memDataPresets.BeforeScroll := nil;
  memDataPresets.AfterScroll := nil;
  memDataPresets.AfterEdit := nil;
end;

function TfrmMain.Decode(const AText: string): string;
begin
  try
    Result := TNetEncoding.Base64.Decode(AText);
  except
    Result := '';
  end;
end;

procedure TfrmMain.DoGETRequest(const AMethodType: TMethodType);
var
  dmy: string;
begin
  try
    case AMethodType of
      mtGET: dmy := FHTTP.Get(edtFullURL.Text);
      mtDELETE: dmy := FHTTP.Delete(edtFullURL.Text);
    end;

    if FHTTP.ResponseCode.ToString[1] <> '2' then
    begin
      ShowError(FHTTP.ResponseText);
    end;
  except
    on E: EIdHTTPProtocolException do
    begin
      dmy := E.ErrorMessage;
      ShowError(E.Message);
    end;
  end;

  // Convert from UTF-8
  dmy := UTF8Decode(dmy);

  ActiveEditor := EditorContent;
  UpdateEditor(dmy);

  ReplaceGlobalVar(dmy);

  ActiveEditor := EditorResponseHeaders;
  UpdateEditor(FHTTP.Response.RawHeaders.Text);
end;

procedure TfrmMain.DoPOSTRequest(const AMethodType: TMethodType);
var
  dmy: string;
  strstream: TStringStream;
begin
  strstream := TStringStream.Create(ReplaceGlobalVar(EditorPostData.Lines.Text), TEncoding.UTF8);
  try
    FHTTP.Request.ContentType := comboPostContentType.Text;
    FHTTP.Request.CharSet := 'utf-8';
    try
      case AMethodType of
        mtPOST: dmy := FHTTP.Post(edtFullURL.Text, strstream);
        mtPATCH: dmy := FHTTP.Patch(edtFullURL.Text, strstream);
        mtPUT: dmy := FHTTP.Put(edtFullURL.Text, strstream);
      end;

      if FHTTP.ResponseCode.ToString[1] <> '2' then
      begin
        ShowError(FHTTP.ResponseText);
      end;
    except
      on E: EIdHTTPProtocolException do
      begin
        dmy := E.ErrorMessage;
        ShowError(E.Message);
      end;
    end;
  finally
    strstream.Free;
  end;

  // Convert from UTF-8
  dmy := UTF8Decode(dmy);

  ActiveEditor := EditorContent;
  UpdateEditor(dmy);
  ActiveEditor := EditorResponseHeaders;
  UpdateEditor(FHTTP.Response.RawHeaders.Text);
end;

procedure TfrmMain.DoSearchReplaceText(AReplace, ABackwards: Boolean);
var
  Options: TSynSearchOptions;
begin
  if AReplace then
    Options := [ssoPrompt, ssoReplace, ssoReplaceAll]
  else
    Options := [];
  if ABackwards then
    Include(Options, ssoBackwards);
  if gbSearchCaseSensitive then
    Include(Options, ssoMatchCase);
  if not FSearchFromCaret then
    Include(Options, ssoEntireScope);
  if gbSearchSelectionOnly then
    Include(Options, ssoSelectedOnly);
  if gbSearchWholeWords then
    Include(Options, ssoWholeWord);
  if gbSearchRegex then
    ActiveEditor.SearchEngine := SynEditRegexSearch
  else
    ActiveEditor.SearchEngine := SynEditSearch;

  if ActiveEditor.SearchReplace(gsSearchText, gsReplaceText, Options) = 0 then
  begin
    MessageDlg('Text not found.', mtInformation, [mbOK], 0);
  end;
end;

procedure TfrmMain.dxBarButton6Click(Sender: TObject);
begin
  Close;
end;

procedure TfrmMain.dxBarButton7Click(Sender: TObject);
begin
  frmAbout.ShowModal;
end;

procedure TfrmMain.dxBarSubItem3Popup(Sender: TObject);
begin
  btnSave.Enabled := not FIsTempProjectLoaded;
end;

procedure TfrmMain.EditorEnter(Sender: TObject);
begin
  (Sender as TSynEdit).ActiveLineColor := $00E6FFFA;
end;

procedure TfrmMain.EditorExit(Sender: TObject);
begin
  (Sender as TSynEdit).ActiveLineColor := clNone;
end;

procedure TfrmMain.EditorContentKeyUp(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  if Key = vkF11 then
  begin
    ActiveEditor := EditorContent;
    pmnuBiggerEditorClick(Sender);
  end;

  if Key = vkF3 then
  begin
    ActiveEditor := EditorContent;
    DoSearchReplaceText(false, gbSearchBackwards);
  end;

  if (Key = 70) and (Shift = [ssCtrl]) then
  begin
    ActiveEditor := EditorContent;
    ShowSearchReplaceDialog(false);
  end;
end;

procedure TfrmMain.EditorContentMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  if Button = mbRight then
  begin
    ActiveEditor := EditorContent;
    PopupEditor.PopupFromCursorPos;
  end;
end;

procedure TfrmMain.EditorExtraHeadersKeyUp(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  if Key = vkF11 then
  begin
    ActiveEditor := EditorExtraHeaders;
    pmnuBiggerEditorClick(Sender);
  end;
end;

procedure TfrmMain.EditorExtraHeadersMouseUp(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  if Button = mbRight then
  begin
    ActiveEditor := EditorExtraHeaders;
    PopupEditor.PopupFromCursorPos;
  end;
end;

procedure TfrmMain.EditorResponseHeadersKeyUp(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  if Key = vkF11 then
  begin
    ActiveEditor := EditorResponseHeaders;
    pmnuBiggerEditorClick(Sender);
  end;
end;

procedure TfrmMain.EditorResponseHeadersMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  if Button = mbRight then
  begin
    ActiveEditor := EditorResponseHeaders;
    PopupEditor.PopupFromCursorPos;
  end;
end;

procedure TfrmMain.EditorPostDataKeyUp(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  if Key = vkF11 then
  begin
    ActiveEditor := EditorPostData;
    pmnuBiggerEditorClick(Sender);
  end;
end;

procedure TfrmMain.EditorPostDataMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  if Button = mbRight then
  begin
    ActiveEditor := EditorPostData;
    PopupEditor.PopupFromCursorPos;
  end;
end;

procedure TfrmMain.EditorURLParamsChange(Sender: TObject);
begin
  UpdateFullURL;
end;

procedure TfrmMain.EditorURLParamsKeyUp(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  if Key = vkF11 then
  begin
    ActiveEditor := EditorURLParams;
    pmnuBiggerEditorClick(Sender);
  end;
end;

procedure TfrmMain.EditorURLParamsMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  if Button = mbRight then
  begin
    ActiveEditor := EditorURLParams;
    PopupEditor.PopupFromCursorPos;
  end;
end;

procedure TfrmMain.edtBaseURLPropertiesChange(Sender: TObject);
begin
  if edtBaseURL.Focused then
  begin
    UpdateFullURL;
  end;
end;

procedure TfrmMain.edtFullURLPropertiesChange(Sender: TObject);
var
  uri: TIdURI;
begin
  if edtFullURL.Focused then
  begin
    uri := TIdURI.Create(edtFullURL.Text);
    try
      EditorURLParams.Lines.Text := StringReplace(uri.Params, '&', #13#10, [rfReplaceAll]);
      edtBaseURL.Text := StringReplace(edtFullURL.Text, '?' + uri.Params, '', [rfReplaceAll]);
    finally
      uri.Free;
    end;
  end;
end;

function TfrmMain.Encode(const AText: string): string;
begin
  Result := StringReplace(TNetEncoding.Base64.Encode(AText), #13#10, '', [rfReplaceAll]);
end;

procedure TfrmMain.FormClose(Sender: TObject; var Action: TCloseAction);
var
  strstream: TStringStream;
begin
  MainConfig.WriteInteger('MainWindowPos', 'Left', Left);
  MainConfig.WriteInteger('MainWindowPos', 'Top', Top);
  MainConfig.WriteInteger('MainWindowPos', 'Width', Width);
  MainConfig.WriteInteger('MainWindowPos', 'Height', Height);
  MainConfig.WriteBool('MainWindowPos', 'StayOnTop', FormStyle = fsStayOnTop);
  MainConfig.WriteInteger('MainWindowPos', 'HeaderHeight', pnlHeader.Height);
  MainConfig.WriteInteger('MainWindowPos', 'PresetPanelWidth', pnlPresets.Width);

  MainConfig.WriteBool('Search', 'gbSearchBackwards', gbSearchBackwards);
  MainConfig.WriteBool('Search', 'gbSearchCaseSensitive', gbSearchCaseSensitive);
  MainConfig.WriteBool('Search', 'gbSearchFromCaret', gbSearchFromCaret);
  MainConfig.WriteBool('Search', 'gbSearchSelectionOnly', gbSearchSelectionOnly);
  MainConfig.WriteBool('Search', 'gbSearchTextAtCaret', gbSearchTextAtCaret);
  MainConfig.WriteBool('Search', 'gbSearchWholeWords', gbSearchWholeWords);
  MainConfig.WriteBool('Search', 'gbSearchRegex', gbSearchRegex);

  MainConfig.WriteString('Search', 'gsSearchText', gsSearchText);
  MainConfig.WriteString('Search', 'gsSearchTextHistory', gsSearchTextHistory);
  MainConfig.WriteString('Search', 'gsReplaceText', gsReplaceText);
  MainConfig.WriteString('Search', 'gsReplaceTextHistory', gsReplaceTextHistory);


  SavePreset;

  // Save presets grid layout
  strstream := TStringStream.Create;
  try
    cxGridDBTableView.StoreToStream(strstream);
    FProjectConfig.WriteString('Main', 'GridViewPresets', Encode(strstream.DataString));
  finally
    strstream.Free;
  end;

  if FProjectConfig.FileModified then
  begin
    if (tbbtnAutoSaveProject.Down) and (not FIsTempProjectLoaded) then
    begin
      SaveProject;
    end
    else
    begin
      if MessageDlg('The project file has changed. Do you want to save it?', mtConfirmation, [mbYes, mbNo], 0) = mrYes then
      begin
        if FIsTempProjectLoaded then
        begin
          SaveProjectAs(False);
        end
        else
        begin
          SaveProject;
        end;
      end;
    end;
  end;

  if FileExists(FTempProjectFile) then
  begin
    DeleteFile(FTempProjectFile);
  end;
end;

procedure TfrmMain.FormCreate(Sender: TObject);
begin
  FRoamingSavePath := IncludeTrailingPathDelimiter(TPath.GetHomePath) + 'Simnet\RESTDebugger\';
  ForceDirectories(FRoamingSavePath);

  MainConfig := TJSONConfig.Create(FRoamingSavePath + 'AppConfig.json');
  FActiveProject := '';


  ActiveEditor := nil;

  if (not MainConfig.ReadBool('Main', 'FirstStartShown')) and (FileExists(IncludeTrailingPathDelimiter(ExtractFilePath(ParamStr(0))) + 'Example Config.rdproj')) then
  begin
    FActiveProject := IncludeTrailingPathDelimiter(ExtractFilePath(ParamStr(0))) + 'Example Config.rdproj';
    FIsTempProjectLoaded := False;
    MainConfig.WriteBool('Main', 'FirstStartShown', True);
    MainConfig.WriteString('Project', 'LastUsed', FActiveProject);
  end
  else
  begin
    if (ParamStr(1) <> '') then
    begin
      FActiveProject := ParamStr(1);
      FIsTempProjectLoaded := False;
    end
    else
    begin
      if (MainConfig.ReadBool('Project', 'AutoOpenLastUsed', True)) and (FileExists(MainConfig.ReadString('Project', 'LastUsed'))) then
      begin
        FActiveProject := MainConfig.ReadString('Project', 'LastUsed');
        FIsTempProjectLoaded := False;
      end
      else
      begin
        FActiveProject := GetTempProjectFile;
        FIsTempProjectLoaded := True;
      end;
    end;
  end;


  with TSearchTextHightlighterSynEditPlugin.Create(EditorContent) do
  begin
    Attribute.Background := $0078AAFF;
  end;

  LoadProject;
end;

procedure TfrmMain.FormDestroy(Sender: TObject);
begin
  FProjectConfig.Free;
  MainConfig.Free;
end;

procedure TfrmMain.FormResize(Sender: TObject);
begin
  Image.Left := Width - 45;
end;

procedure TfrmMain.FormShow(Sender: TObject);
begin
  dxStatusBarApp.Panels[0].Text := 'Copyright © by Simnet ' + FormatDateTime('yyyy', now);

  Left := MainConfig.ReadInteger('MainWindowPos', 'Left', Round((Screen.Width - Width) div 2));

  if Left >= Screen.Width then
  begin
    Left := 10;
  end;

  Top := MainConfig.ReadInteger('MainWindowPos', 'Top', Round((Screen.Height - Height) div 2));

  if Top >= Screen.Height then
  begin
    Top := 10;
  end;


  Width := MainConfig.ReadInteger('MainWindowPos', 'Width', 720);
  Height := MainConfig.ReadInteger('MainWindowPos', 'Height', 930);
  if MainConfig.ReadBool('MainWindowPos', 'StayOnTop') then
  begin
    FormStyle := fsStayOnTop;
    tbbtnStayOnTop.Down := True;
  end
  else
  begin
    FormStyle := fsNormal;
    tbbtnStayOnTop.Down := False;
  end;

  pnlHeader.Height := MainConfig.ReadInteger('MainWindowPos', 'HeaderHeight', 370);
  pnlPresets.Width := MainConfig.ReadInteger('MainWindowPos', 'PresetPanelWidth', 253);

  tbbtnAutoOpenLastUsed.Down := MainConfig.ReadBool('Project', 'AutoOpenLastUsed', True);
  tbbtnAutoSaveProject.Down := MainConfig.ReadBool('Project', 'AutoSaveProject', True);

  EditorContent.Lines.Clear;
  EditorResponseHeaders.Lines.Clear;

  dxStatusBarResponse.Panels[0].Text := '';
  dxStatusBarResponse.Panels[1].Text := '';
  dxStatusBarResponse.Panels[2].Text := '';

  gbSearchBackwards := MainConfig.ReadBool('Search', 'gbSearchBackwards', gbSearchBackwards);
  gbSearchCaseSensitive := MainConfig.ReadBool('Search', 'gbSearchCaseSensitive', gbSearchCaseSensitive);
  gbSearchFromCaret :=MainConfig.ReadBool('Search', 'gbSearchFromCaret', gbSearchFromCaret);
  gbSearchSelectionOnly := MainConfig.ReadBool('Search', 'gbSearchSelectionOnly', gbSearchSelectionOnly);
  gbSearchTextAtCaret := MainConfig.ReadBool('Search', 'gbSearchTextAtCaret', gbSearchTextAtCaret);
  gbSearchWholeWords := MainConfig.ReadBool('Search', 'gbSearchWholeWords', gbSearchWholeWords);
  gbSearchRegex := MainConfig.ReadBool('Search', 'gbSearchRegex', gbSearchRegex);

  gsSearchText := MainConfig.ReadString('Search', 'gsSearchText', gsSearchText);
  gsSearchTextHistory := MainConfig.ReadString('Search', 'gsSearchTextHistory', gsSearchTextHistory);
  gsReplaceText := MainConfig.ReadString('Search', 'gsReplaceText', gsReplaceText);
  gsReplaceTextHistory := MainConfig.ReadString('Search', 'gsReplaceTextHistory', gsReplaceTextHistory);


  CheckForUpdate;
end;

function TfrmMain.GenerateGuid: string;
var
  guid: TGUID;
begin
  CreateGUID(guid);
  Result := GUIDToString(guid);
  Result := StringReplace(Result, '{', '', [rfReplaceAll]);
  Result := StringReplace(Result, '}', '', [rfReplaceAll]);
  Result := StringReplace(Result, '-', '', [rfReplaceAll]);
  Result := LowerCase(Result);
end;

function TfrmMain.GetAppVersion: string;
var
  VerInfoSize: DWORD;
  VerInfo: Pointer;
  VerValueSize: DWORD;
  VerValue: PVSFixedFileInfo;
  dmy: DWORD;
begin
  Result := '';
  VerInfoSize := GetFileVersionInfoSize(PChar(ParamStr(0)), dmy);
  if VerInfoSize = 0 then Exit;

  GetMem(VerInfo, VerInfoSize);
  try
    GetFileVersionInfo(PChar(ParamStr(0)), 0, VerInfoSize, VerInfo);
    VerQueryValue(VerInfo, '\', Pointer(VerValue), VerValueSize);
    with VerValue^ do
    begin
      Result := IntToStr(dwFileVersionMS shr 16);
      Result := Result + '.' + IntToStr(dwFileVersionMS and $FFFF);
      Result := Result + '.' + IntToStr(dwFileVersionLS shr 16);
      Result := Result + '.' + IntToStr(dwFileVersionLS and $FFFF);
    end;
  finally
    FreeMem(VerInfo, VerInfoSize);
  end;
end;

function TfrmMain.GetTempProjectFile: string;
var
  iSize: Integer;
  guid: TGUID;
begin
  iSize := Winapi.Windows.GetTempPath(0, nil);
  SetLength(Result, iSize);
  iSize := Winapi.Windows.GetTempPath(iSize, @Result[1]);
  SetLength(Result, iSize);
  Result := IncludeTrailingPathDelimiter(Result);
  CreateGUID(guid);

  Result := Result + guid.ToString + '.rdproj';
  FTempProjectFile := Result;
end;

procedure TfrmMain.LoadPreset;
var
  preset: string;
begin
  if memDataPresets.RecordCount > 0 then
  begin
    preset := 'Preset_' + memDataPresetsfldGuid.Value;
    edtBaseURL.Text := FProjectConfig.ReadString(preset, 'BaseURL');
    comboMethod.ItemIndex := FProjectConfig.ReadInteger(preset, 'Method', 0);
    EditorURLParams.Lines.Text := FProjectConfig.ReadString(preset, 'URLParams');
    EditorExtraHeaders.Lines.Text := FProjectConfig.ReadString(preset, 'ExtraHeaders');
    EditorPostData.Lines.Text := FProjectConfig.ReadString(preset, 'PostData');
    comboPostContentType.Text := FProjectConfig.ReadString(preset, 'PostDataContentType', 'application/json');
    chkResponseAutoformat.Checked := FProjectConfig.ReadBool(preset, 'ResponseAutoFormat', true);

    HtmlEditor.HTML.Text := Decode(FProjectConfig.ReadString(preset, 'Notes', ''));

    chkUseBasicAuth.Checked := FProjectConfig.ReadBool(preset, 'UseBasicAuth');
    edtAuthUsername.Text := TEncryptStr.DecryptString(FProjectConfig.ReadString(preset, 'AuthUsername'));
    edtAuthPassword.Text := TEncryptStr.DecryptString(FProjectConfig.ReadString(preset, 'AuthPassword'));

    UpdateFullURL;
  end;
end;

procedure TfrmMain.LoadProject;
var
  i: Integer;
  strstream: TStringStream;
  strlist: TStringList;
begin
  DeactivatePresetEvents;

  if FIsTempProjectLoaded then
  begin
    dxStatusBarApp.Panels[1].Text := 'Default project loaded.';
    Caption := 'Simnet REST Debugger';
  end
  else
  begin
    dxStatusBarApp.Panels[1].Text := 'Loaded project: ' + FActiveProject;
    Caption := ExtractFileName(FActiveProject) + ' - Simnet REST Debugger';
    MainConfig.WriteString('Project', 'LastUsed', FActiveProject);
  end;

  if Assigned(FProjectConfig) then
  begin
    FProjectConfig.Free;
  end;


  FProjectConfig := TJSONConfig.Create(FActiveProject, False);
  FProjectConfig.AutoSave := False;

  if FProjectConfig.ReadInteger('Presets', 'ConfigVersion') <> iCurrentConfigVersion then
  begin
    //Upgrade config to newest version
    strlist := TStringList.Create;
    try
      strlist.NameValueSeparator := ':';
      strlist.DelimitedText := FProjectConfig.ReadString('Presets', 'Presets');
      for i := 0 to strlist.Count - 1 do
      begin
        FProjectConfig.WriteString('Preset_' + strlist.Names[i], 'PresetName', Decode(strlist.ValueFromIndex[i]));
        FProjectConfig.WriteString('Preset_' + strlist.Names[i], 'Guid', strlist.Names[i]);
      end;

      FProjectConfig.DeleteKey('Presets', 'Presets');
      FProjectConfig.WriteInteger('Presets', 'ConfigVersion', iCurrentConfigVersion);
      FProjectConfig.Save;
    finally
      strlist.Free;
    end;
  end;

  memDataPresets.Active := False;
  memDataPresets.Active := True;
  try
    strlist := TStringList.Create;
    try
      FProjectConfig.ReadSections(strlist);
      for i := 0 to strlist.Count - 1 do
      begin
        if Pos('Preset_', strlist[i]) = 1 then
        begin
          memDataPresets.Append;
          memDataPresetsfldGuid.Value := FProjectConfig.ReadString(strlist[i], 'Guid');
          memDataPresetsfldName.Value := FProjectConfig.ReadString(strlist[i], 'PresetName');
          memDataPresetsfldCategory.Value := FProjectConfig.ReadString(strlist[i], 'Category');
          memDataPresets.Post;
        end;
      end;
    finally
      strlist.Free;
    end;
  finally
    memDataPresets.Active := True;
  end;

  LoadPreset;

  if memDataPresets.RecordCount = 0 then
  begin
    // Add default
    AddPreset(GenerateGuid, 'Default', '');
    FProjectConfig.Save;
  end
  else
  begin
    memDataPresets.Locate('fldGuid', FProjectConfig.ReadString('Presets', 'LastUsed'), [loPartialKey]);
    LoadPreset;
  end;

  PageControlParams.ActivePage := tabURLParams;
  PageControlResponse.ActivePage := tabContent;

  strlist := TStringList.Create;
  try
    strlist.Text := FProjectConfig.ReadString('Main', 'GlobalVar');
    memDataVariables.Active := False;
    memDataVariables.LoadFromStrings(strlist);
    memDataVariables.Active := True;
  finally
    strlist.Free;
  end;

  UpdateFullURL;


  strstream := TStringStream.Create(Decode(FProjectConfig.ReadString('Main', 'GridViewPresets')));
  try
    cxGridDBTableView.RestoreFromStream(strstream);
  finally
    strstream.Free;
  end;

  ActivatePresetEvents;
end;

procedure TfrmMain.memDataPresetsAfterEdit(DataSet: TDataSet);
begin
  SavePreset;
end;

procedure TfrmMain.memDataPresetsAfterScroll(DataSet: TDataSet);
begin
  if memDataPresets.RecordCount > 0 then
  begin
    LoadPreset;
  end;
end;

procedure TfrmMain.memDataPresetsBeforeScroll(DataSet: TDataSet);
begin
  if memDataPresets.RecordCount > 0 then
  begin
    SavePreset;
  end;
end;

procedure TfrmMain.ParseAndFormatJsonAndXML(const AFormatted: Boolean);
var
  ja: TJsonArray;
  jo: TJsonObject;
  xml: TRwDomDocument;
  URLFilterMatch, PresetFilterMatch: Boolean;
begin
  if ActiveEditor.Lines.Count > 0 then
  begin
    if Length(ActiveEditor.Lines[0]) > 0 then
    begin
      if Pos('/json', FLastResponseContent) > 0 then
      begin
        EditorContent.Highlighter := SynJSONSyn;

        if ActiveEditor.Lines[0][1] = '{' then
        begin
          jo := TJsonObject.Create;
          Screen.Cursor := crHourGlass;
          try
            try
              jo.FromJSON(ActiveEditor.Lines.Text);
            except
              on E: Exception do
              begin
                ShowError(E.Message);
              end;
            end;
            UpdateEditor(jo.ToJSON(not AFormatted));

            memDataVariables.First;
            while not memDataVariables.Eof do
            begin
              if memDataVariablesfldURLFilter.Text = '' then
              begin
                URLFilterMatch := True;
              end
              else
              begin
                URLFilterMatch := Pos(memDataVariablesfldURLFilter.Text, edtFullURL.Text) > 0;
              end;

              if memDataVariablesfldPresetFilter.Value = '' then
              begin
                PresetFilterMatch := True;
              end
              else
              begin
                PresetFilterMatch := memDataPresetsfldName.Value = memDataVariablesfldPresetFilter.Value;
              end;


              if (memDataVariablesfldType.Value = 'XPath') and (memDataVariablesfldContent.Value = 'JSON') and (URLFilterMatch) and (PresetFilterMatch) then
              begin
                memDataVariables.Edit;
                memDataVariablesfldValue.Value := jo.Path[memDataVariablesfldXPath.Value].Value;
                memDataVariables.Post;
                SaveProject;
              end;

              memDataVariables.Next;
            end;

          finally
            Screen.Cursor := crDefault;
            jo.Free;
          end;
        end
        else
        if ActiveEditor.Lines[0][1] = '[' then
        begin
          ja := TJsonArray.Create;
          Screen.Cursor := crHourGlass;
          try
            try
              ja.FromJSON(ActiveEditor.Lines.Text);
            except
              on E: Exception do
              begin
                ShowError(E.Message);
              end;
            end;
            UpdateEditor(ja.ToJSON(not AFormatted));

            // XPath not supported on Json Arrays only Json Objects, do nothing here

          finally
            Screen.Cursor := crDefault;
            ja.Free;
          end;
        end;
      end
      else
      if Pos('/xml', FLastResponseContent) > 0 then
      begin
        EditorContent.Highlighter := SynXMLSyn;

        xml := TRwDOMDocument.Create;
        try
          Screen.Cursor := crHourGlass;
          xml.LoadXML(ActiveEditor.Lines.Text);
          if AFormatted then
          begin
            UpdateEditor(xml.FormattedXML);
          end
          else
          begin
            UpdateEditor(xml.XML);
          end;

          memDataVariables.First;
          while not memDataVariables.Eof do
          begin
            if memDataVariablesfldURLFilter.Text = '' then
            begin
              URLFilterMatch := True;
            end
            else
            begin
              URLFilterMatch := Pos(memDataVariablesfldURLFilter.Text, edtFullURL.Text) > 0;
            end;

            if memDataVariablesfldPresetFilter.Value = '' then
            begin
              PresetFilterMatch := True;
            end
            else
            begin
              PresetFilterMatch := memDataPresetsfldName.Value = memDataVariablesfldPresetFilter.Value;
            end;

            if (memDataVariablesfldType.Value = 'XPath') and (memDataVariablesfldContent.Value = 'XML') and (URLFilterMatch) and (PresetFilterMatch) then
            begin
              memDataVariables.Edit;
              try
                if Assigned(xml.SelectSingleNode(memDataVariablesfldXPath.Value)) then
                begin
                  memDataVariablesfldValue.Value := xml.SelectSingleNode(memDataVariablesfldXPath.Value).NodeValue;
                end;
              except
              end;
              memDataVariables.Post;
              SaveProject;
            end;

            memDataVariables.Next;
          end;


        finally
          xml.Free;
          Screen.Cursor := crDefault;
        end;
      end
      else
      if Pos('text/html', FLastResponseContent) > 0 then
      begin
        EditorContent.Highlighter := SynHTMLSyn;
      end
      else
      begin
        EditorContent.Highlighter := nil;
      end;
    end;
  end;
end;

procedure TfrmMain.pmnuPasteJsonContentClick(Sender: TObject);
begin
  ActiveEditor.PasteFromClipboard;
end;

procedure TfrmMain.pmnuRedoJsonContentClick(Sender: TObject);
begin
  ActiveEditor.Redo;
end;

procedure TfrmMain.pmnuSelectAllClick(Sender: TObject);
begin
  ActiveEditor.SelectAll;
end;

procedure TfrmMain.pmnuUndoJsonContentClick(Sender: TObject);
begin
  ActiveEditor.Undo;
end;

procedure TfrmMain.PopupEditorPopup(Sender: TObject);
begin
  pmnuPasteJsonContent.Enabled := ActiveEditor.CanPaste;
  pmnuUndoJsonContent.Enabled := ActiveEditor.CanUndo;
  pmnuRedoJsonContent.Enabled := ActiveEditor.CanRedo;
  if ActiveEditor.Highlighter = SynJSONSyn then
  begin
    pmnuFormatJsonContent.Visible := ivAlways;
    pmnuCompactJson.Visible := ivAlways;
    pmnuFormatXML.Visible := ivNever;
    pmnuCompactXML.Visible := ivNever;
  end
  else
  if ActiveEditor.Highlighter = SynXMLSyn then
  begin
    pmnuFormatJsonContent.Visible := ivNever;
    pmnuCompactJson.Visible := ivNever;
    pmnuFormatXML.Visible := ivAlways;
    pmnuCompactXML.Visible := ivAlways;
  end
  else
  begin
    pmnuFormatJsonContent.Visible := ivNever;
    pmnuCompactJson.Visible := ivNever;
    pmnuFormatXML.Visible := ivNever;
    pmnuCompactXML.Visible := ivNever;
  end;

  if ActiveEditor = frmBiggerEditor.EditorBiggerEditor then
  begin
    pmnuBiggerEditor.Visible := ivNever;
  end
  else
  begin
    pmnuBiggerEditor.Visible := ivAlways;
  end;
end;

function TfrmMain.ReplaceGlobalVar(const AText: string): string;
begin
  Result := AText;

  memDataVariables.First;
  while not memDataVariables.Eof do
  begin
    if memDataVariablesfldType.Value = '' then
    begin
      memDataVariables.Edit;
      memDataVariablesfldType.Value := 'Static';
      memDataVariables.Post;
      SaveProject;
    end;

    Result := StringReplace(Result, memDataVariablesfldName.Value, memDataVariablesfldValue.Value, [rfReplaceAll]);

    memDataVariables.Next;
  end;
end;

procedure TfrmMain.Run;
var
  tick: Cardinal;
  strlist: TStringList;
  i: Integer;
begin
  FHTTP := TIdHTTP.Create(nil);
  FHTTP.IOHandler := SSLIOHandlerSocketOpenSSL;
  FHTTP.HTTPOptions := [hoKeepOrigProtocol];
  FHTTP.HandleRedirects := True;
  FHTTP.AllowCookies := True;
  FHTTP.Request.UserAgent := 'Simnet REST Debugger/' + GetAppVersion;
  try
    EditorContent.Lines.Clear;
    EditorResponseHeaders.Lines.Clear;

    Screen.Cursor := crHourGlass;
    tick := GetTickCount;
    try
      FHTTP.Request.Accept := '*/*';
      FHTTP.Request.CustomHeaders.Clear;

      strlist := TStringList.Create;
      try
        strlist.Text := EditorExtraHeaders.Lines.Text;
        for i := 0 to strlist.Count - 1 do
        begin
          strlist[i] := ReplaceGlobalVar(strlist[i]);
        end;

        FHTTP.Request.CustomHeaders.AddStrings(strlist);
      finally
        strlist.Free;
      end;


      if chkUseBasicAuth.Checked then
      begin
        FHTTP.Request.Username := edtAuthUsername.Text;
        FHTTP.Request.Password := edtAuthPassword.Text;
        FHTTP.Request.BasicAuthentication := True;
      end
      else
      begin
        FHTTP.Request.BasicAuthentication := False;
        FHTTP.Request.Username := '';
        FHTTP.Request.Password := '';
      end;

      if comboMethod.ItemIndex = 0 then
      begin
        // GET
        DoGETRequest(mtGET);
      end
      else
      if comboMethod.ItemIndex = 1 then
      begin
        // POST
        DoPOSTRequest(mtPOST);
      end
      else
      if comboMethod.ItemIndex = 2 then
      begin
        // PATCH
        DoPOSTRequest(mtPATCH);
      end
      else
      if comboMethod.ItemIndex = 3 then
      begin
        // PUT
        DoPOSTRequest(mtPUT);
      end
      else
      if comboMethod.ItemIndex = 4 then
      begin
        // DELETE
        DoGETRequest(mtDELETE);
      end;
    finally
      ActiveEditor := EditorContent;
      EditorContent.SetFocus;


      FLastResponseContent := FHTTP.Response.ContentType;
      ParseAndFormatJsonAndXML(chkResponseAutoformat.Checked);


      dxStatusBarResponse.Panels[0].Text := FormatFloat('Response Time: 0, ms', GetTickCount - tick);
      dxStatusBarResponse.Panels[1].Text := 'Response Code: ' + FHTTP.ResponseCode.ToString;
      dxStatusBarResponse.Panels[2].Text := 'Response Content: ' + FHTTP.Response.ContentType;
      Screen.Cursor := crDefault;
    end;
  finally
    FHTTP.Free;
  end;
end;

procedure TfrmMain.SavePreset;
var
  preset: string;
begin
  if memDataPresets.RecordCount > 0 then
  begin
    preset := 'Preset_' + memDataPresetsfldGuid.Value;
    FProjectConfig.WriteString(preset, 'BaseURL', edtBaseURL.Text);
    FProjectConfig.WriteInteger(preset, 'Method', comboMethod.ItemIndex);
    FProjectConfig.WriteString(preset, 'URLParams', EditorURLParams.Lines.Text);
    FProjectConfig.WriteString(preset, 'PostData', EditorPostData.Lines.Text);
    FProjectConfig.WriteString(preset, 'ExtraHeaders', EditorExtraHeaders.Lines.Text);
    FProjectConfig.WriteString(preset, 'PostDataContentType', comboPostContentType.Text);
    FProjectConfig.WriteBool(preset, 'ResponseAutoFormat', chkResponseAutoformat.Checked);

    FProjectConfig.WriteBool(preset, 'UseBasicAuth', chkUseBasicAuth.Checked);
    FProjectConfig.WriteString(preset, 'AuthUsername', TEncryptStr.EncryptString(edtAuthUsername.Text));
    FProjectConfig.WriteString(preset, 'AuthPassword', TEncryptStr.EncryptString(edtAuthPassword.Text));

    FProjectConfig.WriteString(preset, 'PresetName', memDataPresetsfldName.Value);
    FProjectConfig.WriteString(preset, 'Guid', memDataPresetsfldGuid.Value);
    FProjectConfig.WriteString(preset, 'Notes', Encode(HtmlEditor.Doc.OuterHTML));
    FProjectConfig.WriteString(preset, 'Category', memDataPresetsfldCategory.Value);

    FProjectConfig.WriteString('Presets', 'LastUsed', memDataPresetsfldGuid.Value);
  end;
end;

procedure TfrmMain.SaveProject;
var
  strlist: TStringList;
begin
  SavePreset;

  strlist := TStringList.Create;
  try
    memDataVariables.SaveToStrings(strlist);
    FProjectConfig.WriteString('Main', 'GlobalVar', strlist.Text);
  finally
    strlist.Free;
  end;


  FProjectConfig.Save;
end;

procedure TfrmMain.SaveProjectAs(const LoadAfterSave: Boolean);
var
  fname: string;
begin
  SaveDialog.InitialDir := MainConfig.ReadString('Dialogs', 'LastSaveDir', ExtractFilePath(ParamStr(0)));
  if SaveDialog.Execute then
  begin
    fname := SaveDialog.FileName;
    MainConfig.WriteString('Dialogs', 'LastSaveDir', ExtractFilePath(fname));

    fname := ChangeFileExt(fname, '');
    fname := fname + '.rdproj';

    SaveProject;
    CopyFile(PChar(FProjectConfig.FileName), PChar(fname), False);

    if LoadAfterSave then
    begin
      FIsTempProjectLoaded := False;
      FActiveProject := fname;
      LoadProject;
    end;
  end;
end;

procedure TfrmMain.ShowError(const AErrorText: string);
begin
  MessageDlg(AErrorText, mtError, [mbOK], 0);
end;

procedure TfrmMain.ShowSearchReplaceDialog(AReplace: Boolean);
begin
  with frmSearch do
  begin
    // assign search options
    SearchBackwards := gbSearchBackwards;
    SearchCaseSensitive := gbSearchCaseSensitive;
    SearchFromCursor := gbSearchFromCaret;
    SearchInSelectionOnly := gbSearchSelectionOnly;
    // start with last search text
    SearchText := gsSearchText;
    if gbSearchTextAtCaret then
    begin
      // if something is selected search for that text
      if ActiveEditor.SelAvail and (ActiveEditor.BlockBegin.Line = ActiveEditor.BlockEnd.Line)
      then
        SearchText := ActiveEditor.SelText
      else
        SearchText := ActiveEditor.GetWordAtRowCol(ActiveEditor.CaretXY);
    end;
    SearchTextHistory := gsSearchTextHistory;
    SearchWholeWords := gbSearchWholeWords;
    if ShowModal = mrOK then
    begin
      gbSearchBackwards := SearchBackwards;
      gbSearchCaseSensitive := SearchCaseSensitive;
      gbSearchFromCaret := SearchFromCursor;
      gbSearchSelectionOnly := SearchInSelectionOnly;
      gbSearchWholeWords := SearchWholeWords;
      gbSearchRegex := SearchRegularExpression;
      gsSearchText := SearchText;
      gsSearchTextHistory := SearchTextHistory;
      FSearchFromCaret := gbSearchFromCaret;
      if gsSearchText <> '' then
      begin
        DoSearchReplaceText(AReplace, gbSearchBackwards);
        FSearchFromCaret := TRUE;
      end;
    end;
  end;
end;

end.
