/*
 * 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 "access/sysattr.h"
#include "catalog/pg_type.h"
#include "nodes/parsenodes.h"
#include "nodes/primnodes.h"
#include "nodes/relation.h"
#include "optimizer/pathnode.h"
#include "optimizer/paths.h"

#include "optimizer/cypher_pathnode.h"
#include "optimizer/cypher_paths.h"
#include "utils/ag_func.h"

typedef enum cypher_clause_kind
{
    CYPHER_CLAUSE_NONE,
    CYPHER_CLAUSE_CREATE,
    CYPHER_CLAUSE_SET,
    CYPHER_CLAUSE_DELETE,
    CYPHER_CLAUSE_MERGE,
    CYPHER_CLAUSE_VLE
} cypher_clause_kind;

static set_rel_pathlist_hook_type prev_set_rel_pathlist_hook;

static void set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti,
                             RangeTblEntry *rte);
static cypher_clause_kind get_cypher_clause_kind(RangeTblEntry *rte);
static void handle_cypher_create_clause(PlannerInfo *root, RelOptInfo *rel,
                                        Index rti, RangeTblEntry *rte);
static void handle_cypher_set_clause(PlannerInfo *root, RelOptInfo *rel,
                                     Index rti, RangeTblEntry *rte);
static void handle_cypher_delete_clause(PlannerInfo *root, RelOptInfo *rel,
                                        Index rti, RangeTblEntry *rte);
static void handle_cypher_merge_clause(PlannerInfo *root, RelOptInfo *rel,
                                        Index rti, RangeTblEntry *rte);
static void handle_cypher_vle_clause(PlannerInfo *root, RelOptInfo *rel,
                                        Index rti, RangeTblEntry *rte);

void set_rel_pathlist_init(void)
{
    prev_set_rel_pathlist_hook = set_rel_pathlist_hook;
    set_rel_pathlist_hook = set_rel_pathlist;
}

void set_rel_pathlist_fini(void)
{
    set_rel_pathlist_hook = prev_set_rel_pathlist_hook;
}

static void set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti,
                             RangeTblEntry *rte)
{
    if (prev_set_rel_pathlist_hook)
        prev_set_rel_pathlist_hook(root, rel, rti, rte);

    switch (get_cypher_clause_kind(rte))
    {
    case CYPHER_CLAUSE_CREATE:
        handle_cypher_create_clause(root, rel, rti, rte);
        break;
    case CYPHER_CLAUSE_SET:
        handle_cypher_set_clause(root, rel, rti, rte);
        break;
    case CYPHER_CLAUSE_DELETE:
        handle_cypher_delete_clause(root, rel, rti, rte);
        break;
    case CYPHER_CLAUSE_MERGE:
        handle_cypher_merge_clause(root, rel, rti, rte);
        break;
    case CYPHER_CLAUSE_VLE:
        handle_cypher_vle_clause(root, rel, rti, rte);
        break;
    case CYPHER_CLAUSE_NONE:
        break;
    default:
        ereport(ERROR, (errmsg_internal("invalid cypher_clause_kind")));
    }
}

/*
 * Check to see if the rte is a Cypher clause. An rte is only a Cypher clause
 * if it is a subquery, with the last entry in its target list, that is a
 * FuncExpr.
 */
static cypher_clause_kind get_cypher_clause_kind(RangeTblEntry *rte)
{
    TargetEntry *te;
    FuncExpr *fe;

    // If it's not a subquery, it's not a Cypher clause.
    if (rte->rtekind != RTE_SUBQUERY)
        return CYPHER_CLAUSE_NONE;

    // Make sure the targetList isn't NULL. NULL means potential EXIST subclause
    if (rte->subquery->targetList == NULL)
        return CYPHER_CLAUSE_NONE;

    // A Cypher clause function is always the last entry.
    te = (TargetEntry*)llast(rte->subquery->targetList);

    // If the last entry is not a FuncExpr, it's not a Cypher clause.
    if (!IsA(te->expr, FuncExpr))
        return CYPHER_CLAUSE_NONE;

    fe = (FuncExpr *)te->expr;

    if (is_oid_ag_func(fe->funcid, CREATE_CLAUSE_FUNCTION_NAME))
        return CYPHER_CLAUSE_CREATE;
    if (is_oid_ag_func(fe->funcid, SET_CLAUSE_FUNCTION_NAME))
        return CYPHER_CLAUSE_SET;
    if (is_oid_ag_func(fe->funcid, DELETE_CLAUSE_FUNCTION_NAME))
        return CYPHER_CLAUSE_DELETE;
    if (is_oid_ag_func(fe->funcid, MERGE_CLAUSE_FUNCTION_NAME))
        return CYPHER_CLAUSE_MERGE;
    if (is_oid_ag_func(fe->funcid, VLE_CLAUSE_FUNCTION_NAME))
        return CYPHER_CLAUSE_VLE;
    else
        return CYPHER_CLAUSE_NONE;
}

// replace all possible paths with our CustomPath
static void handle_cypher_delete_clause(PlannerInfo *root, RelOptInfo *rel,
                                        Index rti, RangeTblEntry *rte)
{
    TargetEntry *te;
    FuncExpr *fe;
    List *custom_private;
    ExtensiblePath *cp;

    // Add the pattern to the CustomPath
    te = (TargetEntry *)llast(rte->subquery->targetList);
    fe = (FuncExpr *)te->expr;
    // pass the const that holds the data structure to the path.
    custom_private = fe->args;

    cp = create_cypher_delete_path(root, rel, custom_private);

    if (root->distinct_pathkeys) {
        rel->cheapest_startup_path = (Path *)cp;
        rel->cheapest_total_path = list_make1((Path *)cp);
        rel->cheapest_parameterized_paths =list_make1((Path *)cp);
    }

    // Discard any pre-existing paths
    rel->pathlist = NIL;

    add_path(root, rel, (Path *)cp);
}

/*
 * Take the paths possible for the RelOptInfo that represents our
 * _cypher_delete_clause function replace them with our delete clause
 * path. The original paths will be children to the new delete path.
 */
static void handle_cypher_create_clause(PlannerInfo *root, RelOptInfo *rel,
                                        Index rti, RangeTblEntry *rte)
{
    TargetEntry *te;
    FuncExpr *fe;
    List *custom_private;
    ExtensiblePath *cp;

    // Add the pattern to the CustomPath
    te = (TargetEntry *)llast(rte->subquery->targetList);
    fe = (FuncExpr *)te->expr;
    // pass the const that holds the data structure to the path.
    custom_private = fe->args;

    cp = create_cypher_create_path(root, rel, custom_private);

    // Discard any pre-existing paths, they should be under the cp path
    rel->pathlist = NIL;

    // Add the new path to the rel.
    add_path(root, rel, &(cp->path));
}

// replace all possible paths with our CustomPath
static void handle_cypher_set_clause(PlannerInfo *root, RelOptInfo *rel,
                                     Index rti, RangeTblEntry *rte)
{
    TargetEntry *te;
    FuncExpr *fe;
    List *custom_private;
    ExtensiblePath *cp;

    // Add the pattern to the CustomPath
    te = (TargetEntry *)llast(rte->subquery->targetList);
    fe = (FuncExpr *)te->expr;
    // pass the const that holds the data structure to the path.
    custom_private = fe->args;

    cp = create_cypher_set_path(root, rel, custom_private);

    // Discard any pre-existing paths
    rel->pathlist = NIL;

    add_path(root, rel, (Path *)cp);
}

// replace all possible paths with our CustomPath
static void handle_cypher_merge_clause(PlannerInfo *root, RelOptInfo *rel,
                                        Index rti, RangeTblEntry *rte)
{
    TargetEntry *te;
    FuncExpr *fe;
    List *custom_private;
    ExtensiblePath *cp;

    // Add the pattern to the CustomPath
    te = (TargetEntry *)llast(rte->subquery->targetList);
    fe = (FuncExpr *)te->expr;
    // pass the const that holds the data structure to the path.
    custom_private = fe->args;

    cp = create_cypher_merge_path(root, rel, custom_private);

    // Discard any pre-existing paths
    rel->pathlist = NIL;

    add_path(root, rel, (Path *)cp);
}

/*
 * Check that the specified List is valid (so far as we can tell).
 */
static void check_list_invariants(const List *list)
{
    if (list == NIL) {
        return;
    }

    Assert(list->length > 0);
    Assert(list->head != NULL);
    Assert(list->tail != NULL);

    Assert(list->type == T_List || list->type == T_IntList || list->type == T_OidList);

    if (list->length == 1) {
        Assert(list->head == list->tail);
    }
    if (list->length == 2) {
        Assert(list->head->next == list->tail);
    }
    Assert(list->tail->next == NULL);
}
static List *
list_delete_last(List *list)
{
	check_list_invariants(list);

	if (list == NIL)
		return NIL;				/* would an error be better? */

	/* list_truncate won't free list if it goes to empty, but this should */
	if (list_length(list) <= 1)
	{
		list_free(list);
		return NIL;
	}

	return list_truncate(list, list_length(list) - 1);
}
static void handle_cypher_vle_clause(PlannerInfo *root, RelOptInfo *rel,
                                        Index rti, RangeTblEntry *rte)
{
    TargetEntry *te;
    FuncExpr *fe;
    List *custom_private;
    ExtensiblePath *cp;

    // Add the pattern to the CustomPath
    te = (TargetEntry *)llast(rte->subquery->targetList);
    fe = (FuncExpr *)te->expr;
    // remove last target
    list_delete_last(rte->subquery->targetList);
    list_delete_last( rel-> subplan->targetlist);
  
    // pass the const that holds the data structure to the path.
    custom_private = fe->args;

    cp = create_cypher_vle_path(root, rel, custom_private);

    // Discard any pre-existing paths
    rel->pathlist = NIL;
    // the path must been chosen
    rel->cheapest_parameterized_paths =list_make1((Path *)cp);
    rel->cheapest_startup_path =  (Path *)cp;
    rel->cheapest_total_path= list_make1((Path *)cp);
    add_path(root, rel, (Path *)cp);
}