/*
 * 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.
 */

#include "postgres.h"

#include "parser/scansup.h"

#include "parser/cypher_gram.h"
#include "parser/cypher_keywords.h"
#include "parser/cypher_parser.h"

int cypher_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, ag_scanner_t scanner)
{
    /*
     * This list must match ag_token_type.
     * 0 means end-of-input.
     */
    const int type_map[] = {
        0,
        INTEGER,
        DECIMAL,
        STRING,
        IDENTIFIER,
        PARAMETER,
        NOT_EQ,
        LT_EQ,
        GT_EQ,
        DOT_DOT,
        TYPECAST,
        PLUS_EQ,
        CHAR,
        BQIDENT,
        OP
    };

    ag_token token;

    token = ag_scanner_next_token(scanner);

    switch (token.type)
    {
    case AG_TOKEN_NULL:
        break;
    case AG_TOKEN_INTEGER:
        lvalp->integer = token.value.i;
        break;
    case AG_TOKEN_DECIMAL:
    case AG_TOKEN_STRING:
    case AG_TOKEN_OP:
        lvalp->string = pstrdup(token.value.s);
        break;
    case AG_TOKEN_IDENTIFIER:
    {
        int kwnum;
        char *ident;

        kwnum = ScanKeywordLookup(token.value.s, &CypherKeyword);
        if (kwnum >= 0)
        {
            /*
             * use token.value.s instead of keyword->name to preserve
             * case sensitivity
             */
            lvalp->keyword = GetScanKeyword(kwnum, &CypherKeyword);
            ident = pstrdup(token.value.s);
            truncate_identifier(ident, strlen(ident), true);
            lvalp->string = ident;
            *llocp = token.location;
            return CypherKeywordTokens[kwnum];
        }

        ident = pstrdup(token.value.s);
        truncate_identifier(ident, strlen(ident), true);
        lvalp->string = ident;
        break;
    }
    case AG_TOKEN_BQIDENT:
    {
        char *ident;

        /* these are identifiers, just back ticked */
        token.type = AG_TOKEN_IDENTIFIER;

        ident = pstrdup(token.value.s);
        truncate_identifier(ident, strlen(ident), true);
        lvalp->string = ident;
        break;
    }
    case AG_TOKEN_PARAMETER:
        lvalp->string = pstrdup(token.value.s);
        break;
    case AG_TOKEN_LT_GT:
    case AG_TOKEN_LT_EQ:
    case AG_TOKEN_GT_EQ:
    case AG_TOKEN_DOT_DOT:
    case AG_TOKEN_PLUS_EQ:
    case AG_TOKEN_TYPECAST:
        break;
    case AG_TOKEN_CHAR:
        *llocp = token.location;
        return token.value.c;
    default:
        ereport(ERROR, (errmsg("unexpected ag_token_type: %d", token.type)));
        break;
    }

    *llocp = token.location;
    return type_map[token.type];
}

void cypher_yyerror(YYLTYPE *llocp, ag_scanner_t scanner,
                    cypher_yy_extra *extra, const char *msg)
{
    ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
                    ag_scanner_errmsg(msg, scanner),
                    ag_scanner_errposition(*llocp, scanner)));
}

/* declaration to make mac os x compiler happy */
int cypher_yyparse(ag_scanner_t scanner, cypher_yy_extra *extra);

List *parse_cypher(const char *s)
{
    ag_scanner_t scanner;
    cypher_yy_extra extra;
    int yyresult;

    scanner = ag_scanner_create(s);
    extra.result = NIL;
    extra.extra = NULL;

    yyresult = cypher_yyparse(scanner, &extra);

    ag_scanner_destroy(scanner);

    /*
     * cypher_yyparse() returns 0 if parsing was successful.
     * Otherwise, it returns 1 (invalid input) or 2 (memory exhaustion).
     */
    if (yyresult)
        return NIL;

    /*
     * Append the extra node regardless of its value. Currently the extra
     * node is only used by EXPLAIN
    */
    return lappend(extra.result, extra.extra);
}
