/*******************************************************************************
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 ******************************************************************************/
package org.apache.olingo.odata2.core.uri.expression;

import org.apache.olingo.odata2.api.edm.EdmEntityType;
import org.apache.olingo.odata2.api.uri.expression.ExpressionKind;
import org.apache.olingo.odata2.api.uri.expression.ExpressionParserException;
import org.junit.Test;

/**
 *  
 */
public class TestParserExceptions extends TestBase {

  @Test
  public void testOPMparseOrderByString() {
    EdmEntityType edmEtAllTypes = edmInfo.getTypeEtAllTypes();

    // CASE 1
    // http://services.odata.org/Northwind/Northwind.svc/Products/?$orderby=UnitPrice%20ascc
    // -->Syntax error at position 10.
    GetPTO(edmEtAllTypes, "String ascc").aExMsgText(
        "Invalid sort order in OData orderby parser at position 8 in \"String ascc\".");

    // CASE 2
    // http://services.odata.org/Northwind/Northwind.svc/Products/?$orderby=UnitPrice%20asc,
    // -->Expression expected at position 12.
    GetPTO(edmEtAllTypes, "String asc,").aExMsgText("Expression expected after position 11 in \"String asc,\".");

    // CASE 3
    // http://services.odata.org/Northwind/Northwind.svc/Products/?$orderby=UnitPrice%20asc%20d
    // -->Syntax error at position 14.
    GetPTO(edmEtAllTypes, "String asc a").aExMsgText(
        "Comma or end of expression expected at position 12 in \"String asc a\".");

    // CASE 4
    // http://services.odata.org/Northwind/Northwind.svc/Products/?$orderby=UnitPrice b
    // -->Syntax error at position 10.
    GetPTO(edmEtAllTypes, "String b").aExMsgText(
        "Invalid sort order in OData orderby parser at position 8 in \"String b\".");

    // CASE 5
    // http://services.odata.org/Northwind/Northwind.svc/Products/?$orderby=UnitPrice, UnitPrice b
    // -->Syntax error at position 21.
    GetPTO(edmEtAllTypes, "String, String b").aExMsgText(
        "Invalid sort order in OData orderby parser at position 16 in \"String, String b\".");

    // CASE 6
    // http://services.odata.org/Northwind/Northwind.svc/Products/?$orderby=UnitPrice a, UnitPrice desc
    // -->Syntax error at position 10.
    GetPTO(edmEtAllTypes, "String a, String desc").aExMsgText(
        "Invalid sort order in OData orderby parser at position 8 in \"String a, String desc\".");

    // CASE 7
    // http://services.odata.org/Northwind/Northwind.svc/Products/?$orderby=UnitPrice asc, UnitPrice b
    // -->Syntax error at position 25.
    GetPTO(edmEtAllTypes, "String asc, String b").aExMsgText(
        "Invalid sort order in OData orderby parser at position 20 in \"String asc, String b\".");

  }

  @Test
  public void testPMvalidateEdmProperty() {
    EdmEntityType edmEtAllTypes = edmInfo.getTypeEtAllTypes();

    // OK
    GetPTF(edmEtAllTypes, "'text' eq String").aKind(ExpressionKind.BINARY).aSerialized("{'text' eq String}");

    // CASE 1
    // http://services.odata.org/Northwind/Northwind.svc/Products/?$filter=NotAProperty
    // -->No property 'NotAProperty' exists in type 'ODataWeb.Northwind.Model.Product' at position 10.
    GetPTF(edmEtAllTypes, "NotAProperty").aExMsgText(
        "No property \"NotAProperty\" exists in type \"TecRefScenario.EtAllTypes\" at position 1 in \"NotAProperty\".");

    // CASE 2
    // http://services.odata.org/Northwind/Northwind.svc/Products/?$filter='text'%20eq%20NotAProperty
    // -->No property 'NotAProperty' exists in type 'ODataWeb.Northwind.Model.Product' at position 10.
    GetPTF(edmEtAllTypes, "'text' eq NotAProperty")
        .aExMsgText(
            "No property \"NotAProperty\" exists in type \"TecRefScenario.EtAllTypes\" at" +
                " position 11 in \"'text' eq NotAProperty\".");

    // CASE 3
    GetPTF(edmEtAllTypes, "Complex/NotAProperty")
        .aExMsgText(
            "No property \"NotAProperty\" exists in type \"TecRefScenario.CtAllTypes\" at" +
                " position 9 in \"Complex/NotAProperty\".");

    // CASE 4
    GetPTF(edmEtAllTypes, "'text' eq Complex/NotAProperty")
        .aExMsgText(
            "No property \"NotAProperty\" exists in type \"TecRefScenario.CtAllTypes\" at " +
                "position 19 in \"'text' eq Complex/NotAProperty\".");

    // CASE 5
    GetPTF(edmEtAllTypes, "String/NotAProperty").aExMsgText(
        "No property \"NotAProperty\" exists in type \"Edm.String\" at position 8 in \"String/NotAProperty\".");

    // CASE 6
    // http://services.odata.org/Northwind/Northwind.svc/Products/?$filter='aText'/NotAProperty
    // -->Exception Stack
    GetPTF(edmEtAllTypes, "'aText'/NotAProperty").aExMsgText(
        "Leftside of method operator at position 8 is not a property in \"'aText'/NotAProperty\".");

    // CASE 7
    // http://services.odata.org/Northwind/Northwind.svc/Products/?$filter='Hong Kong' eq ProductName/city
    // --> No property 'city' exists in type 'System.String' at position 27.
    GetPTF(edmEtAllTypes, "'Hong Kong' eq DateTime/city").aExMsgText(
        "No property \"city\" exists in type \"Edm.DateTime\" at position 25 in \"'Hong Kong' eq DateTime/city\".");

    // CASE 8
    // http://services.odata.org/Northwind/Northwind.svc/Products/?$filter='Hong Kong' eq ProductName/city
    // --> No property 'city' exists in type 'System.String' at position 27.
    GetPTF(edmEtAllTypes, "'Hong Kong' eq String/city").aExMsgText(
        "No property \"city\" exists in type \"Edm.String\" at position 23 in \"'Hong Kong' eq String/city\".");

  }

  @Test
  public void testPMreadParameters() {

    // OK
    GetPTF("concat('A','B')").aSerialized("{concat('A','B')}");

    // CASE 12
    // http://services.odata.org/Northwind/Northwind.svc/Products(1)/Supplier?$filter=startswith()
    // -->No applicable function found for 'startswith' at position 0 with the specified arguments. The functions
    // considered are: startswith(System.String, System.String).
    GetPTF("startswith()")
        .aExType(ExpressionParserException.class)
        .aExMsgText(
            "No applicable method found for \"startswith\" at position 1 in \"startswith()\" with the specified " +
                "arguments. Method \"startswith\" requires exact 2 argument(s).");

    // CASE 13
    // http://services.odata.org/Northwind/Northwind.svc/Products(1)/Supplier?$filter=startswith('A')
    // -->No applicable function found for 'startswith' at position 0 with the specified arguments. The functions
    // considered are: startswith(System.String, System.String).
    GetPTF("startswith('A')")
        .aExType(ExpressionParserException.class)
        .aExMsgText(
            "No applicable method found for \"startswith\" at position 1 in \"startswith('A')\" with the specified " +
                "arguments. Method \"startswith\" requires exact 2 argument(s).");

    // CASE 14
    // http://services.odata.org/Northwind/Northwind.svc/Products(1)/Supplier?$filter=startswith('A','B')
    // -->Resource not found for the segment 'Supplier'.
    GetPTF("startswith('A','B')").aSerialized("{startswith('A','B')}");

    // CASE 15
    // http://services.odata.org/Northwind/Northwind.svc/Products(1)/Supplier?$filter=startswith('A','B','C')
    // -->No applicable function found for 'startswith' at position 0 with the specified arguments. The functions
    // considered are: startswith(System.String, System.String).
    GetPTF("startswith('A','B','C')")
        .aExType(ExpressionParserException.class)
        .aExMsgText(
            "No applicable method found for \"startswith\" at position 1 in \"startswith('A','B','C')\" with the" +
                " specified arguments. Method \"startswith\" requires exact 2 argument(s).");

    // CASE 16
    GetPTF("concat()")
        .aExMsgText(
            "No applicable method found for \"concat\" at position 1 in \"concat()\" with the specified arguments." +
                " Method \"concat\" requires 2 or more arguments.");
    // CASE 17
    GetPTF("concat('A')")
        .aExMsgText(
            "No applicable method found for \"concat\" at position 1 in \"concat('A')\" with the specified " +
                "arguments. Method \"concat\" requires 2 or more arguments.");
    // CASE 18
    GetPTF("concat('A','B')").aSerialized("{concat('A','B')}");
    // CASE 19
    GetPTF("concat('A','B','C')").aSerialized("{concat('A','B','C')}");

    // CASE 20
    GetPTF("'A' and concat('A')")
        .aExMsgText(
            "No applicable method found for \"concat\" at position 9 in \"'A' and concat('A')\" with the specified " +
                "arguments. Method \"concat\" requires 2 or more arguments.");

    // CASE 1
    // http://services.odata.org/Northwind/Northwind.svc/Products(1)/Supplier?$filter=concat(
    // -->Expression expected at position 7.
    GetPTF("concat(").aExType(ExpressionParserException.class).aExMsgText(
        "Expression expected after position 7 in \"concat(\".");

    // CASE 2
    // http://services.odata.org/Northwind/Northwind.svc/Products(1)/Supplier?$filter=concat(123
    // -->')' or ',' expected at position 10.
    GetPTF("concat(123").aExType(ExpressionParserException.class).aExMsgText(
        "\")\" or \",\" expected after position 10 in \"concat(123\".");
    // .aExMsgText("Missing closing parenthesis \")\" for opening parenthesis \"(\" at position 7 in \"concat(\".");

    // CASE 3
    // http://services.odata.org/Northwind/Northwind.svc/Products(1)/Supplier?$filter=concat(,
    // Expression expected at position 7.
    GetPTF("concat(,").aExType(ExpressionParserException.class).aExMsgText(
        "Expression expected at position 8 in \"concat(,\".");

    // CASE 4
    // http://services.odata.org/Northwind/Northwind.svc/Products(1)/Supplier?$filter=concat(123,
    // -->Expression expected at position 11.
    GetPTF("concat(123,").aExType(ExpressionParserException.class).aExMsgText(
        "Expression expected after position 11 in \"concat(123,\".");

    // CASE 5
    // min = -1, max = -1,
    GetPTF("testingMINMAX1()").aSerialized("{concat()}");
    GetPTF("testingMINMAX1('A')").aSerialized("{concat('A')}");
    GetPTF("testingMINMAX1('A','B')").aSerialized("{concat('A','B')}");
    GetPTF("testingMINMAX1('A','B','C')").aSerialized("{concat('A','B','C')}");

    // CASE 6
    // min = 0, max = -1,
    GetPTF("testingMINMAX2()").aSerialized("{concat()}");
    GetPTF("testingMINMAX2('A')").aSerialized("{concat('A')}");
    GetPTF("testingMINMAX2('A','B')").aSerialized("{concat('A','B')}");
    GetPTF("testingMINMAX2('A','B','C')").aSerialized("{concat('A','B','C')}");

    // CASE 7
    // min = 2, max = -1,
    GetPTF("testingMINMAX3()")
        .aExType(ExpressionParserException.class)
        .aExMsgText(
            "No applicable method found for \"concat\" at position 1 in \"testingMINMAX3()\" with the specified " +
                "arguments. Method \"concat\" requires 2 or more arguments.");
    GetPTF("testingMINMAX3('A')")
        .aExType(ExpressionParserException.class)
        .aExMsgText(
            "No applicable method found for \"concat\" at position 1 in \"testingMINMAX3('A')\" with the specified " +
                "arguments. Method \"concat\" requires 2 or more arguments.");
    GetPTF("testingMINMAX3('A','B')").aSerialized("{concat('A','B')}");
    GetPTF("testingMINMAX3('A','B','C')").aSerialized("{concat('A','B','C')}");

    // CASE 8
    // min =-1, max = 0,
    GetPTF("testingMINMAX4()").aSerialized("{concat()}");
    GetPTF("testingMINMAX4('A')")
        .aExType(ExpressionParserException.class)
        .aExMsgText(
            "No applicable method found for \"concat\" at position 1 in \"testingMINMAX4('A')\" with the specified " +
                "arguments. Method \"concat\" requires maximal 0 arguments.");
    GetPTF("testingMINMAX4('A','B')")
        .aExType(ExpressionParserException.class)
        .aExMsgText(
            "No applicable method found for \"concat\" at position 1 in \"testingMINMAX4('A','B')\" with the " +
                "specified arguments. Method \"concat\" requires maximal 0 arguments.");
    GetPTF("testingMINMAX4('A','B','C')")
        .aExType(ExpressionParserException.class)
        .aExMsgText(
            "No applicable method found for \"concat\" at position 1 in \"testingMINMAX4('A','B','C')\" with the " +
                "specified arguments. Method \"concat\" requires maximal 0 arguments.");

    // CASE 9
    // min =-1, max = 2,
    GetPTF("testingMINMAX5()").aSerialized("{concat()}");
    GetPTF("testingMINMAX5('A')").aSerialized("{concat('A')}");
    GetPTF("testingMINMAX5('A','B')").aSerialized("{concat('A','B')}");
    GetPTF("testingMINMAX5('A','B','C')")
        .aExType(ExpressionParserException.class)
        .aExMsgText(
            "No applicable method found for \"concat\" at position 1 in \"testingMINMAX5('A','B','C')\" with the " +
                "specified arguments. Method \"concat\" requires maximal 2 arguments.");

    // CASE 10
    // min =1, max = 2,
    GetPTF("testingMINMAX6()")
        .aExType(ExpressionParserException.class)
        .aExMsgText(
            "No applicable method found for \"concat\" at position 1 in \"testingMINMAX6()\" with the specified " +
                "arguments. Method \"concat\" requires between 1 and 2 arguments.");
    GetPTF("testingMINMAX6('A')").aSerialized("{concat('A')}");
    GetPTF("testingMINMAX6('A','B')").aSerialized("{concat('A','B')}");
    GetPTF("testingMINMAX6('A','B','C')")
        .aExType(ExpressionParserException.class)
        .aExMsgText(
            "No applicable method found for \"concat\" at position 1 in \"testingMINMAX6('A','B','C')\" with the " +
                "specified arguments. Method \"concat\" requires between 1 and 2 arguments.");

    // CASE 11
    // min =1, max = 2,
    GetPTF("testingMINMAX7()")
        .aExType(ExpressionParserException.class)
        .aExMsgText(
            "No applicable method found for \"concat\" at position 1 in \"testingMINMAX7()\" with the specified " +
                "arguments. Method \"concat\" requires exact 1 argument(s).");
    GetPTF("testingMINMAX7('A')").aSerialized("{concat('A')}");
    GetPTF("testingMINMAX7('A','B')")
        .aExType(ExpressionParserException.class)
        .aExMsgText(
            "No applicable method found for \"concat\" at position 1 in \"testingMINMAX7('A','B')\" with the " +
                "specified arguments. Method \"concat\" requires exact 1 argument(s).");
    GetPTF("testingMINMAX7('A','B','C')")
        .aExType(ExpressionParserException.class)
        .aExMsgText(
            "No applicable method found for \"concat\" at position 1 in \"testingMINMAX7('A','B','C')\" with " +
                "the specified arguments. Method \"concat\" requires exact 1 argument(s).");

    // CASE 12
    // http://services.odata.org/Northwind/Northwind.svc/Products(1)/Supplier?$filter=concat('a' 'b')
    // -->')' or ',' expected at position 11.
    GetPTF("concat('a' 'b')").aExMsgText("\")\" or \",\" expected after position 10 in \"concat('a' 'b')\".");

  }

  @Test
  public void testPMreadParenthesis() {
    // http://services.odata.org/Northwind/Northwind.svc/Products(1)/Supplier?$filter=(123
    // -->')' or operator expected at position 4.
    GetPTF("(123").aExType(ExpressionParserException.class).aExMsgText(
        "Missing closing parenthesis \")\" for opening parenthesis \"(\" at position 1 in \"(123\".");

    // http://services.odata.org/Northwind/Northwind.svc/Products(1)/Supplier?$filter=(123 add (456
    // -->')' or operator expected at position 13.
    GetPTF("(123 add (456").aExType(ExpressionParserException.class).aExMsgText(
        "Missing closing parenthesis \")\" for opening parenthesis \"(\" at position 10 in \"(123 add (456\".");

  }

  @Test
  public void testPMvalidateBinaryOperator() {/* PM = Parsermethod */
    // http://services.odata.org/Northwind/Northwind.svc/Products(1)/Supplier?$filter=123 add 'abc'
    // -->Operator 'add' incompatible with operand types 'System.Int32' and 'System.String' at position 4.
    GetPTF("123 add 'abc'")
        .aExType(ExpressionParserException.class)
        .aExKey(ExpressionParserException.INVALID_TYPES_FOR_BINARY_OPERATOR)
        .aExMsgText(
            "Operator \"add\" incompatible with operand types \"System.Uint7\" and \"Edm.String\" at " +
                "position 5 in \"123 add 'abc'\".");
  }

  @Test
  public void testPMvalidateMethodTypes() /* PM = Parsermethod */{
    // CASE 1
    // http://services.odata.org/Northwind/Northwind.svc/Products/?$filter=year(327686)
    // -->No applicable function found for 'year' at position 0 with the specified arguments. The functions considered
    // are: year(System.DateTime).
    String aInt32 = "327686";
    GetPTF("year(" + aInt32 + ")").aExMsgText(
        "No applicable method found for \"year\" at position 1 in \"year(327686)\" for the specified argument types.");
  }

  @Test
  public void testPMparseFilterString() { /* PM = Parsermethod */

    // http://services.odata.org/Northwind/Northwind.svc/Products(1)/Supplier?$filter='123
    // -->Unterminated string literal at position 4 in ''123'.
    GetPTF("'123").aExType(ExpressionParserException.class).aExKey(
        ExpressionParserException.TOKEN_UNDETERMINATED_STRING).aExMsgText(
        "Unterminated string literal at position 1 in \"'123\".");

    // http://services.odata.org/Northwind/Northwind.svc/Products(1)/Supplier?$filter=1%20add%20'123
    // -->Unterminated string literal at position 10 in '1 add '123'.
    GetPTF("1 add '123").aExType(ExpressionParserException.class).aExKey(
        ExpressionParserException.TOKEN_UNDETERMINATED_STRING).aExMsgText(
        "Unterminated string literal at position 7 in \"1 add '123\".");

    // http://services.odata.org/Northwind/Northwind.svc/Products(1)/Supplier?$filter=datetime'123
    // -->Unterminated literal at position 12 in 'datetime'123'.
    GetPTF("datetime'123").aExType(ExpressionParserException.class).aExKey(
        ExpressionParserException.TOKEN_UNDETERMINATED_STRING).aExMsgText(
        "Unterminated string literal at position 9 in \"datetime'123\".");

    // http://services.odata.org/Northwind/Northwind.svc/Products(1)/Supplier?$filter=123%20456
    // -->Expression of type 'System.Boolean' expected at position 0.
    GetPTF("123 456").aExType(ExpressionParserException.class).aExKey(
        ExpressionParserException.INVALID_TRAILING_TOKEN_DETECTED_AFTER_PARSING).aExMsgText(
        "Invalid token \"456\" detected after parsing at position 5 in \"123 456\".");

    // http://services.odata.org/Northwind/Northwind.svc/Products(1)/Supplier?$filter=123%20456
    // -->Expression of type 'System.Boolean' expected at position 0.
    GetPTF("123 456 789").aExType(ExpressionParserException.class).aExKey(
        ExpressionParserException.INVALID_TRAILING_TOKEN_DETECTED_AFTER_PARSING).aExMsgText(
        "Invalid token \"456\" detected after parsing at position 5 in \"123 456 789\".");

    // http://services.odata.org/Northwind/Northwind.svc/Products(1)/Supplier?$filter=abc%20abc
    // -->No property 'abc' exists in type 'ODataWeb.Northwind.Model.Supplier' at position 0.
    GetPTF("abc abc").aExType(ExpressionParserException.class).aExKey(
        ExpressionParserException.INVALID_TRAILING_TOKEN_DETECTED_AFTER_PARSING).aExMsgText(
        "Invalid token \"abc\" detected after parsing at position 5 in \"abc abc\".");
  }

  @Test
  public void testAdditionalStuff() {

    GetPTF("( A mul B )/X eq TEST").aSerialized("{{{A mul B}/X} eq TEST}");

    GetPTF("( 1 mul 2 )/X eq TEST").aSerialized("{{{1 mul 2}/X} eq TEST}");

    // CASE 1
    EdmEntityType edmEtAllTypes = edmInfo.getTypeEtAllTypes();
    GetPTF(edmEtAllTypes, "( 1 mul 2 )/X eq TEST").aExMsgText(
        "Leftside of method operator at position 12 is not a property in \"( 1 mul 2 )/X eq TEST\".");

    // CASE 2
    // http://services.odata.org/Northwind/Northwind.svc/Products/?$filter=notsupportedfunction('a')
    // -->Unknown function 'notsupportedfunction' at position 0.
    GetPTF("notsupportedfunction('a')").aExMsgText(
        "Unknown function \"notsupportedfunction\" at position 1 in \"notsupportedfunction('a')\".");

    // CASE 3
    // http://services.odata.org/Northwind/Northwind.svc/Products/?$filter=notsupportedfunction('a')
    // -->Unknown function 'notsupportedfunction' at position 0.
    GetPTF("notsupportedfunction    ('a')").aExMsgText(
        "Unknown function \"notsupportedfunction\" at position 1 in \"notsupportedfunction    ('a')\".");

    // CASE 4
    // Use \ instead of /
    GetPTF("Address\\NotAProperty").aExMsgText(
        "Error while tokenizing a ODATA expression on token \"\\\" at position 8 in \"Address\\NotAProperty\".");

    // CASE 5
    GetPTF("-(-(- 2d)))").aExMsgText("Invalid token \")\" detected after parsing at position 11 in \"-(-(- 2d)))\".");

    // CASE 6
    GetPTF("a b").aExMsgText("Invalid token \"b\" detected after parsing at position 3 in \"a b\".");

    // CASE 7
    GetPTF("a eq b b").aExMsgText("Invalid token \"b\" detected after parsing at position 8 in \"a eq b b\".");

    // CASE 8
    GetPTF(edmEtAllTypes, "year(Complex)").aExMsgText(
        "No applicable method found for \"year\" at position 1 in \"year(Complex)\" for the specified argument types.");

    // CASE 9
    // http://services.odata.org/Northwind/Northwind.svc/Products(1)/Supplier?$filter=1%20add%202
    // -->Expression of type 'System.Boolean' expected at position 0.
    GetPTF_onlyBinary("1 add 2").aExMsgText(
        "Expression of type \"Edm.Boolean\" expected at position 1 in \"1 add 2\" (actual type is \"Edm.SByte\").");

    // CASE 10
    // http://services.odata.org/Northwind/Northwind.svc/Products(1)/Supplier?$filter=1%20add
    // -->Expression expected at position 5.
    GetPTF_onlyBinary("1 add").aExMsgText("Expression expected after position 5 in \"1 add\".");

    // CASE 11
    // http://services.odata.org/Northwind/Northwind.svc/Products(1)/Supplier?$filter=1%20add
    // -->Expression expected at position 5.
    GetPTF_onlyBinary("1 add   ").aExMsgText("Expression expected after position 5 in \"1 add   \".");
  }

}
