unit XData.JSchema.Generators;

{$I XData.inc}

interface

uses
  Generics.Collections, System.Rtti, System.SysUtils, System.TypInfo,
  Bcl.Json.Converters,
  Bcl.Json.Attributes,
  XData.Aurelius.Model,
  XData.JSchema.Classes;

type
  TTypeToken = Bcl.Json.Converters.TTypeToken;
  TJSchemaGenerators = class;
  TJSchemaGeneratorContext = class;

  IJSchemaGenerator = interface
  ['{DA878C84-99AA-4368-A2E2-66718DD3A2CE}']
    function CreateSchema: TJsonSchema;
  end;

  IJSchemaGeneratorFactory = interface
  ['{A8D24FF5-2401-492E-A73A-048649D5B065}']
    function CreateGenerator(Context: TJSchemaGeneratorContext; const ATypeToken: TTypeToken): IJSchemaGenerator;
  end;

  TJSchemaGeneratorContext = class
  private
    {$IFDEF AUTOREFCOUNT}[Weak]{$ENDIF}
    FGenerators: TJSchemaGenerators;
  public
    property Generators: TJSchemaGenerators read FGenerators write FGenerators;
  end;

  TJSchemaGenerator = class abstract(TInterfacedObject, IJSchemaGenerator)
  public
    function CreateSchema: TJsonSchema; virtual; abstract;
  end;

  TJSchemaTypedGenerator = class abstract(TJSchemaGenerator)
  protected
    function GetTypeToken: TTypeToken; virtual; abstract;
  end;

  TJSchemaGenericGenerator<T; S: TJsonSchema, constructor> = class abstract(TJSchemaTypedGenerator)
  protected
    function GetTypeToken: TTypeToken; override;
    procedure BuildSchema(Schema: S); virtual;
  public
    function CreateSchema: TJsonSchema; override;
  end;

  TJSchemaInt32Generator<T> = class abstract(TJSchemaGenericGenerator<T, TIntegerSchema>)
  end;

  TJSchemaIntegerGenerator = class(TJSchemaInt32Generator<Integer>);
  TJSchemaLongintGenerator = class(TJSchemaInt32Generator<Longint>);
  TJSchemaWordGenerator = class(TJSchemaInt32Generator<Word>);

  TJSchemaInt16Generator<T> = class abstract(TJSchemaGenericGenerator<T, TIntegerSchema>)
  end;

  TJSchemaShortintGenerator = class(TJSchemaInt16Generator<Shortint>);
  TJSchemaByteGenerator = class(TJSchemaInt16Generator<Byte>);
  TJSchemaSmallIntGenerator=  class(TJSchemaInt16Generator<SmallInt>);

  TJSchemaInt64Generator<T> = class abstract(TJSchemaGenericGenerator<T, TIntegerSchema>)
  end;

  TJSchemaCardinalGenerator = class(TJSchemaInt64Generator<Cardinal>);
  TJSchemaLongwordGenerator = class(TJSchemaInt64Generator<Longword>);
  TJSchemaInt64Generator = class(TJSchemaInt64Generator<Int64>);

  TJSchemaBooleanGenerator = class(TJSchemaGenericGenerator<Boolean, TBooleanSchema>)
  end;

  TJSchemaDoubleGenerator<T> = class abstract(TJSchemaGenericGenerator<T, TNumberSchema>)
  end;

  TJSchemaDoubleGenerator = class(TJSchemaDoubleGenerator<Double>);
  TJSchemaSingleGenerator = class(TJSchemaDoubleGenerator<Single>);
  TJSchemaExtendedGenerator = class(TJSchemaDoubleGenerator<Extended>);
  TJSchemaRealGenerator = class(TJSchemaDoubleGenerator<Real>)
  protected
    function GetTypeToken: TTypeToken; override;
  end;

  TJSchemaCurrencyGenerator = class abstract(TJSchemaGenericGenerator<Currency, TNumberSchema>)
  end;

  TJSchemaDateTimeGenerator = class(TJSchemaGenericGenerator<TDateTime, TStringSchema>)
  strict protected
    procedure BuildSchema(Schema: TStringSchema); override;
  end;

  TJSchemaDateGenerator = class(TJSchemaGenericGenerator<TDate, TStringSchema>)
  strict protected
    procedure BuildSchema(Schema: TStringSchema); override;
  end;

  TJSchemaTimeGenerator = class(TJSchemaGenericGenerator<TTime, TStringSchema>)
  strict protected
    procedure BuildSchema(Schema: TStringSchema); override;
  end;

  TJSchemaBinaryGenerator<T> = class abstract(TJSchemaGenericGenerator<T, TStringSchema>)
  strict protected
    procedure BuildSchema(Schema: TStringSchema); override;
  end;

  TJSchemaBytesGenerator = class(TJSchemaBinaryGenerator<TBytes>);
  TJSchemaArrayByteGenerator = class(TJSchemaBinaryGenerator<TArray<byte>>);

  TJSchemaStringGenerator<T> = class abstract(TJSchemaGenericGenerator<T, TStringSchema>)
  strict protected
  end;

  TJSchemaStringGenerator = class(TJSchemaStringGenerator<string>);
  {$IFNDEF NEXTGEN}
  TJSchemaAnsiStringGenerator = class(TJSchemaStringGenerator<AnsiString>);
  TJSchemaWideStringGenerator = class(TJSchemaStringGenerator<WideString>);
  {$ENDIF}

  TJSchemaCharGenerator<T> = class abstract(TJSchemaGenericGenerator<T, TStringSchema>)
  strict protected
  end;

  TJSchemaCharGenerator = class(TJSchemaCharGenerator<Char>);
  {$IFNDEF NEXTGEN}
  TJSchemaAnsiCharGenerator = class(TJSchemaCharGenerator<AnsiChar>);
  TJSchemaWideCharGenerator = class(TJSchemaCharGenerator<WideChar>);
  {$ENDIF}

  TJSchemaGuidGenerator = class(TJSchemaGenericGenerator<TGuid, TStringSchema>)
  strict protected
    procedure BuildSchema(Schema: TStringSchema); override;
  end;

  TJSchemaVariantGenerator = class(TJSchemaGenericGenerator<Variant, TStringSchema>)
  strict protected
    procedure BuildSchema(Schema: TStringSchema); override;
  end;

  TJSchemaEnumGenerator = class(TJSchemaGenerator)
  strict private
    FTypeToken: TTypeToken;
  public
    constructor Create(const ATypeToken: TTypeToken);
    function CreateSchema: TJsonSchema; override;
  end;

  TJSchemaValuedEnumGenerator = class(TJSchemaGenerator)
  strict private
    FTypeToken: TTypeToken;
    FEnumValues: TList<string>;
  public
    constructor Create(const ATypeToken: TTypeToken; EnumValues: TList<string>);
    function CreateSchema: TJsonSchema; override;
  end;

  TJSchemaEnumGeneratorFactory = class(TInterfacedObject, IJSchemaGeneratorFactory)
  strict protected
    function CreateGenerator(Context: TJSchemaGeneratorContext; const ATypeToken: TTypeToken): IJSchemaGenerator;
  end;

  {$IFDEF DELPHIXE3_LVL}
  TJSchemaArrayGenerator = class(TJSchemaGenerator)
  private
    FElementGenerator: IJSchemaGenerator;
  public
    constructor Create(const ATypeToken, AElementTypeToken: TTypeToken; Generators: TJSchemaGenerators);
    function CreateSchema: TJsonSchema; override;
  end;

  TJSchemaArrayGeneratorFactory = class(TInterfacedObject, IJSchemaGeneratorFactory)
  strict private
    function GetElementType(ATypeInfo: PTypeInfo): PTypeInfo;
  strict protected
    function CreateGenerator(Context: TJSchemaGeneratorContext; const ATypeToken: TTypeToken): IJSchemaGenerator;
  end;
  {$ENDIF}

  TJSchemaSetGenerator = class(TJSchemaGenerator)
  strict private
    FTypeToken: TTypeToken;
    FEnumGenerator: IJSchemaGenerator;
  public
    constructor Create(const ATypeToken: TTypeToken; AEnumGenerator: IJSchemaGenerator);
    function CreateSchema: TJsonSchema; override;
  end;

  TJSchemaSetGeneratorFactory = class(TInterfacedObject, IJSchemaGeneratorFactory)
  strict protected
    function CreateGenerator(Context: TJSchemaGeneratorContext; const ATypeToken: TTypeToken): IJSchemaGenerator;
  end;

  TJSchemaStringsGenerator = class(TJSchemaGenerator)
  public
    function CreateSchema: TJsonSchema; override;
  end;

  TJSchemaStringsGeneratorFactory = class(TInterfacedObject, IJSchemaGeneratorFactory)
  strict protected
    function CreateGenerator(Context: TJSchemaGeneratorContext; const ATypeToken: TTypeToken): IJSchemaGenerator;
  end;

  TJSchemaNullableGenerator = class(TJSchemaGenerator)
  private
    FTypeGenerator: IJSchemaGenerator;
  public
    constructor Create(ATypeGenerator: IJSchemaGenerator);
    function CreateSchema: TJsonSchema; override;
  end;

  TJSchemaNullableGeneratorFactory = class(TInterfacedObject, IJSchemaGeneratorFactory)
  strict protected
    function CreateGenerator(Context: TJSchemaGeneratorContext; const ATypeToken: TTypeToken): IJSchemaGenerator;
  end;

  TJSchemaGeneratorFactory = class(TInterfacedObject, IJSchemaGeneratorFactory)
  strict private
    FTypeToken: TTypeToken;
    FGenerator: IJSchemaGenerator;
  strict protected
    function CreateGenerator(Context: TJSchemaGeneratorContext; const ATypeToken: TTypeToken): IJSchemaGenerator;
  public
    constructor Create(const ATypeToken: TTypeToken; AGenerator: IJSchemaGenerator);
  end;

  TJSchemaGeneratorStaticFactory<C: TJSchemaTypedGenerator, constructor> = class
  strict private
    class var
      FFactory: IJSchemaGeneratorFactory;
  strict private
    class function GetFactory: IJSchemaGeneratorFactory; static;
  public
    class destructor Destroy;
    class function CreateFactory: IJSchemaGeneratorFactory; static;
    class property Factory: IJSchemaGeneratorFactory read GetFactory;
  end;

//  TJSchemaGenerators = class(TInterfacedObject, IJsonGenerators)
  TJSchemaGenerators = class(TInterfacedObject)
  public
    type
      ETypeGeneratorNotFound = class(Exception)
      public
        constructor Create(const ATypeToken: TTypeToken);
      end;
  strict private
    FGenerators: TDictionary<TTypeToken, IJSchemaGenerator>;
    FCustomFactories: TList<IJSchemaGeneratorFactory>;
    FDefinitions: TJsonSchemaDefinitions;
    FContext: TRttiContext;
    procedure SetDefinitions(const Value: TJsonSchemaDefinitions);
  public
    constructor Create;
    destructor Destroy; override;
    procedure AddFactory(AFactory: IJSchemaGeneratorFactory);
    procedure All(Proc: TProc<IJSchemaGeneratorFactory>);
    function Find(const ATypeToken: TTypeToken): IJSchemaGenerator;
    function Get(const ATypeToken: TTypeToken): IJSchemaGenerator;
    function ProcessRef(const ATypeToken: TTypeToken): TJsonSchema;
    procedure TrySchemaDefinitions(ADefinitions: TJsonSchemaDefinitions);
    property Definitions: TJsonSchemaDefinitions read FDefinitions write SetDefinitions;
  end;

  TJSchemaGeneratorFactories = class
  strict private
    class var FFactories: TList<IJSchemaGeneratorFactory>;
    class procedure AddDefaultFactories(AList: TList<IJSchemaGeneratorFactory>);
  public
    class constructor Create;
    class destructor Destroy;
    class function DefaultFactories: TEnumerable<IJSchemaGeneratorFactory>;
  end;

  EJsonGeneratorNotFound = class(Exception)
  public
    constructor Create(const ATypeToken: TTypeToken);
  end;

  {$IFDEF DELPHIXE6_LVL}
  TJsonValueGenerator = class(TInterfacedObject, IJSchemaGenerator)
  protected
    function CreateSchema: TJsonSchema;
  end;

  TJsonValueGeneratorFactory = class(TInterfacedObject, IJSchemaGeneratorFactory)
  protected
    function CreateGenerator(Context: TJSchemaGeneratorContext; const ATypeToken: TTypeToken): IJSchemaGenerator;
  end;
  {$ENDIF}

  { **** XData specific code **** }
  TXDataJSchemaGenerators = class(TJSchemaGenerators)
  public
    constructor Create(AModel: TXDataAureliusModel); reintroduce;
  end;

implementation

uses
  {$IFDEF DELPHIXE6_LVL}
  System.JSON,
  {$ENDIF}
  System.Classes,
  Bcl.Collections,
  Bcl.Rtti.Utils,
  XData.JSchema.BlobGenerator,
  XData.JSchema.CriteriaResultGenerator,
  XData.JSchema.ListGenerator,
  XData.JSchema.OrderedDictionaryGenerator,
  XData.JSchema.ObjectGenerator,
  XData.JSchema.EntityGenerator;

{ TJSchemaGeneratorStaticFactory<T, C> }

class function TJSchemaGeneratorStaticFactory<C>.CreateFactory: IJSchemaGeneratorFactory;
var
  Generator: IJSchemaGenerator;
  GeneratorObj: TJSchemaTypedGenerator;
begin
  GeneratorObj := C.Create;
  Generator := GeneratorObj;
  Result := TJSchemaGeneratorFactory.Create(GeneratorObj.GetTypeToken, Generator);
end;

class destructor TJSchemaGeneratorStaticFactory<C>.Destroy;
begin
  FFactory := nil;
end;

class function TJSchemaGeneratorStaticFactory<C>.GetFactory: IJSchemaGeneratorFactory;
begin
  if FFactory = nil then
    FFactory := CreateFactory;
  Result := FFactory;
end;

{ TJSchemaGeneratorFactory }

constructor TJSchemaGeneratorFactory.Create(const ATypeToken: TTypeToken;
  AGenerator: IJSchemaGenerator);
begin
  FTypeToken := ATypeToken;
  FGenerator := AGenerator;
end;

function TJSchemaGeneratorFactory.CreateGenerator(Context: TJSchemaGeneratorContext;
  const ATypeToken: TTypeToken): IJSchemaGenerator;
begin
  if FTypeToken = ATypeToken then
    Result := FGenerator
  else
    Result := nil;
end;

{ TJSchemaEnumGeneratorFactory }

function TJSchemaEnumGeneratorFactory.CreateGenerator(Context: TJSchemaGeneratorContext;
  const ATypeToken: TTypeToken): IJSchemaGenerator;
var
  Attr: JsonEnumValuesAttribute;
  RttiCtxt: TRttiContext;
  RttiType: TRttiType;
begin
  if (ATypeToken.TypeInfo.Kind = tkEnumeration) then
  begin
    RttiCtxt := TRttiContext.Create;
    try
      RttiType := RttiCtxt.GetType(ATypeToken);
      if (RttiType <> nil) and TRttiUtils.GetInstance.TryGetAttribute<JsonEnumValuesAttribute>(RttiType, Attr) then
        Result := TJSchemaValuedEnumGenerator.Create(ATypeToken, Attr.EnumValues)
      else
        Result := TJSchemaEnumGenerator.Create(ATypeToken)
    finally
      RttiCtxt.Free;
    end;
  end
  else
    Result := nil;
end;

{ TJSchemaGeneratorFactories }

class procedure TJSchemaGeneratorFactories.AddDefaultFactories(
  AList: TList<IJSchemaGeneratorFactory>);
begin
  // CUSTOM CONVERTERS - Equivalent to custom converters
  AList.Add(TJSchemaStringsGeneratorFactory.Create);
  AList.Add(TJSchemaListGeneratorFactory.Create);
  AList.Add(TJSchemaOrderedDictionaryGeneratorFactory.Create);

  // Most common types
  AList.Add(TJSchemaGeneratorStaticFactory<TJSchemaStringGenerator>.Factory);
  AList.Add(TJSchemaGeneratorStaticFactory<TJSchemaIntegerGenerator>.Factory);
  AList.Add(TJSchemaGeneratorStaticFactory<TJSchemaBooleanGenerator>.Factory);
  AList.Add(TJSchemaGeneratorStaticFactory<TJSchemaDoubleGenerator>.Factory);
  AList.Add(TJSchemaGeneratorStaticFactory<TJSchemaDateTimeGenerator>.Factory);
  AList.Add(TJSchemaEnumGeneratorFactory.Create);

  // Integer types
  AList.Add(TJSchemaGeneratorStaticFactory<TJSchemaInt64Generator>.Factory);
  AList.Add(TJSchemaGeneratorStaticFactory<TJSchemaLongintGenerator>.Factory);
  AList.Add(TJSchemaGeneratorStaticFactory<TJSchemaCardinalGenerator>.Factory);
  AList.Add(TJSchemaGeneratorStaticFactory<TJSchemaLongwordGenerator>.Factory);
  AList.Add(TJSchemaGeneratorStaticFactory<TJSchemaWordGenerator>.Factory);
  AList.Add(TJSchemaGeneratorStaticFactory<TJSchemaShortintGenerator>.Factory);
  AList.Add(TJSchemaGeneratorStaticFactory<TJSchemaByteGenerator>.Factory);
  AList.Add(TJSchemaGeneratorStaticFactory<TJSchemaSmallIntGenerator>.Factory);

  // Float types
  AList.Add(TJSchemaGeneratorStaticFactory<TJSchemaRealGenerator>.Factory);
  AList.Add(TJSchemaGeneratorStaticFactory<TJSchemaSingleGenerator>.Factory);
  AList.Add(TJSchemaGeneratorStaticFactory<TJSchemaExtendedGenerator>.Factory);
  AList.Add(TJSchemaGeneratorStaticFactory<TJSchemaCurrencyGenerator>.Factory);

  // Date time types
  AList.Add(TJSchemaGeneratorStaticFactory<TJSchemaDateGenerator>.Factory);
  AList.Add(TJSchemaGeneratorStaticFactory<TJSchemaTimeGenerator>.Factory);

  // Native structure types
  AList.Add(TJSchemaSetGeneratorFactory.Create);

  // Guid types
  AList.Add(TJSchemaGeneratorStaticFactory<TJSchemaGuidGenerator>.Factory);

  // Blob types
  AList.Add(TJSchemaGeneratorStaticFactory<TJSchemaBytesGenerator>.Factory);
  AList.Add(TJSchemaGeneratorStaticFactory<TJSchemaArrayByteGenerator>.Factory);

  // Array types
  {$IFDEF DELPHIXE3_LVL}
  AList.Add(TJSchemaArrayGeneratorFactory.Create);
  {$ENDIF}

  // String types
  {$IFNDEF NEXTGEN}
  AList.Add(TJSchemaGeneratorStaticFactory<TJSchemaAnsiStringGenerator>.Factory);
  AList.Add(TJSchemaGeneratorStaticFactory<TJSchemaWideStringGenerator>.Factory);
  {$ENDIF}

  // Char types
  AList.Add(TJSchemaGeneratorStaticFactory<TJSchemaCharGenerator>.Factory);
  {$IFNDEF NEXTGEN}
  AList.Add(TJSchemaGeneratorStaticFactory<TJSchemaAnsiCharGenerator>.Factory);
  AList.Add(TJSchemaGeneratorStaticFactory<TJSchemaWideCharGenerator>.Factory);
  {$ENDIF}

  // Variant type
  AList.Add(TJSchemaGeneratorStaticFactory<TJSchemaVariantGenerator>.Factory);

  // Nullable types
  AList.Add(TJSchemaNullableGeneratorFactory.Create);

  // Object type
  AList.Add(TJSchemaObjectGeneratorFactory.Create);
end;

class constructor TJSchemaGeneratorFactories.Create;
begin
  FFactories := TList<IJSchemaGeneratorFactory>.Create;
  AddDefaultFactories(FFactories);
end;

class function TJSchemaGeneratorFactories.DefaultFactories: TEnumerable<IJSchemaGeneratorFactory>;
begin
  Result := FFactories;
end;

class destructor TJSchemaGeneratorFactories.Destroy;
begin
  FFactories.Free;
end;

{ TJSchemaGenerators }

procedure TJSchemaGenerators.AddFactory(AFactory: IJSchemaGeneratorFactory);
begin
  FCustomFactories.Add(AFactory);
end;

procedure TJSchemaGenerators.All(Proc: TProc<IJSchemaGeneratorFactory>);
var
  Factory: IJSchemaGeneratorFactory;
begin
  for Factory in FCustomFactories do
    Proc(Factory);
  for Factory in TJSchemaGeneratorFactories.DefaultFactories do
    Proc(Factory);
end;

constructor TJSchemaGenerators.Create;
begin
  FGenerators := TObjectDictionary<TTypeToken, IJSchemaGenerator>.Create();
  FCustomFactories := TList<IJSchemaGeneratorFactory>.Create;
  FContext := TRttiContext.Create;
  {$IFDEF DELPHIXE6_LVL}
//  FCustomFactories.Add(TJSchemaValueGeneratorFactory.Create);
  {$ENDIF}
end;

destructor TJSchemaGenerators.Destroy;
begin
  FContext.Free;
  FGenerators.Free;
  FCustomFactories.Free;
  inherited;
end;

function TJSchemaGenerators.Find(const ATypeToken: TTypeToken): IJSchemaGenerator;
var
  Factory: IJSchemaGeneratorFactory;
  Generator: IJSchemaGenerator;
  Context: TJSchemaGeneratorContext;
begin
  if FGenerators.TryGetValue(ATypeToken, Result) then
    Exit;

  Result := nil;
  Context := TJSchemaGeneratorContext.Create;
  try
    Context.Generators := Self;
    for Factory in FCustomFactories do
    begin
      Generator := Factory.CreateGenerator(Context, ATypeToken);
      if Generator <> nil then
      begin
        FGenerators.Add(ATypeToken, Generator);
        Exit(Generator);
      end;
    end;
    for Factory in TJSchemaGeneratorFactories.DefaultFactories do
    begin
      Generator := Factory.CreateGenerator(Context, ATypeToken);
      if Generator <> nil then
      begin
        FGenerators.Add(ATypeToken, Generator);
        Exit(Generator);
      end;
    end;
  finally
    Context.Free;
  end;
end;

function TJSchemaGenerators.Get(const ATypeToken: TTypeToken): IJSchemaGenerator;
begin
  Result := Find(ATypeToken);
  if Result = nil then
    raise EJsonGeneratorNotFound.Create(ATypeToken);
end;

function TJSchemaGenerators.ProcessRef(const ATypeToken: TTypeToken): TJsonSchema;
var
  DefinitionName: string;
  ItemType: TRttiType;
begin
  if (FDefinitions <> nil) and ATypeToken.IsClass and not (ATypeToken.GetClass = System.TObject)
    and not IsValueList(ATypeToken.GetClass) then
  begin
    DefinitionName := ATypeToken.GetClass.QualifiedClassName;
    if ATypeToken.ItemTypeInfo <> nil then
    begin
      // special case when ItemTypeInfo is not nil and we're using definitions. In this case
      // we must "build" the class name based on type info
      ItemType := FContext.GetType(ATypeToken.ItemTypeInfo);
      if ItemType <> nil then
      begin
        // if it's a dictionary, replace the second parameter, otherwise replaces first parameter
        // This is quick and dirty to support dictionaries and lists for now
        if TRttiUtils.GetInstance.IsGenericOf(ATypeToken.GetClass,
            System.Generics.Collections.TDictionary<string, TObject>) or
            TRttiUtils.GetInstance.IsGenericOf(ATypeToken.GetClass,
            Bcl.Collections.TOrderedDictionary<string, TObject>) then
          DefinitionName := TRttiUtils.GetInstance.ReplaceGenericParam(DefinitionName, 1,
            ItemType.QualifiedName)
        else
          DefinitionName := TRttiUtils.GetInstance.ReplaceGenericParam(DefinitionName, 0,
            ItemType.QualifiedName);
      end;
    end;

    if not FDefinitions.ContainsKey(DefinitionName) then
    begin
      // Add a null schema first to avoid stack overflow
      // since CreateSchema might recursive call this ProcessRef function again
      FDefinitions.Add(DefinitionName, nil);
      FDefinitions[DefinitionName] := Get(ATypeToken).CreateSchema;
    end;
    Result := TNameReferenceSchema.Create(DefinitionName);
  end
  else
    Result := Get(ATypeToken).CreateSchema;
end;

procedure TJSchemaGenerators.SetDefinitions(
  const Value: TJsonSchemaDefinitions);
begin
  FDefinitions := Value;
end;

procedure TJSchemaGenerators.TrySchemaDefinitions(
  ADefinitions: TJsonSchemaDefinitions);
begin
  if FDefinitions = nil then
    SetDefinitions(ADefinitions);
end;

{ TJSchemaGenerators.ETypeGeneratorNotFound }

constructor TJSchemaGenerators.ETypeGeneratorNotFound.Create(const ATypeToken: TTypeToken);
begin
  inherited CreateFmt('Could not find Generator for type "%s"', [GetTypeName(ATypeToken)]);
end;

{ TJSchemaEnumGenerator }

constructor TJSchemaEnumGenerator.Create(const ATypeToken: TTypeToken);
begin
  FTypeToken := ATypeToken;
end;

function TJSchemaEnumGenerator.CreateSchema: TJsonSchema;
var
  EnumSchema: TStringSchema;
  I: Integer;
  Info: PTypeInfo;
begin
  EnumSchema := TStringSchema.Create;
  try
    Info := FTypeToken;
    for I := GetTypeData(Info)^.MinValue to GetTypeData(Info)^.MaxValue do
      EnumSchema.Enum.Add(GetEnumName(Info, I));
    Result := EnumSchema;
  except
    EnumSchema.Free;
    raise;
  end;
end;

{ TJSchemaGenericGenerator<T> }

procedure TJSchemaGenericGenerator<T; S>.BuildSchema(Schema: S);
begin
end;

function TJSchemaGenericGenerator<T; S>.CreateSchema: TJsonSchema;
begin
  Result := S.Create;
  try
    BuildSchema(S(Result))
  except
    Result.Free;
    raise;
  end;
end;

function TJSchemaGenericGenerator<T; S>.GetTypeToken: TTypeToken;
begin
  Result := TypeInfo(T);
end;

{ EJsonGeneratorNotFound }

constructor EJsonGeneratorNotFound.Create(const ATypeToken: TTypeToken);
begin
  inherited CreateFmt('Could not find JSON Generator for type "%s"',
    [GetTypeName(ATypeToken)]);
end;

{ TXDataJSchemaGenerators }

constructor TXDataJSchemaGenerators.Create(AModel: TXDataAureliusModel);
begin
  inherited Create;
  AddFactory(TJSchemaGeneratorStaticFactory<TJSchemaBlobGenerator>.CreateFactory);
  AddFactory(TJSchemaGeneratorStaticFactory<TJSchemaCriteriaResultGenerator>.CreateFactory);
  AddFactory(TXDataJSchemaGeneratorFactory.Create(Self, AModel));
  {$IFDEF DELPHIXE6_LVL}
  AddFactory(TJsonValueGeneratorFactory.Create);
  {$ENDIF}
end;

{ TJSchemaDateTimeGenerator }

procedure TJSchemaDateTimeGenerator.BuildSchema(Schema: TStringSchema);
begin
  inherited;
  Schema.Format := 'date-time';
end;

{ TJSchemaDateGenerator }

procedure TJSchemaDateGenerator.BuildSchema(Schema: TStringSchema);
begin
  inherited;
  Schema.Format := 'date';
end;

{ TJSchemaTimeGenerator }

procedure TJSchemaTimeGenerator.BuildSchema(Schema: TStringSchema);
begin
  inherited;
  Schema.Format := 'date-time';
end;

{ TJSchemaBinaryGenerator<T> }

procedure TJSchemaBinaryGenerator<T>.BuildSchema(Schema: TStringSchema);
begin
  inherited;
  Schema.Format := 'byte';
end;

{ TJSchemaGuidGenerator }

procedure TJSchemaGuidGenerator.BuildSchema(Schema: TStringSchema);
begin
  inherited;
//  {$MESSAGE WARN 'Review this'}
//  Schema.Format := 'uuid';
end;

{ TJSchemaVariantGenerator }

procedure TJSchemaVariantGenerator.BuildSchema(Schema: TStringSchema);
begin
  inherited;
  Schema.Format := 'variant';
end;

{ TJSchemaNullableGeneratorFactory }

function TJSchemaNullableGeneratorFactory.CreateGenerator(Context: TJSchemaGeneratorContext;
  const ATypeToken: TTypeToken): IJSchemaGenerator;
var
  RealType: PTypeInfo;
  Generator: IJSchemaGenerator;
begin
  if TRttiUtils.GetInstance.IsNullable(ATypeToken) then
  begin
    RealType := TRttiUtils.GetInstance.GetRealType(ATypeToken);
    Generator := Context.Generators.Get(RealType);
    Result := TJSchemaNullableGenerator.Create(Generator);
  end
  else
    Result := nil;
end;

{ TJSchemaNullableGenerator }

constructor TJSchemaNullableGenerator.Create(ATypeGenerator: IJSchemaGenerator);
begin
  FTypeGenerator := ATypeGenerator;
end;

function TJSchemaNullableGenerator.CreateSchema: TJsonSchema;
begin
  Result := FTypeGenerator.CreateSchema;
end;

{ TJSchemaRealGenerator }

function TJSchemaRealGenerator.GetTypeToken: TTypeToken;
begin
  Result := TypeInfo(Real);
end;

{ TJsonValueGeneratorFactory }

{$IFDEF DELPHIXE6_LVL}
function TJsonValueGeneratorFactory.CreateGenerator(
  Context: TJSchemaGeneratorContext;
  const ATypeToken: TTypeToken): IJSchemaGenerator;
begin
  if ATypeToken.IsClass and
    GetTypeData(ATypeToken)^.ClassType.InheritsFrom(TJSONAncestor) then
    Result := TJsonValueGenerator.Create
  else
    Result := nil;
end;
{$ENDIF}

{ TJsonValueGenerator }

{$IFDEF DELPHIXE6_LVL}
function TJsonValueGenerator.CreateSchema: TJsonSchema;
begin
  Result := TJsonSchema.Create;
end;
{$ENDIF}

{ TJSchemaValuedEnumGenerator }

constructor TJSchemaValuedEnumGenerator.Create(const ATypeToken: TTypeToken;
  EnumValues: TList<string>);
begin
  FTypeToken := ATypeToken;
  FEnumValues := EnumValues;
end;

function TJSchemaValuedEnumGenerator.CreateSchema: TJsonSchema;
var
  I: Integer;
  EnumSchema: TStringSchema;
begin
  EnumSchema := TStringSchema.Create;
  try
    for I := 0 to FEnumValues.Count - 1 do
      EnumSchema.Enum.Add(FEnumValues[I]);
    Result := EnumSchema;
  except
    EnumSchema.Free;
    raise;
  end;
end;

{ TJSchemaSetGeneratorFactory }

function TJSchemaSetGeneratorFactory.CreateGenerator(
  Context: TJSchemaGeneratorContext;
  const ATypeToken: TTypeToken): IJSchemaGenerator;
var
  EnumGenerator: IJSchemaGenerator;
begin
  Result := nil;
  if (ATypeToken.TypeInfo.Kind = tkSet) then
  begin
    EnumGenerator := Context.Generators.Find(GetTypeData(ATypeToken.TypeInfo).CompType^);
    if EnumGenerator <> nil then
      Result := TJSchemaSetGenerator.Create(ATypeToken, EnumGenerator);
  end;
end;

{ TJSchemaSetGenerator }

constructor TJSchemaSetGenerator.Create(const ATypeToken: TTypeToken;
  AEnumGenerator: IJSchemaGenerator);
begin
  FTypeToken := ATypeToken;
  FEnumGenerator := AEnumGenerator;
end;

function TJSchemaSetGenerator.CreateSchema: TJsonSchema;
var
  Schema: TArraySchema;
begin
  Schema := TArraySchema.Create;
  try
    Schema.ItemSchemas.Add(FEnumGenerator.CreateSchema);
    Schema.UniqueItems := True;
    Result := Schema;
  except
    Schema.Free;
    raise;
  end;
end;

{ TJSchemaArrayGenerator }

{$IFDEF DELPHIXE3_LVL}
constructor TJSchemaArrayGenerator.Create(const ATypeToken,
  AElementTypeToken: TTypeToken; Generators: TJSchemaGenerators);
begin
  FElementGenerator := Generators.Get(AElementTypeToken);
end;
{$ENDIF}

{$IFDEF DELPHIXE3_LVL}
function TJSchemaArrayGenerator.CreateSchema: TJsonSchema;
var
  Schema: TArraySchema;
begin
  Schema := TArraySchema.Create;
  try
    Schema.ItemSchemas.Add(FElementGenerator.CreateSchema);
    Result := Schema;
  except
    Schema.Free;
    raise;
  end;
end;
{$ENDIF}

{ TJSchemaArrayGeneratorFactory }

{$IFDEF DELPHIXE3_LVL}
function TJSchemaArrayGeneratorFactory.CreateGenerator(
  Context: TJSchemaGeneratorContext;
  const ATypeToken: TTypeToken): IJSchemaGenerator;
var
  ElementType: PTypeInfo;
begin
  Result := nil;
  if ATypeToken.TypeInfo.Kind = tkDynArray then
  begin
    ElementType := GetElementType(ATypeToken.TypeInfo);
    if ElementType <> nil then
      Result := TJSchemaArrayGenerator.Create(ATypeToken, ElementType, Context.Generators);
  end;
end;
{$ENDIF}

{$IFDEF DELPHIXE3_LVL}
function TJSchemaArrayGeneratorFactory.GetElementType(
  ATypeInfo: PTypeInfo): PTypeInfo;
var
  ref: PPTypeInfo;
begin
  ref := GetTypeData(ATypeInfo).DynArrElType;
  if ref = nil then
    Result := nil
  else
    Result := ref^;
end;
{$ENDIF}

{ TJSchemaStringsGeneratorFactory }

function TJSchemaStringsGeneratorFactory.CreateGenerator(
  Context: TJSchemaGeneratorContext;
  const ATypeToken: TTypeToken): IJSchemaGenerator;
begin
  if ATypeToken.IsClass and ATypeToken.GetClass.InheritsFrom(TStrings) then
    Result := TJSchemaStringsGenerator.Create
  else
    Result := nil;
end;

{ TJSchemaStringsGenerator }

function TJSchemaStringsGenerator.CreateSchema: TJsonSchema;
var
  Schema: TArraySchema;
begin
  Schema := TArraySchema.Create;
  try
    Schema.ItemSchemas.Add(TStringSchema.Create);
    Result := Schema;
  except
    Schema.Free;
    raise;
  end;
end;

end.

