﻿using Irony.Parsing;

namespace Breeze.WebApi {

  [Language("ODataFilterGrammar", "1.65", "OData $filter grammar")]
  public class ODataFilterGrammar : Grammar {
    public ODataFilterGrammar() {
      //Terminals

      var simpleIdentifier = TerminalFactory.CreateCSharpIdentifier("Identifier");
      var stringLiteral = new StringLiteral("StringLiteral", "'", StringOptions.NoEscapes | StringOptions.AllowsDoubledQuote);
      var numberLiteral = new NumberLiteral("NumberLiteral");
      
      // This impl has problems with .NET 4.5 - so ToTerm methods below were used instead.
      //var constant = new ConstantTerminal("Constant", typeof(Object));
      //constant.Add("true", true);
      //constant.Add("false", false);
      //constant.Add("null", null);
      var trueLiteral = ToTerm("true", "Constant");
      var falseLiteral = ToTerm("false", "Constant");
      var nullLiteral = ToTerm("null", "Constant");

      var dateTimeOffsetLiteral = new StringLiteral("DateTimeOffsetLiteral");
      dateTimeOffsetLiteral.AddStartEnd("datetimeoffset'","'", StringOptions.None);
      var dateTimeLiteral = new StringLiteral("DateTimeLiteral");
      dateTimeLiteral.AddStartEnd("datetime'", "'", StringOptions.None);
      var timeLiteral = new StringLiteral("TimeLiteral");
      timeLiteral.AddStartEnd("time'", "'", StringOptions.None);
      var guidLiteral = new StringLiteral("GuidLiteral");
      guidLiteral.AddStartEnd("guid'", "'", StringOptions.None);

      //NonTerminals
      var baseExpr = new NonTerminal("BaseExpr");
      var compositeIdentifier = new NonTerminal("CompositeIdentifier");
      var parenExpr = new NonTerminal("ParenExpr");
      var methodCallExpr = new NonTerminal("MethodCallExpr"); 
      var literalExpr = new NonTerminal("LiteralExpr");
      var subExpr= new NonTerminal("SubExpr");
      var lambdaExpr = new NonTerminal("LambdaExpr");
      var lambdaVariable = new NonTerminal("LambdaVariable");

      var memberExpr = new NonTerminal("MemberExpr");
      var binaryExpr = new NonTerminal("BinaryExpr"); 
      var binaryExprOp = new NonTerminal("BinaryExprOp");
      var unaryExpr = new NonTerminal("UnaryExpr"); 
      var unaryExprOp = new NonTerminal("UnaryExprOp");      

      #region Methods
      // bool
      var anyMethodCallExpr = new NonTerminal("AnyMethodCallExpr");
      var allMethodCallExpr = new NonTerminal("AllMethodCallExpr");
      var substringOfMethodCallExpr = new NonTerminal("SubstringOfMethodCallExpr"); 
      var endsWithMethodCallExpr = new NonTerminal("EndsWithMethodCallExpr"); 
      var startsWithMethodCallExpr = new NonTerminal("StartsWithMethodCallExpr");

      // int
      var lengthMethodCallExpr = new NonTerminal("LengthMethodCallExpr"); 
      var indexOfMethodCallExpr = new NonTerminal("IndexOfMethodCallExpr"); 

      // string
      var replaceMethodCallExpr = new NonTerminal("ReplaceMethodCallExpr"); 
      var substring1MethodCallExpr = new NonTerminal("Substring1MethodCallExpr"); 
      var substring2MethodCallExpr = new NonTerminal("Substring2MethodCallExpr"); 
      var toLowerMethodCallExpr = new NonTerminal("ToLowerMethodCallExpr"); 
      var toUpperMethodCallExpr = new NonTerminal("ToUpperMethodCallExpr"); 
      var trimMethodCallExpr = new NonTerminal("TrimMethodCallExpr"); 
      var concatMethodCallExpr = new NonTerminal("ConcatMethodCallExpr"); 

      // date
      var secondMethodCallExpr = new NonTerminal("SecondMethodCallExpr"); 
      var minuteMethodCallExpr = new NonTerminal("MinuteMethodCallExpr"); 
      var hourMethodCallExpr = new NonTerminal("HourMethodCallExpr"); 
      var dayMethodCallExpr = new NonTerminal("DayMethodCallExpr"); 
      var monthMethodCallExpr = new NonTerminal("MonthMethodCallExpr"); 
      var yearMethodCallExpr = new NonTerminal("YearMethodCallExpr"); 

      // math
      var roundMethodCallExpr = new NonTerminal("RoundMethodCallExpr"); 
      var floorMethodCallExpr = new NonTerminal("FloorMethodCallExpr"); 
      var ceilingMethodCallExpr = new NonTerminal("CeilingMethodCallExpr"); 

      // type
      var isOf1MethodCallExpr = new NonTerminal("isOf1MethodCallExpr"); 
      var isOf2MethodCallExpr = new NonTerminal("isOf2MethodCallExpr"); 

      #endregion

      Root = baseExpr;
      baseExpr.Rule = parenExpr
                    | literalExpr
                    | memberExpr
                    | methodCallExpr
                    | binaryExpr
                    | unaryExpr;

   
      parenExpr.Rule = "(" + baseExpr + ")";
      literalExpr.Rule = stringLiteral
                        | numberLiteral
                        // | constant
                        | trueLiteral | falseLiteral | nullLiteral
                        | dateTimeLiteral
                        | dateTimeOffsetLiteral
                        | timeLiteral
                        | guidLiteral;

      subExpr.Rule = simpleIdentifier | anyMethodCallExpr | allMethodCallExpr;

      KeyTerm backSlash = ToTerm("/", "backslash");
      compositeIdentifier.Rule = MakePlusRule(compositeIdentifier, backSlash, subExpr);
      memberExpr.Rule =  compositeIdentifier;

      lambdaVariable.Rule = simpleIdentifier + ":";
      lambdaExpr.Rule = lambdaVariable + baseExpr;

      unaryExpr.Rule = unaryExprOp + baseExpr;
      unaryExprOp.Rule = (BnfExpression) "not" | "-";

      binaryExpr.Rule = baseExpr + binaryExprOp + baseExpr;
      binaryExprOp.Rule = (BnfExpression)"eq" | "ne" | "lt" | "le" | "gt" | "ge" | "add" | "sub" | "mul" | "div" | "mod" | "and" | "or";

      methodCallExpr.Rule = anyMethodCallExpr
                          | allMethodCallExpr
                          | replaceMethodCallExpr
                          | substring1MethodCallExpr
                          | substring2MethodCallExpr
                          | toLowerMethodCallExpr
                          | toUpperMethodCallExpr
                          | trimMethodCallExpr
                          | concatMethodCallExpr
                          | lengthMethodCallExpr
                          | indexOfMethodCallExpr
                          | secondMethodCallExpr
                          | minuteMethodCallExpr
                          | hourMethodCallExpr
                          | dayMethodCallExpr
                          | monthMethodCallExpr
                          | yearMethodCallExpr
                          | roundMethodCallExpr
                          | floorMethodCallExpr
                          | ceilingMethodCallExpr
                          | isOf1MethodCallExpr
                          | isOf2MethodCallExpr
                          | substringOfMethodCallExpr
                          | startsWithMethodCallExpr
                          | endsWithMethodCallExpr;

      anyMethodCallExpr.Rule = (BnfExpression) "any" + "(" + lambdaExpr + ")";
      allMethodCallExpr.Rule = (BnfExpression) "all" + "(" + lambdaExpr + ")";
      replaceMethodCallExpr.Rule = (BnfExpression)"replace" + "(" + baseExpr + "," + baseExpr + "," + baseExpr + ")";
      substring1MethodCallExpr.Rule = (BnfExpression)"substring" + "(" + baseExpr + "," + baseExpr + ")";
      substring2MethodCallExpr.Rule = (BnfExpression)"substring" + "(" + baseExpr + "," + baseExpr + "," + baseExpr + ")";
      toLowerMethodCallExpr.Rule = (BnfExpression)"tolower" + "(" + baseExpr + ")";
      toUpperMethodCallExpr.Rule = (BnfExpression)"toupper" + "(" + baseExpr + ")";
      trimMethodCallExpr.Rule = (BnfExpression)"trim" + "(" + baseExpr + ")";
      concatMethodCallExpr.Rule = (BnfExpression)"concat" + "(" + baseExpr + "," + baseExpr + ")";

      lengthMethodCallExpr.Rule = (BnfExpression)"length" + "(" + baseExpr + ")";
      indexOfMethodCallExpr.Rule = (BnfExpression)"indexof" + "(" + baseExpr + "," + baseExpr + ")";

      substringOfMethodCallExpr.Rule = (BnfExpression)"substringof" + "(" + baseExpr + "," + baseExpr + ")";
      startsWithMethodCallExpr.Rule = (BnfExpression)"startswith" + "(" + baseExpr + "," + baseExpr + ")";
      endsWithMethodCallExpr.Rule = (BnfExpression)"endswith" + "(" + baseExpr + "," + baseExpr + ")";

      secondMethodCallExpr.Rule = (BnfExpression)"second" + "(" + baseExpr + ")";
      minuteMethodCallExpr.Rule = (BnfExpression)"minute" + "(" + baseExpr + ")";
      hourMethodCallExpr.Rule = (BnfExpression)"hour" + "(" + baseExpr + ")";
      dayMethodCallExpr.Rule = (BnfExpression)"day" + "(" + baseExpr + ")";
      monthMethodCallExpr.Rule = (BnfExpression)"month" + "(" + baseExpr + ")";
      yearMethodCallExpr.Rule = (BnfExpression)"year" + "(" + baseExpr + ")";

      roundMethodCallExpr.Rule = (BnfExpression)"round" + "(" + baseExpr + ")";
      ceilingMethodCallExpr.Rule = (BnfExpression)"ceiling" + "(" + baseExpr + ")";
      floorMethodCallExpr.Rule = (BnfExpression)"floor" + "(" + baseExpr + ")";

      isOf1MethodCallExpr.Rule = (BnfExpression)"isof" + "(" + baseExpr + ")";
      isOf2MethodCallExpr.Rule = (BnfExpression)"isof" + "(" + baseExpr + "," + baseExpr + ")";

      
      RegisterOperators(10, "or");
      RegisterOperators(20, "and");
      RegisterOperators(30, "eq", "ne", "lt", "le", "gt", "ge");
      RegisterOperators(40, "add", "sub", "mul", "div", "mod");
      RegisterOperators(50, "-");

      RegisterBracePair("(", ")");

      MarkPunctuation(",", "(", ")" , "/", ":");

      MarkTransient(baseExpr);
      MarkTransient(binaryExprOp);
      MarkTransient(unaryExprOp);

      MarkTransient(parenExpr);
      MarkTransient(methodCallExpr);
      MarkTransient(literalExpr);
      MarkTransient(subExpr);
      MarkTransient(lambdaVariable);

    }
  }
}

