unit XData.Aurelius.CriteriaBuilder;

interface

uses
  Generics.Collections,
  Aurelius.Criteria.Base,
  XData.Aurelius.Model,
  XData.Criteria.AliasVisitor,
  XData.Criteria.CriterionVisitor,
  XData.Criteria.ExpandVisitor,
  XData.Criteria.ProjectionVisitor,
  XData.Module.QueryInfo,
  XData.Model.Classes,
  XData.Query.Expression;

type
  TXDataCriteriaBuilder = class
  strict private
    FModel: TXDataAureliusModel;
    FAliasVisitor: TQueryAliasVisitor;
    FExpandVisitor: TQueryAliasExpandVisitor;
    FCriterionVisitor: TQueryCriterionVisitor;
    FProjectionVisitor: TQueryProjectionVisitor;
    FCriterionList: TObjectList<TCustomCriterion>;
    FOrderList: TObjectList<TOrder>;
    FQueryInfo: TXDataQueryInfo;
    function CreateCriterion(const Expr: TQueryExpression): TCustomCriterion;
    function CreateOrder(const Info: TQueryOrderInfo): TOrder;
    procedure ProcessExpand(Info: TQueryExpandInfo);
  public
    constructor Create(const AModel: TXDataAureliusModel; const AQueryInfo: TXDataQueryInfo);
    destructor Destroy; override;
    procedure UpdateCriteria(const Criteria: TCriteria);
  end;

implementation

{ TCriteriaBuilder }

constructor TXDataCriteriaBuilder.Create(const AModel: TXDataAureliusModel;
  const AQueryInfo: TXDataQueryInfo);
begin
  FModel := AModel;
  FQueryInfo := AQueryInfo;
  FAliasVisitor := TQueryAliasVisitor.Create(FModel);
  FExpandVisitor := TQueryAliasExpandVisitor.Create(FModel, FAliasVisitor.Aliases);
  FCriterionVisitor := TQueryCriterionVisitor.Create;
  FProjectionVisitor := TQueryProjectionVisitor.Create;
  FCriterionList := TObjectList<TCustomCriterion>.Create(false);
  FOrderList := TObjectList<TOrder>.Create(false);
end;

function TXDataCriteriaBuilder.CreateCriterion(const Expr: TQueryExpression): TCustomCriterion;
begin
  Expr.Accept(FAliasVisitor);
  FAliasVisitor.Peek.Accept(FCriterionVisitor);
  Result := FCriterionVisitor.Pop;
end;

function TXDataCriteriaBuilder.CreateOrder(const Info: TQueryOrderInfo): TOrder;
begin
  Info.Expression.Accept(FAliasVisitor);
  FAliasVisitor.Peek.Accept(FProjectionVisitor);
  Result := TOrder.Create(FProjectionVisitor.Pop, Info.Ascending);
end;

destructor TXDataCriteriaBuilder.Destroy;
begin
  FExpandVisitor.Free;
  FAliasVisitor.Free;
  FCriterionVisitor.Free;
  FProjectionVisitor.Free;
  FCriterionList.OwnsObjects := true;
  FCriterionList.Free;
  FOrderList.OwnsObjects := true;
  FOrderList.Free;
  inherited;
end;

procedure TXDataCriteriaBuilder.ProcessExpand(Info: TQueryExpandInfo);
begin
  Info.Expression.Accept(FExpandVisitor);
end;

procedure TXDataCriteriaBuilder.UpdateCriteria(const Criteria: TCriteria);
var
  Expr: TQueryExpression;
  OrderInfo: TQueryOrderInfo;
  ExpandInfo: TQueryExpandInfo;
  Alias: TQueryAliasInfo;
  Criterion: TCustomCriterion;
  Order: TOrder;
begin
  if FQueryInfo.IsEmpty then Exit;

  // final processing, create the TCriteria and TOrder objects
  for Expr in FQueryInfo.Filters do
    FCriterionList.Add(CreateCriterion(Expr));
  for OrderInfo in FQueryInfo.Orders do
    FOrderList.Add(CreateOrder(OrderInfo));

  for ExpandInfo in FQueryInfo.ExpandItems do
    ProcessExpand(ExpandInfo);

  // Now update criteria
  for Alias in FAliasVisitor.Aliases do
    Criteria.CreateAlias(Alias.FullPath, Alias.Alias, Alias.FetchMode);
  for Criterion in FCriterionList do
    Criteria.Add(Criterion);
  FCriterionList.Clear;
  for Order in FOrderList do
    Criteria.AddOrder(Order);
  FOrderList.Clear;

  if FQueryInfo.HasTop then
    Criteria.Take(FQueryInfo.Top);
  if FQueryInfo.HasSkip then
    Criteria.Skip(FQueryInfo.Skip);
end;

end.
