{
 ***************************************************************************
 *                                                                         *
 *   This source is free software; you can redistribute it and/or modify   *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This code is distributed in the hope that it will be useful, but      *
 *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
 *   General Public License for more details.                              *
 *                                                                         *
 *   A copy of the GNU General Public License is available on the World    *
 *   Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also      *
 *   obtain it by writing to the Free Software Foundation,                 *
 *   Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA.   *
 *                                                                         *
 ***************************************************************************

  Author: Mattias Gaertner

  Abstract:
    Dialog to explore IDE internal codetools structures.
}
unit CodeToolsDefPreview;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Math, Laz_AVL_Tree,
  // LCL
  Forms, Dialogs, ButtonPanel, StdCtrls, ComCtrls, ExtCtrls,
  // LazUtils
  LazFileUtils, LazUtilities, LazLoggerBase,
  // Codetools
  DefineTemplates, ExprEval,
  // SynEdit
  SynEdit,
  // IdeIntf
  IDEWindowIntf, IDEHelpIntf, InputHistory,
  // IdeConfig
  RecentListProcs, EnvironmentOpts,
  // IDE
  LazarusIDEStrConsts, EditorOptions, CodeToolsOptions;

type
  TCodeToolsDefinesNodeValues = class
  public
    Node: TDefineTemplate;
    ValueParsed: boolean;
    ParsedValue: string;
    ExpressionCalculated: boolean;
    ExpressionResult: string;
    Execute: boolean;
  end;

  { TCodeToolsDefinesDialog }

  TCodeToolsDefinesDialog = class(TForm)
    ButtonPanel: TButtonPanel;
    DirectoryBrowseButton: TButton;
    DirectoryCombobox: TComboBox;
    DirectoryGroupbox: TGroupBox;
    PageControl1: TPageControl;
    ReportMemo: TMemo;
    Splitter1: TSplitter;
    ValuesTabSheet: TTabSheet;
    ReportTabSheet: TTabSheet;
    TemplatesMemo: TMemo;
    TemplatesSplitter: TSplitter;
    TemplatesGroupBox: TGroupBox;
    MainSplitter: TSplitter;
    ParsedTemplatesTreeView: TTreeView;
    ValueSynedit: TSynEdit;
    ValueGroupbox: TGroupBox;
    ValuesListview: TListView;
    procedure CodeToolsDefinesDialogCLOSE(Sender: TObject;
      var {%H-}CloseAction: TCloseAction);
    procedure CodeToolsDefinesDialogCREATE(Sender: TObject);
    procedure DirectoryBrowseButtonCLICK(Sender: TObject);
    procedure DirectoryComboboxCHANGE(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure HelpButtonClick(Sender: TObject);
    procedure ParsedTemplatesTreeViewSelectionChanged(Sender: TObject);
    procedure ValuesListviewSELECTITEM(Sender: TObject; {%H-}Item: TListItem;
      {%H-}Selected: Boolean);
  private
    FDefineTree: TDefineTree;
    FNodeValues: TAvlTree;
    fReport: TStringList;
    procedure SetDefineTree(const AValue: TDefineTree);
    procedure UpdateValues;
    procedure UpdateValue;
    procedure UpdateTemplateValues;
    procedure ClearValues;
    procedure FillTemplateTree;
    procedure SetComboBox(AComboBox: TComboBox; const NewText: string);
    procedure DefineTreeCalculate({%H-}DefineTree: TDefineTree; Node: TDefineTemplate;
                  ValueParsed: boolean; const ParsedValue: string;
                  ExpressionCalculated: boolean; const ExpressionResult: string;
                  Execute: boolean);
  public
    property DefineTree: TDefineTree read FDefineTree write SetDefineTree;
  end;


function ShowCodeToolsDefinesValuesDialog(ADefineTree: TDefineTree;
  const InitialDirectory: string): TModalresult;
  
procedure RebuildDefineTreeView(ATreeView: TTreeView;
  RootTemplate: TDefineTemplate);
procedure AddDefineNodes(ATreeView: TTreeView; ANode: TDefineTemplate;
  AParent: TTreeNode; WithChilds,WithNextSiblings: boolean);
procedure SetNodeImages(ANode: TTreeNode; WithSubNodes: boolean);

function CompareNodeValues(Data1, Data2: Pointer): Integer;
function CompareNodeAndNodeValues(Node, NodeValues: Pointer): Integer;

implementation

{$R *.lfm}

function ShowCodeToolsDefinesValuesDialog(ADefineTree: TDefineTree;
  const InitialDirectory: string): TModalresult;
var
  CodeToolsDefinesDialog: TCodeToolsDefinesDialog;
begin
  CodeToolsDefinesDialog:=TCodeToolsDefinesDialog.Create(nil);
  if InitialDirectory<>'' then
    CodeToolsDefinesDialog.SetComboBox(CodeToolsDefinesDialog.DirectoryCombobox,
                                       InitialDirectory);
  CodeToolsDefinesDialog.DefineTree:=ADefineTree;
  Result:=CodeToolsDefinesDialog.ShowModal;
  CodeToolsDefinesDialog.Free;
end;

procedure RebuildDefineTreeView(ATreeView: TTreeView;
  RootTemplate: TDefineTemplate);
begin
  ATreeView.Items.BeginUpdate;
  ATreeView.Items.Clear;
  AddDefineNodes(ATreeView,RootTemplate,nil,true,true);
  ATreeView.Items.EndUpdate;
end;

procedure AddDefineNodes(
  ATreeView: TTreeView; ANode: TDefineTemplate; AParent: TTreeNode;
  WithChilds, WithNextSiblings: boolean);
var NewTreeNode: TTreeNode;
begin
  if ANode=nil then exit;
  ATreeView.Items.BeginUpdate;
  NewTreeNode:=ATreeView.Items.AddChildObject(AParent,ANode.Name,ANode);
  SetNodeImages(NewTreeNode,false);
  if WithChilds and (ANode.FirstChild<>nil) then begin
    AddDefineNodes(ATreeView,ANode.FirstChild,NewTreeNode,true,true);
  end;
  if WithNextSiblings and (ANode.Next<>nil) then begin
    AddDefineNodes(ATreeView,ANode.Next,AParent,WithChilds,true);
  end;
  ATreeView.Items.EndUpdate;
end;

procedure SetNodeImages(ANode: TTreeNode;
  WithSubNodes: boolean);
var
  ADefineTemplate: TDefineTemplate;
begin
  ADefineTemplate := TDefineTemplate(ANode.Data);

  ANode.ImageIndex := DefineActionImages[ADefineTemplate.Action];
  ANode.SelectedIndex := ANode.ImageIndex;

  if ADefineTemplate.IsAutoGenerated then
    ANode.StateIndex := AutogeneratedImage
  else
    ANode.StateIndex := -1;

  if WithSubNodes then
  begin
    ANode:=ANode.GetFirstChild;
    while ANode<>nil do
    begin
      SetNodeImages(ANode,true);
      ANode:=ANode.GetNextSibling;
    end;
  end;
end;

function CompareNodeValues(Data1, Data2: Pointer): Integer;
begin
  Result:=ComparePointers(TCodeToolsDefinesNodeValues(Data1).Node,
                          TCodeToolsDefinesNodeValues(Data2).Node);
end;

function CompareNodeAndNodeValues(Node, NodeValues: Pointer): Integer;
begin
  Result:=ComparePointers(TCodeToolsDefinesNodeValues(Node),
                          TCodeToolsDefinesNodeValues(NodeValues).Node);
end;

{ TCodeToolsDefinesDialog }

procedure TCodeToolsDefinesDialog.FormDestroy(Sender: TObject);
begin
  if FNodeValues<>nil then begin
    FNodeValues.FreeAndClear;
    FreeAndNil(FNodeValues);
  end;
end;

procedure TCodeToolsDefinesDialog.HelpButtonClick(Sender: TObject);
begin
  LazarusHelp.ShowHelpForIDEControl(Self);
end;

procedure TCodeToolsDefinesDialog.ParsedTemplatesTreeViewSelectionChanged(
  Sender: TObject);
begin
  UpdateTemplateValues;
end;

procedure TCodeToolsDefinesDialog.ValuesListviewSELECTITEM(Sender: TObject;
  Item: TListItem; Selected: Boolean);
begin
  UpdateValue;
end;

procedure TCodeToolsDefinesDialog.SetDefineTree(const AValue: TDefineTree);
begin
  if FDefineTree=AValue then exit;
  FDefineTree:=AValue;
  UpdateValues;
  FillTemplateTree;
end;

procedure TCodeToolsDefinesDialog.UpdateValues;
// let the codetools calculate the defines for the directory

  procedure AddNodeReport(Prefix: string; DefTempl: TDefineTemplate);
  var
    AVLNode: TAvlTreeNode;
    NodeValues: TCodeToolsDefinesNodeValues;
    s: String;
  begin
    while DefTempl<>nil do begin
      s:=Prefix+'Name="'+DefTempl.Name+'"';
      s:=s+' Description="'+DefTempl.Description+'"';
      s:=s+' Action="'+DefineActionNames[DefTempl.Action]+'"';
      s:=s+' Variable="'+DefTempl.Variable+'"';
      s:=s+' Value="'+dbgstr(DefTempl.Value)+'"';
      if FNodeValues<>nil then begin
        AVLNode:=FNodeValues.FindKey(DefTempl,@CompareNodeAndNodeValues);
        if AVLNode<>nil then begin
          NodeValues:=TCodeToolsDefinesNodeValues(AVLNode.Data);
          if NodeValues.ValueParsed then
            s:=s+' ParsedValue="'+dbgstr(NodeValues.ParsedValue)+'"';
          if NodeValues.ExpressionCalculated then
            s:=s+' ExpressionResult="'+dbgstr(NodeValues.ExpressionResult)+'"';
          s:=s+' Executed="'+dbgs(NodeValues.Execute)+'"';
        end;
      end;
      fReport.Add(s);
      if DefTempl.FirstChild<>nil then
        AddNodeReport(Prefix+'    ',DefTempl.FirstChild);
      DefTempl:=DefTempl.Next;
    end;
  end;

var
  Dir: String;
  Defines: TExpressionEvaluator;
  i: Integer;
  ListItem: TListItem;
  Value: String;
  OldOnCalculate: TDefTreeCalculate;
begin
  Dir:=TrimFilename(DirectoryCombobox.Text);
  if (DefineTree=nil) or (not FilenameIsAbsolute(Dir))
  or (not DirPathExists(Dir)) then begin
    ClearValues;
    exit;
  end;

  // set our debug function, clear codetools cache and calculate the values
  if FNodeValues<>nil then
    FNodeValues.FreeAndClear;
  DefineTree.ClearCache;// make sure the defines are reparsed

  fReport:=TStringList.Create;
  try
    fReport.Add('Directory: '+Dir);

    OldOnCalculate:=DefineTree.OnCalculate;
    DefineTree.OnCalculate:=@DefineTreeCalculate;
    try
      Defines:=DefineTree.GetDefinesForDirectory(Dir,false);
    finally
      DefineTree.OnCalculate:=OldOnCalculate;
    end;

    // fill the ValuesListview
    ValuesListview.BeginUpdate;
    if Defines<>nil then begin
      fReport.Add('Defines:');
      for i:=0 to Defines.Count-1 do begin
        if ValuesListview.Items.Count<=i then
          ListItem:=ValuesListview.Items.Add
        else
          ListItem:=ValuesListview.Items[i];
        fReport.Add(Defines.Names(i)+'='+dbgstr(Defines.Values(i)));
        ListItem.Caption:=Defines.Names(i);
        Value:=Defines.Values(i);
        if length(Value)>100 then
          Value:=copy(Value,1,100)+' ...';
        if ListItem.SubItems.Count<1 then
          ListItem.SubItems.Add(Value)
        else
          ListItem.SubItems[0]:=Value;
      end;
      fReport.Add('');
      while ValuesListview.Items.Count>Defines.Count do
        ValuesListview.Items.Delete(ValuesListview.Items.Count-1);
    end else begin
      ValuesListview.Items.Clear;
    end;
    ValuesListview.EndUpdate;
    UpdateValue;

    // add all nodes to report
    fReport.Add('Tree:');
    AddNodeReport('  ',DefineTree.RootTemplate);

    ReportMemo.Lines.Assign(fReport);
  finally
    FreeAndNil(fReport);
  end;
end;

procedure TCodeToolsDefinesDialog.UpdateValue;
var
  VariableName: String;
  Dir: String;
  Defines: TExpressionEvaluator;
  Value: string;
begin
  Dir:=TrimFilename(DirectoryCombobox.Text);
  if (ValuesListview.Selected=nil) or (DefineTree=nil)
  or (not FilenameIsAbsolute(Dir)) then begin
    ValueGroupbox.Caption:=lisCTDefnoVariableSelected;
    ValueSynedit.Lines.Text:='';
  end else begin
    VariableName:=ValuesListview.Selected.Caption;
    ValueGroupbox.Caption:=Format(lisCTDefVariable, [VariableName]);
    Defines:=DefineTree.GetDefinesForDirectory(Dir,false);
    Value:=Defines.Variables[VariableName];
    ValueSynedit.Lines.Text:=Value;
  end;
end;

procedure TCodeToolsDefinesDialog.UpdateTemplateValues;
var
  SelTreeNode: TTreeNode;
  SelDefNode: TDefineTemplate;
  s: string;
  AVLNode: TAvlTreeNode;
  NodeValues: TCodeToolsDefinesNodeValues;
begin
  SelTreeNode:=ParsedTemplatesTreeView.Selected;
  if SelTreeNode=nil then begin
    TemplatesMemo.Text:='No node selected';
  end else begin
    SelDefNode:=TDefineTemplate(SelTreeNode.Data);
    s:='Name="'+SelDefNode.Name+'"'+LineEnding;
    s:=s+'Description="'+SelDefNode.Description+'"'+LineEnding;
    s:=s+'Action="'+DefineActionNames[SelDefNode.Action]+'"'+LineEnding;
    s:=s+'Variable="'+SelDefNode.Variable+'"'+LineEnding;
    s:=s+'Value="'+SelDefNode.Value+'"'+LineEnding;

    if FNodeValues<>nil then begin
      AVLNode:=FNodeValues.FindKey(SelDefNode,@CompareNodeAndNodeValues);
      if AVLNode<>nil then begin
        NodeValues:=TCodeToolsDefinesNodeValues(AVLNode.Data);
        if NodeValues.ValueParsed then
          s:=s+'Parsed Value="'+NodeValues.ParsedValue+'"'+LineEnding;
        if NodeValues.ExpressionCalculated then
          s:=s+'Expression Result="'+NodeValues.ExpressionResult+'"'+LineEnding;
        s:=s+'Executed="'+dbgs(NodeValues.Execute)+'"'+LineEnding;
      end;
    end;
    TemplatesMemo.Text:=s;
  end;
end;

procedure TCodeToolsDefinesDialog.ClearValues;
begin
  ValuesListview.Items.Clear;
  if FNodeValues<>nil then
    FNodeValues.FreeAndClear;
end;

procedure TCodeToolsDefinesDialog.FillTemplateTree;
begin
  RebuildDefineTreeView(ParsedTemplatesTreeView,DefineTree.RootTemplate);
  UpdateTemplateValues;
end;

procedure TCodeToolsDefinesDialog.SetComboBox(AComboBox: TComboBox;
  const NewText: string);
var
  i: Integer;
begin
  i:=AComboBox.Items.IndexOf(NewText);
  if i<0 then
    AComboBox.Items.Add(NewText)
  else
    AComboBox.ItemIndex:=i;
  AComboBox.Text:=NewText;
  //debugln('TCodeToolsDefinesDialog.SetComboBox Text=',AComboBox.Text,' NewText=',NewText);
end;

procedure TCodeToolsDefinesDialog.DefineTreeCalculate(DefineTree: TDefineTree;
  Node: TDefineTemplate; ValueParsed: boolean; const ParsedValue: string;
  ExpressionCalculated: boolean; const ExpressionResult: string;
  Execute: boolean);
var
  NewNodeValues: TCodeToolsDefinesNodeValues;
begin
  NewNodeValues:=TCodeToolsDefinesNodeValues.Create;
  NewNodeValues.Node:=Node;
  NewNodeValues.ValueParsed:=ValueParsed;
  NewNodeValues.ParsedValue:=ParsedValue;
  NewNodeValues.ExpressionCalculated:=ExpressionCalculated;
  NewNodeValues.ExpressionResult:=ExpressionResult;
  NewNodeValues.Execute:=Execute;
  if FNodeValues=nil then
    FNodeValues:=TAvlTree.Create(@CompareNodeValues);
  FNodeValues.Add(NewNodeValues);
end;

procedure TCodeToolsDefinesDialog.CodeToolsDefinesDialogCREATE(Sender: TObject);
var
  ListColumn: TListColumn;
begin
  IDEDialogLayoutList.ApplyLayout(Self,485,450);

  Caption:=lisCTDefCodeToolsDirectoryValues;

  ValuesTabSheet.Caption:=lisValues;

  ListColumn:=ValuesListview.Columns.Add;
  ListColumn.Caption:=lisCTDefVariableName;
  ListColumn.Width:=150;
  ListColumn:=ValuesListview.Columns.Add;
  ListColumn.Caption:=lisValue;
  
  DirectoryGroupbox.Caption:=lisCodeToolsDefsInsertBehindDirectory;
  ButtonPanel.HelpButton.OnClick := @HelpButtonClick;
  DirectoryCombobox.Items.Assign(
    InputHistories.HistoryLists.GetList(hlCodeToolsDirectories,true,rltFile));
  if DirectoryCombobox.Items.Count>0 then
    DirectoryCombobox.ItemIndex:=0
  else
    DirectoryCombobox.Text:='';
    
  TemplatesGroupBox.Caption:=lisCTDefDefineTemplates;
    
  MainSplitter.SetSplitterPosition(
         Max(20,Min(ClientWidth-100,CodeToolsOpts.DefinesPreviewMainSplitterPos)));
  TemplatesSplitter.SetSplitterPosition(
         Max(20,Min(TemplatesGroupBox.ClientHeight-50,
                    CodeToolsOpts.DefinesPreviewTemplSplitterPos)));

  EditorOpts.GetSynEditSettings(ValueSynedit);
  ValueSynedit.Gutter.Visible:=false;

  ReportTabSheet.Caption:=dlgReport;
  PageControl1.PageIndex:=0;
  DirectoryCombobox.DropDownCount:=EnvironmentOptions.DropDownCount;
end;

procedure TCodeToolsDefinesDialog.CodeToolsDefinesDialogCLOSE(Sender: TObject;
  var CloseAction: TCloseAction);
begin
  IDEDialogLayoutList.SaveLayout(Self);
  InputHistories.HistoryLists.GetList(hlCodeToolsDirectories,true,rltFile).Assign(
    DirectoryCombobox.Items);
  CodeToolsOpts.DefinesPreviewMainSplitterPos:=MainSplitter.GetSplitterPosition;
  CodeToolsOpts.DefinesPreviewTemplSplitterPos:=TemplatesSplitter.GetSplitterPosition;
  CodeToolsOpts.Save;
end;

procedure TCodeToolsDefinesDialog.DirectoryBrowseButtonCLICK(Sender: TObject);
var
  OpenDialog: TOpenDialog;
  Filename: string;
begin
  OpenDialog:=TSelectDirectoryDialog.Create(nil);
  try
    InputHistories.ApplyFileDialogSettings(OpenDialog);
    OpenDialog.Title:=lisCTDefChooseDirectory;
    OpenDialog.Options:=OpenDialog.Options+[ofPathMustExist];
    if OpenDialog.Execute then begin
      Filename:=CleanAndExpandFilename(OpenDialog.Filename);
      SetComboBox(DirectoryCombobox,Filename);
      UpdateValues;
    end;
    InputHistories.StoreFileDialogSettings(OpenDialog);
  finally
    OpenDialog.Free;
  end;
end;

procedure TCodeToolsDefinesDialog.DirectoryComboboxCHANGE(Sender: TObject);
begin
  UpdateValues;
end;

end.

