unit UnitCodeInspector;

interface

uses SysUtils, Classes, Windows, JvInspector, UnitMainTools, Contnrs;

type
  TSelectionTextList = class(TStringList)
  private
    FSelected: Integer;
    function GetSelectedText: string;
    function GetSelected: Integer;
    procedure SetSelectedText(const Value: string);
    procedure SetSelected(const Value: Integer);
  public
    property Selected: Integer read GetSelected write SetSelected;
    property SelectedText: string read GetSelectedText write SetSelectedText;
  end;

  TJvInspectorSelectionTextListItem = class(TJvCustomInspectorItem)
  protected
    function GetDisplayValue: string; override;
    procedure GetValueList(const Strings: TStrings); override;
    procedure SetDisplayValue(const Value: string); override;
    procedure SetFlags(const Value: TInspectorItemFlags); override;
  end;

  TStringWrapper = class(TObject)
  public
    Value: string;
    constructor Create(const AValue: string);
  end;

  TSTLWrapper = class(TObject)
  public
    STL: TSelectionTextList;
    Value: String;
    constructor Create(const ASTL: TSelectionTextList; const AValue: String);
  end;

function AddField(eName, eCategory, eValue: String): TJvCustomInspectorItem;
function AddCombo(eName, eCategory, eValue: String; eValues: array of string): TJvCustomInspectorItem;

procedure UpdateCI;
procedure UpdateCI_PAWN;

var eFormatSettings: String;
    eAllIncluded: TStringArray;
    
    FItems: TObjectList;
    STLItem: TSelectionTextList;

implementation

uses UnitfrmMain, UnitLanguages, UnitCodeUtils, UnitMenuGenerators,
  UnitPlugins;

var eBraceTexts: TStringList;

{ "Combobox"-Item }

function TSelectionTextList.GetSelected: Integer;
begin
  if FSelected < -1 then
    FSelected := -1
  else if FSelected >= Count then
    FSelected := Count - 1;
  Result := FSelected;
end;

function TSelectionTextList.GetSelectedText: string;
begin
  if Selected >= 0 then
    Result := Strings[Selected]
  else
    Result := '';
end;

procedure TSelectionTextList.SetSelected(const Value: Integer);
begin
  FSelected := Value;
  GetSelected; // adjust FSelected
end;

procedure TSelectionTextList.SetSelectedText(const Value: string);
begin
  FSelected := IndexOf(Value);
end;

function TJvInspectorSelectionTextListItem.GetDisplayValue: string;
begin
  Result := TSelectionTextList(Data.AsOrdinal).SelectedText;
end;

procedure TJvInspectorSelectionTextListItem.GetValueList(const Strings: TStrings);
begin
  Strings.Assign(TSelectionTextList(Data.AsOrdinal));
end;

procedure TJvInspectorSelectionTextListItem.SetDisplayValue(const Value: string);
begin
  TSelectionTextList(Data.AsOrdinal).SelectedText := Value;
end;

procedure TJvInspectorSelectionTextListItem.SetFlags(const Value: TInspectorItemFlags);
begin
  inherited SetFlags(Value + [iifValueList]);
end;

constructor TStringWrapper.Create(const AValue: string);
begin
  inherited Create;
  Value := AValue;
end;

{ Codeinspector Add Functions }

function AddCombo(eName, eCategory, eValue: String; eValues: array of string): TJvCustomInspectorItem;
var
  i: integer;
  eParent: TJvCustomInspectorItem;
  Item: TSTLWrapper;
  Found: Boolean;
begin
  eParent := nil;
  for i := 0 to frmMain.jviCode.Root.Count -1 do
  begin
    if (frmMain.jviCode.Root.Items[i].DisplayName = eCategory) and (frmMain.jviCode.Root.Items[i] is TJvInspectorCustomCategoryItem) then
    begin
      eParent := frmMain.jviCode.Root.Items[i];
      Break;
    end;
  end;
  if eParent = nil then
  begin
    eParent := TJvInspectorCustomCategoryItem.Create(frmMain.jviCode.Root, nil);
    eParent.DisplayName := eCategory;
  end;

  if eName <> '' then
  begin
    STLItem := TSelectionTextList.Create;
    Found := False;
    for i := 0 to High(eValues) do begin
      STLItem.Add(eValues[i]);
      if eValues[i] = eValue then
        Found := True;
    end;

    if not Found then begin
      STLItem.Add(eValue);
      STLItem.Sort;
    end;

    Item := TSTLWrapper.Create(STLItem, eValue);
    FItems.Add(Item);
    FItems.Add(STLItem);
    
    STLItem.SelectedText := Item.Value;
    Result := TJvInspectorVarData.New(eParent, eName, TypeInfo(TSelectionTextList), @Item.STL);
    frmMain.jviCode.Root.Sort;
    eParent.Expanded := True;
  end
  else
  begin
    Result := nil;
    frmMain.jviCode.Root.Sort;
  end;
end;

function AddField(eName, eCategory, eValue: String): TJvCustomInspectorItem;
var
  i: integer;
  eParent: TJvCustomInspectorItem;
  Item: TStringWrapper;
begin
  eParent := nil;
  for i := 0 to frmMain.jviCode.Root.Count -1 do
  begin
    if (frmMain.jviCode.Root.Items[i].DisplayName = eCategory) and (frmMain.jviCode.Root.Items[i] is TJvInspectorCustomCategoryItem) then
    begin
      eParent := frmMain.jviCode.Root.Items[i];
      Break;
    end;
  end;
  if eParent = nil then
  begin
    eParent := TJvInspectorCustomCategoryItem.Create(frmMain.jviCode.Root, nil);
    eParent.DisplayName := eCategory;
  end;

  if eName <> '' then
  begin
    Item := TStringWrapper.Create(eValue); // StringWrapper erzeugen, damit der String erhalten bleibt
    FItems.Add(Item); // und das Item in die Liste eintragen, damit kein Speicherleck entsteht
    Result := TJvInspectorVarData.New(eParent, eName, TypeInfo(String), @Item.Value);
    frmMain.jviCode.Root.Sort;
    eParent.Expanded := True;
  end
  else
  begin
    Result := nil;
    frmMain.jviCode.Root.Sort;
  end;
end;

{ Parse Functions }

procedure UpdateCI;
begin
  if not Plugin_UpdateCodeInspector(GetCurrLang.Name, ActiveDoc.FileName, frmMain.tsMain.Items[frmMain.tsMain.ActiveTabIndex].Caption, True) then exit;

  if GetCurrLang.Name = 'Pawn' then begin
    UpdateCI_PAWN;
    Plugin_UpdateCodeInspector(GetCurrLang.Name, ActiveDoc.FileName, frmMain.tsMain.Items[frmMain.tsMain.ActiveTabIndex].Caption, False);
  end;
end;

procedure UpdateCI_PAWN;
procedure HideBracesAndStrings(var eStr: String);
begin
  while Between(eStr, '{', '}') <> '' do begin
    eBraceTexts.Add('{' + Between(eStr, '{', '}') + '}');
    eStr := StringReplace(eStr, '{' + Between(eStr, '{', '}') + '}', #1 + IntToStr(eBraceTexts.Count) + #1, []);
  end;
  while CountChars(eStr, '"') > 1 do begin
    eBraceTexts.Add('"' + StringReplace(Between(eStr, '"', '"'), ':', #3, [rfReplaceAll]) + '"');
    eStr := StringReplace(eStr, '"' + Between(eStr, '"', '"') + '"', #2 + IntToStr(eBraceTexts.Count) + #2, []);
  end;
end;

function ShowBracesAndStrings(eStr: String): String;
var k: integer;
begin
  while Between(eStr, #1, #1) <> '' do begin
     k := StrToInt(Between(eStr, #1, #1));
     eStr := StringReplace(eStr, #1 + IntToStr(k) + #1, eBraceTexts[k -1], []);
  end;
  while Between(eStr, #2, #2) <> '' do begin
    k := StrToInt(Between(eStr, #2, #2));
    eStr := StringReplace(eStr, #2 + IntToStr(k) + #2, eBraceTexts[k -1], []);
  end;
  Result := eStr;
end;

var eCurrLine, eBackupLine: String;
    i, k: integer;
    eStr: TStringList;
    eVars, eConsts: Integer;
    eVarName, eVarType, eVarValue: String;
begin
  eBackupLine := frmMain.sciEditor.Lines[frmMain.sciEditor.GetCurrentLineNumber];
  eCurrLine := Trim(StringReplace(eBackupLine, #9, #32, [rfReplaceAll]));
  eCurrLine := RemoveStringsAndComments(eCurrLine, False);
  eAllIncluded := GetAllIncludeFiles;
  eStr := TStringList.Create;
  eBraceTexts := TStringList.Create;
  eVars := 0;
  eConsts := 0;
  frmMain.jviCode.Clear;
  FItems.Clear;

  eFormatSettings := '';
  { Constants and Variables }
  if (IsAtStart('new', eCurrLine, False)) then begin // const or variable
    Delete(eCurrLine, 1, 4);

    // done? okay, split all items if there are more than one; and if not, it's okay...
    HideBracesAndStrings(eCurrLine);
    eStr.Text := StringReplace(eCurrLine, ',', #13, [rfReplaceAll]);

    for i := 0 to eStr.Count - 1 do begin
      eStr[i] := ShowBracesAndStrings(eStr[i]);
      eVarType := '';
      eVarValue := '';
      
      if (Trim(eStr[i]) <> '') then begin
        eVarName := Trim(RemoveSemicolon(eStr[i]));
        if Pos(':', eVarName) <> 0 then begin
          eVarType := Trim(Copy(eVarName, 1,  Pos(':', eVarName) -1));
          eVarName := Trim(Copy(eVarName, Pos(':', eVarName) +1, Length(eVarName)));
        end;

        if Pos('=', eVarName) <> 0 then begin // constant
          Inc(eConsts, 1);
          eFormatSettings := eFormatSettings + '-Constant ' + IntToStr(eConsts) + '-';

          eVarValue := Trim(Copy(eVarName, Pos('=', eVarName) +1, Length(eVarName)));
          eVarValue := StringReplace(eVarValue, #3, ':', [rfReplaceAll]);
          eVarName := Trim(Copy(eVarName, 1, Pos('=', eVarName) -1));

          AddField(lName, 'Constant ' + IntToStr(eConsts), eVarName);
          if eVarType <> '' then
            AddField(lType, 'Constant ' + IntToStr(eConsts), eVarType);
          if eVarValue <> '' then
            AddField(lValue, 'Constant ' + IntToStr(eConsts), eVarValue);
        end
        else begin // variable
          Inc(eVars, 1);
          eFormatSettings := eFormatSettings + '-Variable ' + IntToStr(eVars) + '-';
          AddField(lName, 'Variable ' + IntToStr(eVars), eVarName);
          if eVarType <> '' then
            AddField(lType, 'Variable ' + IntToStr(eVars), eVarType);            
       end;
      end;
    end;

    if frmMain.jviCode.Root.Count = 1 then
      frmMain.jviCode.Root.Items[0].DisplayName := Copy(frmMain.jviCode.Root.Items[0].DisplayName, 1, Length(frmMain.jviCode.Root.Items[0].DisplayName) -2);
  end
  { Conditions }
  else if (IsAtStart('if', eCurrLine, False)) then begin
    if (CountChars(eCurrLine, '(') = CountChars(eCurrLine, ')')) and (CountChars(eCurrLine, '(') <> 0) then begin
      eCurrLine := Copy(eCurrLine, 1, GetMatchingBrace(eCurrLine) -1);
      eCurrLine := Copy(eCurrLine, Pos('(', eCurrLine) +1, Length(eCurrLine));
      eFormatSettings := StringReplace(eBackupLine, '(' + eCurrLine + ')', #1#3#3#7, []);
      HideBracesAndStrings(eCurrLine);
      eStr.Text := StringReplace(eCurrLine, '||', #13, [rfReplaceAll]);
      k := eStr.Count -1; // from 0 to k -> OR operators
      eStr.Text := StringReplace(eStr.Text, '&&', #13, [rfReplaceAll]);

      for i := 0 to eStr.Count -1 do begin
        eStr[i] := Trim(ShowBracesAndStrings(eStr[i]));
        if (Pos('(', eStr[i]) = 1) and (Pos(')', eStr[i]) = Length(eStr[i])) then
          eStr[i] := Copy(Copy(eStr[i], 2, Length(eStr[i])), 1, Length(eStr[i]) -1);

        if eStr.Count = 1 then
          eCurrLine := 'If-Condition'
        else
          eCurrLine := 'If-Condition ' + IntToStr(frmMain.jviCode.Root.Count +1);

        if i <> eStr.Count -1 then begin
          if i < k then
            AddCombo('Operator', eCurrLine, '||', ['||', '&&'])
          else
            AddCombo('Operator', eCurrLine, '&&', ['||', '&&']);
        end;
        AddField('Condition', eCurrLine, eStr[i]);
      end;
    end
    else
      AddField('', 'Invalid condition', '');
  end
  { Defined }
  else if (IsAtStart('#define', eCurrLine, False)) then begin
    eCurrLine := Trim(Copy(eCurrLine, 8, Length(eCurrLine)));
    HideBracesAndStrings(eCurrLine);
    eCurrLine := StringReplace(eCurrLine, #9, #32, [rfReplaceAll]);
    eCurrLine := ShowBracesAndStrings(eCurrLine);
    AddField('Name', 'Defined', Copy(eCurrLine, 1, Pos(#32, eCurrLine) -1));
    eCurrLine := Trim(Copy(eCurrLine, Pos(#32, eCurrLine) +1, Length(eCurrLine)));
    AddField('Value', 'Defined', eCurrLine);
  end
  { Included }
  else if (IsAtStart('#include', eCurrLine, False)) then begin
    eCurrLine := Trim(StringReplace(eCurrLine, #9, #32, [rfReplaceAll]));
    if Between(eCurrLine, '<', '>') <> '' then begin
      eCurrLine := Between(eCurrLine, '<', '>');
      eFormatSettings := StringReplace(eBackupLine, '<' + eCurrLine + '>', '<-Filename->', []);
    end
    else if Between(eCurrLine, '"', '"') <> '' then begin
      eCurrLine := Between(eCurrLine, '"', '"');
      eFormatSettings := StringReplace(eBackupLine, '"' + eCurrLine + '"', '"-Filename-"', []);
    end
    else begin
      eCurrLine := Copy(eCurrLine, 9, Length(eCurrLine));
      eFormatSettings := '#include -Filename-';
    end;
    eCurrLine := Trim(eCurrLine);
    AddCombo('File', 'Included', eCurrLine, eAllIncluded);
  end
  { Assignments }
  else begin
    if (Pos('=', eCurrLine) <> 0) then begin
      eCurrLine := Trim(eCurrLine);
      while Pos(eCurrLine[1], frmMain.sciEditor.WordChars + '[]') <> 0 do
        Delete(eCurrLine, 1, 1);
      eCurrLine := Trim(eCurrLine);
      if Pos('=', eCurrLine) <= 2 then begin
        while (Pos(Copy(eCurrLine, 1, 1), frmMain.sciEditor.WordChars + #32 + #9) = 0) and (Length(eCurrLine) <> 0) do begin
           eFormatSettings := eFormatSettings + eCurrLine[1];
           Delete(eCurrLine, 1, 1);
        end;
        eCurrLine := RemoveSemicolon(Trim(eBackupLine));
        eCurrLine := StringReplace(eCurrLine, #9, #32, [rfReplaceAll]);
        AddField('a', 'Assignment', Copy(eCurrLine, 1, Pos(eFormatSettings, eCurrLine) - Length(eFormatSettings)));
        AddField('b', 'Assignment', Trim(Copy(eCurrLine, Pos(eFormatSettings, eCurrLine) + Length(eFormatSettings), Length(eCurrLine))));
        AddField('Operator', 'Assignment', eFormatSettings);
      end
      else
        AddField('', 'No information available.', '');
    end
    else
      AddField('', 'No information available.', '');
  end;
  eStr.Free;
  eBraceTexts.Free;
end;

{ TSTLWrapper }

constructor TSTLWrapper.Create(const ASTL: TSelectionTextList;
  const AValue: String);
begin
  STL := ASTL;
  Value := AValue;
end;

initialization

FItems := TObjectList.Create;

finalization

FItems.Free;

end.