unit U_PreviewEngine;

interface
uses
  System.Classes, System.Generics.Collections,
  Vcl.Forms, Vcl.ActnList;

type
  TCustomPreviewEngineForm = class;
  TCustomPreviewEngineFormClass = class of TCustomPreviewEngineForm;

  { -------------------------------------------------------------------------- }
  IInfoLineDisplayer = interface
    procedure Add(const Group, Name, Value, Chars: string); overload;
    procedure Add(const Group, Name: string; const Value: Integer); overload;
    procedure Add(const Group, Name, Prefix: string; const Value: Integer); overload;
    procedure Add(const Group, Name: string; const Value: AnsiChar); overload;
    procedure Add(const Group, Name: string; const Value: string); overload;
    procedure Add(const Group, Name: string; const Value: Integer; const Names: array of string); overload;
  end;
  TInfoLineProvider = reference to procedure(const Group, Key, Short, Long: string);

  { -------------------------------------------------------------------------- }
  TRenderer = class
  private
    FName: string;
  protected
    FParent: TCustomPreviewEngineForm;
    FDisplayName: string;
  public
    constructor Create(const Parent: TCustomPreviewEngineForm;
                       const Name: string = '');

    function  CouldShowFile(const FileName: string): boolean; virtual; abstract;
    function  TryShowFile(const FileName: string): boolean; virtual; abstract;
    procedure PopulateInfo(const IInfoLines: IInfoLineDisplayer); virtual; abstract;
    function  SummarizeInfo: string; virtual;

    property Form: TCustomPreviewEngineForm read FParent;
    property Name: string                   read FName;
    property DisplayName: string            read FDisplayName write FDisplayName;
  end {TPreviewEngine};


  { -------------------------------------------------------------------------- }
  TRendererList = TObjectList<TRenderer>;
  TDynRendererArray = TArray<TRenderer>;


  { -------------------------------------------------------------------------- }
  TCustomPreviewEngineForm = class(TForm)
  protected
    FRenderers: TRendererList;
    FFileName: string;

    function GetRenderers: TRendererList; virtual;
    function GetActionList: TActionList; virtual;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;

    procedure Clear; virtual; abstract;
    function  CouldShowFile(const FileName: string): TDynRendererArray; virtual;
    function  TryShowFile(const FileName: string; out Renderer: TRenderer): boolean; virtual;

    property Renderers: TRendererList     read GetRenderers;
    property ActionList: TActionList      read GetActionList;

    property FileName: string             read FFileName;
  private
    class var FFormClasses: TList<TCustomPreviewEngineFormClass>;
  public
    class procedure RegisterForm(const FormClass: TCustomPreviewEngineFormClass);
    class property  RegisteredForms: TList<TCustomPreviewEngineFormClass> read FFormClasses;
    class destructor Destroy;
  end;


implementation

uses
  System.SysUtils;

{ ================================================================================================ }
{ TCustomPreviewEngineForm }

{ ------------------------------------------------------------------------------------------------ }
constructor TCustomPreviewEngineForm.Create(AOwner: TComponent);
begin
  inherited;
  FRenderers := TObjectList<TRenderer>.Create;
end {TCustomPreviewEngineForm.Create};
{ ------------------------------------------------------------------------------------------------ }
destructor TCustomPreviewEngineForm.Destroy;
begin
  FRenderers.OnNotify := nil;
  FRenderers.Free;
  inherited;
end {TCustomPreviewEngineForm.Destroy};

{ ------------------------------------------------------------------------------------------------ }
function TCustomPreviewEngineForm.GetActionList: TActionList;
begin
  Result := nil;
end {TCustomPreviewEngineForm.GetActionList};

{ ------------------------------------------------------------------------------------------------ }
function TCustomPreviewEngineForm.GetRenderers: TRendererList;
begin
  Result := FRenderers;
end {TCustomPreviewEngineForm.GetRenderers};

{ ------------------------------------------------------------------------------------------------ }
function TCustomPreviewEngineForm.CouldShowFile(const FileName: string): TDynRendererArray;
var
  i, Count, Offset: integer;
begin
  Result := GetRenderers.ToArray();
  Count := Length(Result);
  for i := Low(Result) to High(Result) do begin
    if not Result[i].CouldShowFile(FileName) then begin
      Result[i] := nil;
      Dec(Count);
    end;
  end;
  if Count = 0 then begin
    SetLength(Result, 0);
  end else if Count = Length(Result) then begin
    // just return all engines
  end else begin
    Offset := 0;
    for i := Low(Result) to High(Result) do begin
      while (i + Offset <= High(Result)) and (Result[i + Offset] = nil) do
        Inc(Offset);
      if (i + Offset) > High(Result) then
        Break;
      if Offset > 0 then
        Result[i] := Result[i + Offset];
    end {for};
    SetLength(Result, Count);
  end {if};
end {TCustomPreviewEngineForm.CouldShowFile};

{ ------------------------------------------------------------------------------------------------ }
function TCustomPreviewEngineForm.TryShowFile(const FileName: string; out Renderer: TRenderer): boolean;
var
  RenderEngine: TRenderer;
  Buffer: array[0..1023] of Char;
  BufferSize: integer;
  ErrorMsg: string;
begin
  FFileName := FileName;
  for RenderEngine in CouldShowFile(FileName) do begin
    try
      Result := RenderEngine.TryShowFile(FileName);
      if Result then begin
        Renderer := RenderEngine;
        Exit;
      end;
    except
      BufferSize := ExceptionErrorMessage(ExceptObject, ExceptAddr, Buffer, Length(Buffer));
      ErrorMsg := Format('%s reports an error: %s',
                         [RenderEngine.DisplayName, Copy(Buffer, 1, BufferSize)]);
    end;
  end;
  Result := False;
  Renderer := nil;
  if Length(ErrorMsg) > 0 then
    raise Exception.Create(ErrorMsg);
end {TCustomPreviewEngineForm.TryShowFile};

{ ------------------------------------------------------------------------------------------------ }
class procedure TCustomPreviewEngineForm.RegisterForm(const FormClass: TCustomPreviewEngineFormClass);
begin
  if FFormClasses = nil then
    FFormClasses := TList<TCustomPreviewEngineFormClass>.Create;
  FFormClasses.Add(FormClass);
end {TCustomPreviewEngineForm.RegisterForm};

{ ------------------------------------------------------------------------------------------------ }
class destructor TCustomPreviewEngineForm.Destroy;
begin
  if FFormClasses <> nil then
    FreeAndNil(FFormClasses);
end {TCustomPreviewEngineForm.Destroy};



{ ================================================================================================ }
{ TPreviewEngine }

{ ------------------------------------------------------------------------------------------------ }
constructor TRenderer.Create(const Parent: TCustomPreviewEngineForm;
                             const Name: string);
begin
  inherited Create;
  FParent := Parent;
  if Name <> '' then
    FName := Name
  else
    FName := Self.ClassName;
  FDisplayName := FName;
end {TPreviewEngine.Create};

{ ------------------------------------------------------------------------------------------------ }
function TRenderer.SummarizeInfo: string;
begin
  Result := #0;
end {TRenderer.SummarizeInfo};

end.
