/*
 * 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 "utils/age_graphid_ds.h"

/* defines */
/*
 * A simple linked list node for graphid lists (int64). PG's implementation
 * has too much overhead for this type of list as it only directly supports
 * regular ints, not int64s, of which a graphid currently is.
 */
typedef struct GraphIdNode
{
    graphid id;
    struct GraphIdNode *next;
} GraphIdNode;

/* a container for a linked list of GraphIdNodes */
typedef struct ListGraphId
{
    GraphIdNode *head;
    GraphIdNode *tail;
    int64 size;
} ListGraphId;

/* declarations */

/* definitions */
/* return the next GraphIdNode */
GraphIdNode *next_GraphIdNode(GraphIdNode *node)
{
    return node->next;
}

/* return the graphid */
graphid get_graphid(GraphIdNode *node)
{
    return node->id;
}

/* get the size of the passed stack */
int64 get_stack_size(ListGraphId *stack)
{
    return stack->size;
}

/* return a reference to the head entry in the stack, or NULL if empty */
GraphIdNode *peek_stack_head(ListGraphId *stack)
{
    if (stack == NULL)
    {
        return NULL;
    }

    return stack->head;
}

/* return a reference to the tail entry in the stack */
GraphIdNode *peek_stack_tail(ListGraphId *stack)
{
    return stack->tail;
}

/* return a reference to the head entry of a list */
GraphIdNode *get_list_head(ListGraphId *list)
{
    return list->head;
}

/* get the size of the passed list */
int64 get_list_size(ListGraphId *list)
{
    return list->size;
}

/*
 * Helper function to add a graphid to the end of a ListGraphId container.
 * If the container is NULL, it creates the container with the entry.
 */
ListGraphId *append_graphid(ListGraphId *container, graphid id)
{
    GraphIdNode *new_node = NULL;

    /* create the new link */
    new_node = palloc0(sizeof(GraphIdNode));
    new_node->id = id;
    new_node->next = NULL;

    /*
     * If the container is NULL then this is a new list. So, create the
     * container and add in the new link.
     */
    if (container == NULL)
    {
        container = palloc0(sizeof(ListGraphId));
        container->head = new_node;
        container->tail = new_node;
        container->size = 1;
    }
    /* otherwise, this is an existing list, append id */
    else
    {
        container->tail->next = new_node;
        container->tail = new_node;
        container->size++;
    }

    return container;
}

/* free (delete) a ListGraphId list */
void free_ListGraphId(ListGraphId *container)
{
    GraphIdNode *curr_node = NULL;
    GraphIdNode *next_node = NULL;

    /* if the container is NULL we don't need to delete anything */
    if (container == NULL)
    {
        return;
    }

    /* otherwise, start from the head, free, and delete the links */
    curr_node = container->head;
    while (curr_node != NULL)
    {
        next_node = curr_node->next;
        /* we can do this because this is just a list of ints */
        pfree_if_not_null(curr_node);
        container->size--;
        curr_node = next_node;
    }

    Assert(container->size == 0);
    /* free the container */
    pfree_if_not_null(container);
}

/* helper function to create a new, empty, graphid stack */
ListGraphId *new_graphid_stack(void)
{
    ListGraphId *stack = NULL;

    /* allocate the container for the stack */
    stack = palloc0(sizeof(ListGraphId));

    /* set it to its initial values */
    stack->head = NULL;
    stack->tail = NULL;
    stack->size = 0;

    /* return the new stack */
    return stack;
}

/* helper function to free a graphid stack's contents but, not the container */
void free_graphid_stack(ListGraphId *stack)
{
    Assert(stack != NULL);

    if (stack == NULL)
    {
        elog(ERROR, "free_graphid_stack: NULL stack");
    }

    /* while there are entries */
    while (stack->head != NULL)
    {
        /* get the next element in the stack */
        GraphIdNode *next = stack->head->next;

        /* free the head element */
        pfree_if_not_null(stack->head);
        /* move the head to the next */
        stack->head = next;
    }

    /* reset the tail and size */
    stack->tail = NULL;
    stack->size = 0;
}

/*
 * Helper function for a generic push graphid (int64) to a stack. If the stack
 * is NULL, it will error out.
 */
void push_graphid_stack(ListGraphId *stack, graphid id)
{
    GraphIdNode *new_node = NULL;

    Assert(stack != NULL);

    if (stack == NULL)
    {
        elog(ERROR, "push_graphid_stack: NULL stack");
    }

    /* create the new element */
    new_node = palloc0(sizeof(GraphIdNode));
    new_node->id = id;

    /* insert (push) the new element on the top */
    new_node->next = stack->head;
    stack->head = new_node;
    stack->size++;
}

/*
 * Helper function for a generic pop graphid (int64) from a stack. If the stack
 * is empty, it will error out. You should verify that the stack isn't empty
 * prior to calling.
 */
graphid pop_graphid_stack(ListGraphId *stack)
{
    GraphIdNode *node = NULL;
    graphid id;

    Assert(stack != NULL);
    Assert(stack->size != 0);

    if (stack == NULL)
    {
        elog(ERROR, "pop_graphid_stack: NULL stack");
    }

    if (stack->size <= 0)
    {
        elog(ERROR, "pop_graphid_stack: empty stack");
    }


    /* remove the element from the top of the stack */
    node = stack->head;
    id = node->id;
    stack->head = stack->head->next;
    stack->size--;
    /* free the element */
    pfree_if_not_null(node);

    /* return the id */
    return id;
}
