/*
 * Copyright (c) 2016 MariaDB Corporation Ab
 *
 * Use of this software is governed by the Business Source License included
 * in the LICENSE.TXT file and at www.mariadb.com/bsl11.
 *
 * Change Date: 2020-01-01
 *
 * On the date above, in accordance with the Business Source License, use
 * of this software will be governed by version 2 or later of the General
 * Public License.
 */

#include "internal/mlist.h"
#include <maxscale/alloc.h>

static void mlist_free_memory(mlist_t* ml, char* name);
static mlist_node_t* mlist_node_init(void* data, mlist_cursor_t* cursor);
//static mlist_node_t* mlist_node_get_next(mlist_node_t* curr_node);
//static mlist_node_t* mlist_get_first(mlist_t* list);
//static mlist_cursor_t* mlist_get_cursor(mlist_t* list);


/**
 * @node Cut off nodes of the list.
 *
 * Parameters:
 * @param ml - <usage>
 *          <description>
 *
 * @return Pointer to the first of the detached nodes.
 *
 *
 * @details (write detailed description here)
 *
 */
mlist_node_t* mlist_detach_nodes(mlist_t* ml)
{
    mlist_node_t* node;
    CHK_MLIST(ml);

    node = ml->mlist_first;
    ml->mlist_first = NULL;
    ml->mlist_last = NULL;
    ml->mlist_nodecount = 0;
    return node;
}

/**
 * @node Create a list with rwlock and optional read-only cursor
 *
 * Parameters:
 * @param listp - <usage>
 *          <description>
 *
 * @param cursor - <usage>
 *          <description>
 *
 * @param name - <usage>
 *          <description>
 *
 * @return Address of mlist_t struct.
 *
 *
 * @details Cursor must protect its reads with read lock, and after
 * acquiring read lock reader must check whether the list is deleted
 * (mlist_deleted).
 *
 */
mlist_t* mlist_init(mlist_t* listp, mlist_cursor_t** cursor, char* name,
                    void (*datadel)(void*), int maxnodes)
{
    mlist_cursor_t* c;
    mlist_t* list;

    if (cursor != NULL)
    {
        ss_dassert(*cursor == NULL);
    }
    /** listp is not NULL if caller wants flat list */
    if (listp == NULL)
    {
        list = (mlist_t*) MXS_CALLOC(1, sizeof (mlist_t));
    }
    else
    {
        /** Caller wants list flat, memory won't be freed */
        list = listp;
        list->mlist_flat = true;
    }
    ss_dassert(list != NULL);

    if (list == NULL)
    {
        mlist_free_memory(list, name);
        goto return_list;
    }
    list->mlist_chk_top = CHK_NUM_MLIST;
    list->mlist_chk_tail = CHK_NUM_MLIST;
    /** Set size limit for list. 0 means unlimited */
    list->mlist_nodecount_max = maxnodes;
    /** Set data deletion callback fun */
    list->mlist_datadel = datadel;

    if (name != NULL)
    {
        list->mlist_name = name;
    }
    /** Create mutex, return NULL if fails. */
    if (simple_mutex_init(&list->mlist_mutex, "writebuf mutex") == NULL)
    {
        ss_dfprintf(stderr, "* Creating rwlock for mlist failed\n");
        mlist_free_memory(list, name);
        list = NULL;
        goto return_list;
    }

    /** Create cursor for reading the list */
    if (cursor != NULL)
    {
        c = mlist_cursor_init(list);

        if (c == NULL)
        {
            simple_mutex_done(&list->mlist_mutex);
            mlist_free_memory(list, name);
            list = NULL;
            goto return_list;
        }
        CHK_MLIST_CURSOR(c);
        *cursor = c;
    }
    list->mlist_versno = 2; /*< vresno != 0 means that list is initialized */
    CHK_MLIST(list);

return_list:
    return list;
}

/**
 * @node Free mlist memory allocations. name must be explicitly
 * set if mlist has one.
 *
 * Parameters:
 * @param ml - <usage>
 *          <description>
 *
 * @param name - <usage>
 *          <description>
 *
 * @return void
 *
 *
 * @details (write detailed description here)
 *
 */
static void mlist_free_memory(mlist_t* ml, char* name)
{
    mlist_node_t* node;

    /** name */
    if (name != NULL)
    {
        MXS_FREE(name);
    }
    if (ml != NULL)
    {
        /** list data */
        while (ml->mlist_first != NULL)
        {
            /** Scan list and free nodes and data inside nodes */
            node = ml->mlist_first->mlnode_next;
            mlist_node_done(ml->mlist_first);
            ml->mlist_first = node;
        }

        /** list structure */
        if (!ml->mlist_flat)
        {
            MXS_FREE(ml);
        }
    }
}

void* mlist_node_get_data(mlist_node_t* node)
{
    CHK_MLIST_NODE(node);
    return node->mlnode_data;
}

void mlist_node_done(mlist_node_t* n)
{
    CHK_MLIST_NODE(n);
    if (n->mlnode_data != NULL)
    {
        if (n->mlnode_list->mlist_datadel != NULL)
        {
            (n->mlnode_list->mlist_datadel(n->mlnode_data));
        }
        MXS_FREE(n->mlnode_data);
    }
    MXS_FREE(n);
}

/**
 * @node Mark list as deleted and free the memory.
 *
 * Parameters:
 * @param list - <usage>
 *          <description>
 *
 * @return void
 *
 *
 * @details (write detailed description here)
 *
 */
void mlist_done(mlist_t* list)
{
    CHK_MLIST(list);
    simple_mutex_lock(&list->mlist_mutex, true);
    list->mlist_deleted = true;
    simple_mutex_unlock(&list->mlist_mutex);
    simple_mutex_done(&list->mlist_mutex);
    mlist_free_memory(list, list->mlist_name);
}

/**
 * @node Adds data to list by allocating node for it. Checks list size limit.
 *
 * Parameters:
 * @param list - <usage>
 *          <description>
 *
 * @param data - <usage>
 *          <description>
 *
 * @return true, if succeed, false, if list had node limit and it is full.
 *
 *
 * @details (write detailed description here)
 *
 */
bool mlist_add_data_nomutex(mlist_t* list, void* data)
{
    bool succp;

    succp = mlist_add_node_nomutex(list, mlist_node_init(data, NULL));

    return succp;
}

static mlist_node_t* mlist_node_init(void* data, mlist_cursor_t* cursor)
{
    mlist_node_t* node;

    node = (mlist_node_t*) MXS_CALLOC(1, sizeof (mlist_node_t));
    MXS_ABORT_IF_NULL(node);
    node->mlnode_chk_top = CHK_NUM_MLIST_NODE;
    node->mlnode_chk_tail = CHK_NUM_MLIST_NODE;
    node->mlnode_data = data;
    CHK_MLIST_NODE(node);

    if (cursor != NULL)
    {
        cursor->mlcursor_pos = node;
    }

    return node;
}

mlist_node_t* mlist_detach_first(mlist_t* ml)
{
    mlist_node_t* node;

    CHK_MLIST(ml);
    node = ml->mlist_first;
    CHK_MLIST_NODE(node);
    ml->mlist_first = node->mlnode_next;
    node->mlnode_next = NULL;

    ml->mlist_nodecount -= 1;
    if (ml->mlist_nodecount == 0)
    {
        ml->mlist_last = NULL;
    }
    else
    {
        CHK_MLIST_NODE(ml->mlist_first);
    }
    CHK_MLIST(ml);

    return (node);
}

/**
 * @node Add new node to end of list if there is space for it.
 *
 * Parameters:
 * @param list - <usage>
 *          <description>
 *
 * @param newnode - <usage>
 *          <description>
 *
 * @param add_last - <usage>
 *          <description>
 *
 * @return true, if succeede, false, if list size limit was exceeded.
 *
 *
 * @details (write detailed description here)
 *
 */
bool mlist_add_node_nomutex(mlist_t* list, mlist_node_t* newnode)
{
    bool succp = false;

    CHK_MLIST(list);
    CHK_MLIST_NODE(newnode);
    ss_dassert(!list->mlist_deleted);

    /** List is full already. */
    if (list->mlist_nodecount == list->mlist_nodecount_max)
    {
        goto return_succp;
    }
    /** Find location for new node */
    if (list->mlist_last != NULL)
    {
        ss_dassert(!list->mlist_last->mlnode_deleted);
        CHK_MLIST_NODE(list->mlist_last);
        CHK_MLIST_NODE(list->mlist_first);
        ss_dassert(list->mlist_last->mlnode_next == NULL);
        list->mlist_last->mlnode_next = newnode;
    }
    else
    {
        list->mlist_first = newnode;
    }
    list->mlist_last = newnode;
    newnode->mlnode_list = list;
    list->mlist_nodecount += 1;
    succp = true;
return_succp:
    CHK_MLIST(list);
    return succp;
}


/**
 * mlist_cursor_t
 */
mlist_cursor_t* mlist_cursor_init(mlist_t* list)
{
    CHK_MLIST(list);
    mlist_cursor_t* c;

    /** acquire shared lock to the list */
    simple_mutex_lock(&list->mlist_mutex, true);

    c = (mlist_cursor_t *) MXS_CALLOC(1, sizeof (mlist_cursor_t));

    if (c == NULL)
    {
        simple_mutex_unlock(&list->mlist_mutex);
        goto return_cursor;
    }
    c->mlcursor_chk_top = CHK_NUM_MLIST_CURSOR;
    c->mlcursor_chk_tail = CHK_NUM_MLIST_CURSOR;
    c->mlcursor_list = list;

    /** Set cursor position if list is not empty */
    if (list->mlist_first != NULL)
    {
        c->mlcursor_pos = list->mlist_first;
    }
    simple_mutex_unlock(&list->mlist_mutex);

    CHK_MLIST_CURSOR(c);

return_cursor:
    return c;
}

void* mlist_cursor_get_data_nomutex(mlist_cursor_t* mc)
{
    CHK_MLIST_CURSOR(mc);
    return (mc->mlcursor_pos->mlnode_data);
}

bool mlist_cursor_move_to_first(mlist_cursor_t* mc)
{
    bool succp = false;
    mlist_t* list;

    CHK_MLIST_CURSOR(mc);
    list = mc->mlcursor_list;
    CHK_MLIST(list);
    simple_mutex_lock(&list->mlist_mutex, true);

    if (mc->mlcursor_list->mlist_deleted)
    {
        simple_mutex_unlock(&list->mlist_mutex);
        return false;
    }
    /** Set position point to first node */
    mc->mlcursor_pos = list->mlist_first;

    if (mc->mlcursor_pos != NULL)
    {
        CHK_MLIST_NODE(mc->mlcursor_pos);
        succp = true;
    }
    simple_mutex_unlock(&list->mlist_mutex);
    return succp;
}
