unit XData.Wizard.Service.CodeGenerator;

interface

uses
  System.Classes, System.SysUtils,
  System.Generics.Collections,

  XData.Wizard.Service.Settings;

type
  TGeneratedCode = class;

  TXDataServiceCodeGenerator = class
  strict private
    FSettings: TXDataServiceSettings;
    FGeneratedCode: TGeneratedCode;
    function GetInterfaceUnitName: string;
    function GetImplementationUnitName: string;
    function GetUniqueUnitName: string;
    function GetNewGuid: string;
    function GetServiceInterfaceSeparetedUnit: string;
    function GetServiceImplementationSeparetedUnit: string;
    function GetServiceUniqueUnit: string;
    function GetSampleMethodsDeclaration(AIncludeAttributesAndComments: Boolean = True): string;
    function GetSampleMethodsImplementation: string;
    procedure ReplaceText(AContent: TStrings; AOldValue: string;
      ANewValue: string);
  public
    constructor Create(ASettings: TXDataServiceSettings);
    destructor Destroy; override;
    procedure Execute;
    property GeneratedCode: TGeneratedCode read FGeneratedCode;
  end;

  TXDataServiceUnit = class
  strict private
    FName: string;
    FSourceCode: string;
  public
    constructor Create(AName: string; ASourceCode: string);
    property Name: string read FName;
    property SourceCode: string read FSourceCode;
  end;

  TGeneratedCode = class
  strict private
    FUnits: TList<TXDataServiceUnit>;
  private
    procedure AddUnit(AUnitName: string; ASourceCode: string);
  public
    constructor Create;
    destructor Destroy; override;
    property Units: TList<TXDataServiceUnit> read FUnits;
  end;


implementation

uses
  Aurelius.Global.Utils;

{ TXDataServiceCodeGenerator }

constructor TXDataServiceCodeGenerator.Create(ASettings: TXDataServiceSettings);
begin
  FSettings := ASettings;
  FGeneratedCode := TGeneratedCode.Create;
end;

function TXDataServiceCodeGenerator.GetServiceInterfaceSeparetedUnit: string;
var
  Source: TStrings;
begin
  Result := '';
  Source := TStringList.Create;
  try
    Source.Add('unit {UNITNAME_INTERFACE};');
    Source.Add('');
    Source.Add('interface');
    Source.Add('');
    Source.Add('uses');
    if FSettings.Model <> '' then
      Source.Add('  Aurelius.Mapping.Attributes,');
    Source.Add('  XData.Service.Common;');
    Source.Add('');
    Source.Add('type');
    Source.Add('  [ServiceContract]');
    if FSettings.Model <> '' then
      Source.Add('  [Model(''{MODEL_NAME}'')]');
    Source.Add('  I{SERVICE_NAME}Service = interface(IInvokable)');
    Source.Add('    [''{GUID}'']');
    if FSettings.AddSampleMethods then
      Source.Add(GetSampleMethodsDeclaration);
    Source.Add('  end;');
    Source.Add('');
    Source.Add('implementation');
    Source.Add('');
    Source.Add('initialization');
    Source.Add('  RegisterServiceType(TypeInfo(I{SERVICE_NAME}Service));');
    Source.Add('');
    Source.Add('end.');

    //ReplaceText(Source, '{UNITNAME_INTERFACE}', GetInterfaceUnitName);
    ReplaceText(Source, '{SERVICE_NAME}', FSettings.ServiceName);
    ReplaceText(Source, '{MODEL_NAME}', FSettings.Model);
    ReplaceText(Source, '{GUID}', GetNewGuid);

    Result := Source.Text;
  finally
    Source.Free;
  end;
end;

function TXDataServiceCodeGenerator.GetServiceUniqueUnit: string;
var
  Source: TStrings;
begin
  Result := '';
  Source := TStringList.Create;
  try
    Source.Add('unit {UNITNAME};');
    Source.Add('');
    Source.Add('interface');
    Source.Add('');
    Source.Add('uses');
    if FSettings.Model <> '' then
      Source.Add('  Aurelius.Mapping.Attributes,');
    Source.Add('  XData.Server.Module,');
    Source.Add('  XData.Service.Common;');
    Source.Add('');
    Source.Add('type');
    Source.Add('  [ServiceContract]');
    if FSettings.Model <> '' then
      Source.Add('  [Model(''{MODEL_NAME}'')]');
    Source.Add('  I{SERVICE_NAME}Service = interface(IInvokable)');
    Source.Add('    [''{GUID}'']');
    if FSettings.AddSampleMethods then
      Source.Add(GetSampleMethodsDeclaration);
    Source.Add('  end;');
    Source.Add('');
    Source.Add('  [ServiceImplementation]');
    Source.Add('  T{SERVICE_NAME}Service = class(TInterfacedObject, I{SERVICE_NAME}Service)');
    if FSettings.AddSampleMethods then
    begin
      Source.Add('  private');
      Source.Add(GetSampleMethodsDeclaration(False));
    end;
    Source.Add('  end;');
    Source.Add('');
    Source.Add('implementation');
    Source.Add('');
    if FSettings.AddSampleMethods then
      Source.Add(GetSampleMethodsImplementation);
    Source.Add('');
    Source.Add('initialization');
    Source.Add('  RegisterServiceType(TypeInfo(I{SERVICE_NAME}Service));');
    Source.Add('  RegisterServiceType(T{SERVICE_NAME}Service);');
    Source.Add('');
    Source.Add('end.');

    //ReplaceText(Source, '{UNITNAME}', GetUniqueUnitName);
    ReplaceText(Source, '{SERVICE_NAME}', FSettings.ServiceName);
    ReplaceText(Source, '{MODEL_NAME}', FSettings.Model);
    ReplaceText(Source, '{GUID}', GetNewGuid);

    Result := Source.Text;
  finally
    Source.Free;
  end;
end;

function TXDataServiceCodeGenerator.GetServiceImplementationSeparetedUnit: string;
var
  Source: TStrings;
begin
  Result := '';
  Source := TStringList.Create;
  try
    Source.Add('unit {UNITNAME_IMPLEMENTATION};');
    Source.Add('');
    Source.Add('interface');
    Source.Add('');
    Source.Add('uses');
    Source.Add('  XData.Server.Module,');
    Source.Add('  XData.Service.Common,');
    Source.Add('  {UNITNAME_INTERFACE};');
    Source.Add('');
    Source.Add('type');
    Source.Add('  [ServiceImplementation]');
    Source.Add('  T{SERVICE_NAME}Service = class(TInterfacedObject, I{SERVICE_NAME}Service)');
    if FSettings.AddSampleMethods then
    begin
      Source.Add('  private');
      Source.Add(GetSampleMethodsDeclaration(False));
    end;
    Source.Add('  end;');
    Source.Add('');
    Source.Add('implementation');
    Source.Add('');
    if FSettings.AddSampleMethods then
      Source.Add(GetSampleMethodsImplementation);
    Source.Add('');
    Source.Add('initialization');
    Source.Add('  RegisterServiceType(T{SERVICE_NAME}Service);');
    Source.Add('');
    Source.Add('end.');

    //ReplaceText(Source, '{UNITNAME_IMPLEMENTATION}', GetImplementationUnitName);
    //ReplaceText(Source, '{UNITNAME_INTERFACE}', GetInterfaceUnitName);
    ReplaceText(Source, '{SERVICE_NAME}', FSettings.ServiceName);

    Result := Source.Text;
  finally
    Source.Free;
  end;
end;

destructor TXDataServiceCodeGenerator.Destroy;
begin
  FGeneratedCode.Free;
  inherited;
end;

procedure TXDataServiceCodeGenerator.Execute;
begin
  FGeneratedCode.Units.Clear;
  if FSettings.SeparetedUnits then
  begin
    FGeneratedCode.AddUnit(GetInterfaceUnitName, GetServiceInterfaceSeparetedUnit);
    FGeneratedCode.AddUnit(GetImplementationUnitName, GetServiceImplementationSeparetedUnit);
  end
  else
    FGeneratedCode.AddUnit(GetUniqueUnitName, GetServiceUniqueUnit);
end;

function TXDataServiceCodeGenerator.GetImplementationUnitName: string;
begin
  Result := Format('%sServiceImplementation', [FSettings.ServiceName]);
end;

function TXDataServiceCodeGenerator.GetInterfaceUnitName: string;
begin
  Result := Format('%sService', [FSettings.ServiceName]);
end;

function TXDataServiceCodeGenerator.GetNewGuid: string;
begin
  Result := TUtils.GuidToVariant(TUtils.NewGuid);
end;

function TXDataServiceCodeGenerator.GetSampleMethodsDeclaration(AIncludeAttributesAndComments: Boolean): string;
var
  Source: TStrings;
begin
  Result := '';
  Source := TStringList.Create;
  try
    if AIncludeAttributesAndComments then
      Source.Add('    [HttpGet] function Sum(A, B: double): double;')
    else
      Source.Add('    function Sum(A, B: double): double;');
    if AIncludeAttributesAndComments then
      Source.Add('    // By default, any service operation responds to (is invoked by) a POST request from the client.');
    Source.Add('    function EchoString(Value: string): string;');

    Result := TrimRight(Source.Text);
  finally
    Source.Free;
  end;
end;

function TXDataServiceCodeGenerator.GetSampleMethodsImplementation: string;
var
  Source: TStrings;
begin
  Result := '';
  Source := TStringList.Create;
  try
    Source.Add('function T{SERVICE_NAME}Service.Sum(A, B: double): double;');
    Source.Add('begin');
    Source.Add('  Result := A + B;');
    Source.Add('end;');
    Source.Add('');
    Source.Add('function T{SERVICE_NAME}Service.EchoString(Value: string): string;');
    Source.Add('begin');
    Source.Add('  Result := Value;');
    Source.Add('end;');

    ReplaceText(Source, '{SERVICE_NAME}', FSettings.ServiceName);

    Result := TrimRight(Source.Text);
  finally
    Source.Free;
  end;
end;

function TXDataServiceCodeGenerator.GetUniqueUnitName: string;
begin
  Result := Format('%sService', [FSettings.ServiceName]);
end;

procedure TXDataServiceCodeGenerator.ReplaceText(AContent: TStrings; AOldValue,
  ANewValue: string);
var
  I: Integer;
begin
  for I := 0 to AContent.Count - 1 do
    AContent[I] := StringReplace(AContent[I], AOldValue, ANewValue, [rfReplaceAll, rfIgnoreCase]);
end;

{ TGeneratedCode }

procedure TGeneratedCode.AddUnit(AUnitName: string; ASourceCode: string);
begin
  FUnits.Add(TXDataServiceUnit.Create(AUnitName, ASourceCode));
end;

constructor TGeneratedCode.Create;
begin
  FUnits := TObjectList<TXDataServiceUnit>.Create;
end;

destructor TGeneratedCode.Destroy;
begin
  FUnits.Free;
  inherited;
end;

{ TXDataServiceUnit }

constructor TXDataServiceUnit.Create(AName: string; ASourceCode: string);
begin
  FName := AName;
  FSourceCode := ASourceCode;
end;

end.
