unit ASSource;

interface

uses
  Windows, Messages, SysUtils, Types, UITypes, Classes, Controls, Forms,
  Graphics, ASExpression;

type
  TAlgosimSource = record
  strict private
    class var FFullSource: string;
    class var FSourceLines: TArray<string>;
    class procedure DoLoad; static;
  public
    class function HasSourceFor(AFcnClass: TASFunctionClass): Boolean; static;
    class function TryGetSourceFor(AFcnClass: TASFunctionClass;
      out AFcnName: string; out AFcnSource: string): Boolean; static;
    class function TryGetSourceWithContextFor(AFcnClass: TASFunctionClass;
      out AFcnName: string; out ASource: string; out ALineIndex: Integer;
      out ABookmark1, ABookmark2: Integer): Boolean; static;
  end;

implementation

uses
  Math, StrUtils, ASFcnMgr;

{ TAlgosimSource }

class procedure TAlgosimSource.DoLoad;
begin
  if FFullSource.IsEmpty then
  begin
    try
      var RS := TResourceStream.Create(HInstance, 'ASFCN', RT_RCDATA);
      try
        var SR := TStreamReader.Create(RS, TEncoding.UTF8);
        try
          FFullSource := SR.ReadToEnd;
          FSourceLines := FFullSource.Split([#13#10]);
          for var i := 0 to High(FSourceLines) do
            FSourceLines[i] := FSourceLines[i].TrimRight;
        finally
          SR.Free;
        end;
      finally
        RS.Free;
      end;
    except
      on EResNotFound do
      begin
        FFullSource := 'Source not available.';
        FSourceLines := [FFullSource];
      end;
    end;
  end;
end;

class function TAlgosimSource.HasSourceFor(
  AFcnClass: TASFunctionClass): Boolean;
begin
  if AFcnClass = nil then
    Exit(False);
  DoLoad;
  const LClassName = AFcnClass.ClassName;
  const S = Format('{ %s }', [LClassName]);
  for var LLine in FSourceLines do
    if LLine = S then
      Exit(True);
  Result := False;
end;

class function TAlgosimSource.TryGetSourceFor(AFcnClass: TASFunctionClass;
  out AFcnName, AFcnSource: string): Boolean;
begin
  AFcnName := '';
  AFcnSource := '';
  if AFcnClass = nil then
    Exit(False);
  DoLoad;
  const LClassName = AFcnClass.ClassName;
  const S = Format('{ %s }', [LClassName]);
  var LStart := -1;
  var LEnd := -1;
  for var i := 0 to High(FSourceLines) do
    if FSourceLines[i] = S then
      LStart := i
    else if (LStart <> -1) and FSourceLines[i].StartsWith('{ FCN_') then
    begin
      LEnd := i;
      Break;
    end;
  Result := (LStart <> -1) and (LEnd > LStart);
  if Result then
  begin
    var L := TArray<string>(nil);
    if TFunctionMgr.Names.TryGetValue(AFcnClass, L) and (L <> nil) then
      AFcnName := L[0]
    else
      AFcnName := AFcnClass.ClassName;
    AFcnSource := string.Join(#13#10, Copy(FSourceLines, LStart, LEnd - LStart)).Trim;
  end;
end;

class function TAlgosimSource.TryGetSourceWithContextFor(
  AFcnClass: TASFunctionClass; out AFcnName, ASource: string; out ALineIndex,
  ABookmark1, ABookmark2: Integer): Boolean;
begin
  AFcnName := '';
  ASource := '';
  ALineIndex := -1;
  ABookmark1 := -1;
  ABookmark2 := -1;
  if AFcnClass = nil then
    Exit(False);
  DoLoad;
  const LClassName = AFcnClass.ClassName;
  const S = Format('{ %s }', [LClassName]);
  const T = Format('%s = class', [LClassName]);
  for var i := 0 to High(FSourceLines) do
    if FSourceLines[i].Contains(T) then
      ABookmark1 := i
    else if FSourceLines[i].Contains(S) then
      ALineIndex := i
    else if (ALineIndex <> -1) and FSourceLines[i].StartsWith('{ FCN_') then
      Break
    else if (ALineIndex <> -1) and (ABookmark2 = -1) and not FSourceLines[i].Trim.IsEmpty then
      ABookmark2 := i;
  Result := ALineIndex <> -1;
  if Result then
  begin
    var L := TArray<string>(nil);
    if TFunctionMgr.Names.TryGetValue(AFcnClass, L) and (L <> nil) then
      AFcnName := L[0]
    else
      AFcnName := AFcnClass.ClassName;
    ASource := FFullSource;
  end;
end;

end.
