/*
  Copyright (c) 2008-2012 Red Hat, Inc. <http://www.redhat.com>
  This file is part of GlusterFS.

  This file is licensed to you under your choice of the GNU Lesser
  General Public License, version 3 or any later version (LGPLv3 or
  later), or the GNU General Public License, version 2 (GPLv2), in all
  cases as published by the Free Software Foundation.
*/

#include "glusterfs/fd-lk.h"
#include "glusterfs/common-utils.h"
#include "glusterfs/libglusterfs-messages.h"

int32_t
_fd_lk_delete_lock(fd_lk_ctx_node_t *lock)
{
    int32_t ret = -1;

    GF_VALIDATE_OR_GOTO("fd-lk", lock, out);

    list_del_init(&lock->next);

    ret = 0;
out:
    return ret;
}

int32_t
_fd_lk_destroy_lock(fd_lk_ctx_node_t *lock)
{
    int32_t ret = -1;

    GF_VALIDATE_OR_GOTO("fd-lk", lock, out);

    GF_FREE(lock);

    ret = 0;
out:
    return ret;
}

int
_fd_lk_destroy_lock_list(fd_lk_ctx_t *lk_ctx)
{
    int ret = -1;
    fd_lk_ctx_node_t *lk = NULL;
    fd_lk_ctx_node_t *tmp = NULL;

    GF_VALIDATE_OR_GOTO("fd-lk", lk_ctx, out);

    list_for_each_entry_safe(lk, tmp, &lk_ctx->lk_list, next)
    {
        _fd_lk_delete_lock(lk);
        _fd_lk_destroy_lock(lk);
    }
    ret = 0;
out:
    return ret;
}

int
fd_lk_ctx_unref(fd_lk_ctx_t *lk_ctx)
{
    int ref = -1;

    GF_VALIDATE_OR_GOTO("fd-lk", lk_ctx, err);

    ref = GF_ATOMIC_DEC(lk_ctx->ref);
    if (ref < 0)
        GF_ASSERT(!ref);
    if (ref == 0)
        _fd_lk_destroy_lock_list(lk_ctx);

    if (ref == 0) {
        LOCK_DESTROY(&lk_ctx->lock);
        GF_FREE(lk_ctx);
    }

    return 0;
err:
    return -1;
}

fd_lk_ctx_t *
fd_lk_ctx_ref(fd_lk_ctx_t *lk_ctx)
{
    if (!lk_ctx) {
        gf_msg_callingfn("fd-lk", GF_LOG_WARNING, EINVAL, LG_MSG_INVALID_ARG,
                         "invalid argument");
        return NULL;
    }

    GF_ATOMIC_INC(lk_ctx->ref);

    return lk_ctx;
}

fd_lk_ctx_t *
fd_lk_ctx_create()
{
    fd_lk_ctx_t *fd_lk_ctx = NULL;

    fd_lk_ctx = GF_CALLOC(1, sizeof(fd_lk_ctx_t), gf_common_mt_fd_lk_ctx_t);
    if (!fd_lk_ctx)
        goto out;

    INIT_LIST_HEAD(&fd_lk_ctx->lk_list);

    LOCK_INIT(&fd_lk_ctx->lock);

    fd_lk_ctx = fd_lk_ctx_ref(fd_lk_ctx);
out:
    return fd_lk_ctx;
}

int
_fd_lk_insert_lock(fd_lk_ctx_t *lk_ctx, fd_lk_ctx_node_t *lock)
{
    list_add_tail(&lock->next, &lk_ctx->lk_list);
    return 0;
}

static off_t
_fd_lk_get_lock_len(off_t start, off_t end)
{
    if (end == LLONG_MAX)
        return 0;
    else
        return (end - start + 1);
}

fd_lk_ctx_node_t *
fd_lk_ctx_node_new(int32_t cmd, struct gf_flock *flock)
{
    fd_lk_ctx_node_t *new_lock = NULL;

    /* TODO: get from mem-pool */
    new_lock = GF_CALLOC(1, sizeof(fd_lk_ctx_node_t),
                         gf_common_mt_fd_lk_ctx_node_t);
    if (!new_lock)
        goto out;

    new_lock->cmd = cmd;

    if (flock) {
        new_lock->fl_type = flock->l_type;
        new_lock->fl_start = flock->l_start;

        if (flock->l_len == 0)
            new_lock->fl_end = LLONG_MAX;
        else
            new_lock->fl_end = flock->l_start + flock->l_len - 1;

        memcpy(&new_lock->user_flock, flock, sizeof(struct gf_flock));
    }

    INIT_LIST_HEAD(&new_lock->next);
out:
    return new_lock;
}

int32_t
_fd_lk_delete_unlck_locks(fd_lk_ctx_t *lk_ctx)
{
    int32_t ret = -1;
    fd_lk_ctx_node_t *tmp = NULL;
    fd_lk_ctx_node_t *lk = NULL;

    GF_VALIDATE_OR_GOTO("fd-lk", lk_ctx, out);

    list_for_each_entry_safe(lk, tmp, &lk_ctx->lk_list, next)
    {
        if (lk->fl_type == F_UNLCK) {
            _fd_lk_delete_lock(lk);
            _fd_lk_destroy_lock(lk);
        }
    }
out:
    return ret;
}

int
fd_lk_overlap(fd_lk_ctx_node_t *l1, fd_lk_ctx_node_t *l2)
{
    if (l1->fl_end >= l2->fl_start && l2->fl_end >= l1->fl_start)
        return 1;

    return 0;
}

fd_lk_ctx_node_t *
_fd_lk_add_locks(fd_lk_ctx_node_t *l1, fd_lk_ctx_node_t *l2)
{
    fd_lk_ctx_node_t *sum = NULL;

    sum = fd_lk_ctx_node_new(0, NULL);
    if (!sum)
        goto out;

    sum->fl_start = min(l1->fl_start, l2->fl_start);
    sum->fl_end = max(l1->fl_end, l2->fl_end);

    sum->user_flock.l_start = sum->fl_start;
    sum->user_flock.l_len = _fd_lk_get_lock_len(sum->fl_start, sum->fl_end);
out:
    return sum;
}

/* Subtract two locks */
struct _values {
    fd_lk_ctx_node_t *locks[3];
};

int32_t
_fd_lk_sub_locks(struct _values *v, fd_lk_ctx_node_t *big,
                 fd_lk_ctx_node_t *small)
{
    int32_t ret = -1;

    if ((big->fl_start == small->fl_start) && (big->fl_end == small->fl_end)) {
        /* both edges coincide with big */
        v->locks[0] = fd_lk_ctx_node_new(small->cmd, NULL);
        if (!v->locks[0])
            goto out;

        memcpy(v->locks[0], big, sizeof(fd_lk_ctx_node_t));

        v->locks[0]->fl_type = small->fl_type;
        v->locks[0]->user_flock.l_type = small->fl_type;
    } else if ((small->fl_start > big->fl_start) &&
               (small->fl_end < big->fl_end)) {
        /* small lock is completely inside big lock,
           break it down into 3 different locks. */
        v->locks[0] = fd_lk_ctx_node_new(big->cmd, NULL);
        if (!v->locks[0])
            goto out;

        v->locks[1] = fd_lk_ctx_node_new(small->cmd, NULL);
        if (!v->locks[1])
            goto out;

        v->locks[2] = fd_lk_ctx_node_new(big->cmd, NULL);
        if (!v->locks[2])
            goto out;

        memcpy(v->locks[0], big, sizeof(fd_lk_ctx_node_t));
        v->locks[0]->fl_end = small->fl_start - 1;
        v->locks[0]->user_flock.l_len = _fd_lk_get_lock_len(
            v->locks[0]->fl_start, v->locks[0]->fl_end);

        memcpy(v->locks[1], small, sizeof(fd_lk_ctx_node_t));

        memcpy(v->locks[2], big, sizeof(fd_lk_ctx_node_t));
        v->locks[2]->fl_start = small->fl_end + 1;
        v->locks[2]->user_flock.l_len = _fd_lk_get_lock_len(
            v->locks[2]->fl_start, v->locks[2]->fl_end);
    } else if (small->fl_start == big->fl_start) {
        /* One of the ends co-incide, break the
           locks into two separate parts */
        v->locks[0] = fd_lk_ctx_node_new(small->cmd, NULL);
        if (!v->locks[0])
            goto out;

        v->locks[1] = fd_lk_ctx_node_new(big->cmd, NULL);
        if (!v->locks[1])
            goto out;

        memcpy(v->locks[0], small, sizeof(fd_lk_ctx_node_t));

        memcpy(v->locks[1], big, sizeof(fd_lk_ctx_node_t));
        v->locks[1]->fl_start = small->fl_end + 1;
        v->locks[1]->user_flock.l_start = small->fl_end + 1;
    } else if (small->fl_end == big->fl_end) {
        /* One of the ends co-incide, break the
           locks into two separate parts */
        v->locks[0] = fd_lk_ctx_node_new(small->cmd, NULL);
        if (!v->locks[0])
            goto out;

        v->locks[1] = fd_lk_ctx_node_new(big->cmd, NULL);
        if (!v->locks[1])
            goto out;

        memcpy(v->locks[0], big, sizeof(fd_lk_ctx_node_t));
        v->locks[0]->fl_end = small->fl_start - 1;
        v->locks[0]->user_flock.l_len = _fd_lk_get_lock_len(
            v->locks[0]->fl_start, v->locks[0]->fl_end);

        memcpy(v->locks[1], small, sizeof(fd_lk_ctx_node_t));
    } else {
        /* We should never come to this case */
        GF_ASSERT(!"Invalid case");
    }
    ret = 0;
out:
    return ret;
}

static void
_fd_lk_insert_and_merge(fd_lk_ctx_t *lk_ctx, fd_lk_ctx_node_t *lock)
{
    int32_t ret = -1;
    int32_t i = 0;
    fd_lk_ctx_node_t *entry = NULL;
    fd_lk_ctx_node_t *t = NULL;
    fd_lk_ctx_node_t *sum = NULL;
    struct _values v = {.locks = {0, 0, 0}};

    list_for_each_entry_safe(entry, t, &lk_ctx->lk_list, next)
    {
        if (!fd_lk_overlap(entry, lock))
            continue;

        if (entry->fl_type == lock->fl_type) {
            sum = _fd_lk_add_locks(entry, lock);
            if (!sum)
                return;
            sum->fl_type = entry->fl_type;
            sum->user_flock.l_type = entry->fl_type;
            _fd_lk_delete_lock(entry);
            _fd_lk_destroy_lock(entry);
            _fd_lk_destroy_lock(lock);
            _fd_lk_insert_and_merge(lk_ctx, sum);
            return;
        } else {
            sum = _fd_lk_add_locks(entry, lock);
            sum->fl_type = lock->fl_type;
            sum->user_flock.l_type = lock->fl_type;
            ret = _fd_lk_sub_locks(&v, sum, lock);
            if (ret)
                return;
            _fd_lk_delete_lock(entry);
            _fd_lk_destroy_lock(entry);

            _fd_lk_delete_lock(lock);
            _fd_lk_destroy_lock(lock);

            _fd_lk_destroy_lock(sum);

            for (i = 0; i < 3; i++) {
                if (!v.locks[i])
                    continue;

                INIT_LIST_HEAD(&v.locks[i]->next);
                _fd_lk_insert_and_merge(lk_ctx, v.locks[i]);
            }
            _fd_lk_delete_unlck_locks(lk_ctx);
            return;
        }
    }

    /* no conflicts, so just insert */
    if (lock->fl_type != F_UNLCK) {
        _fd_lk_insert_lock(lk_ctx, lock);
    } else {
        _fd_lk_destroy_lock(lock);
    }
}

static void
print_lock_list(fd_lk_ctx_t *lk_ctx)
{
    fd_lk_ctx_node_t *lk = NULL;

    gf_msg_debug("fd-lk", 0, "lock list:");

    list_for_each_entry(lk, &lk_ctx->lk_list, next)
        gf_msg_debug("fd-lk", 0,
                     "owner = %s, cmd = %s fl_type = %s,"
                     " fs_start = %" PRId64 ", fs_end = %" PRId64
                     ", "
                     "user_flock: l_type = %s, l_start = %" PRId64
                     ", "
                     "l_len = %" PRId64 ", ",
                     lkowner_utoa(&lk->user_flock.l_owner), get_lk_cmd(lk->cmd),
                     get_lk_type(lk->fl_type), lk->fl_start, lk->fl_end,
                     get_lk_type(lk->user_flock.l_type), lk->user_flock.l_start,
                     lk->user_flock.l_len);
}

int
fd_lk_insert_and_merge(fd_t *fd, int32_t cmd, struct gf_flock *flock)
{
    int32_t ret = -1;
    fd_lk_ctx_t *lk_ctx = NULL;
    fd_lk_ctx_node_t *lk = NULL;

    GF_VALIDATE_OR_GOTO("fd-lk", fd, out);
    GF_VALIDATE_OR_GOTO("fd-lk", flock, out);

    lk_ctx = fd_lk_ctx_ref(fd->lk_ctx);
    lk = fd_lk_ctx_node_new(cmd, flock);

    gf_msg_debug("fd-lk", 0,
                 "new lock request: owner = %s, fl_type = %s"
                 ", fs_start = %" PRId64 ", fs_end = %" PRId64
                 ", user_flock:"
                 " l_type = %s, l_start = %" PRId64 ", l_len = %" PRId64,
                 lkowner_utoa(&flock->l_owner), get_lk_type(lk->fl_type),
                 lk->fl_start, lk->fl_end, get_lk_type(lk->user_flock.l_type),
                 lk->user_flock.l_start, lk->user_flock.l_len);

    LOCK(&lk_ctx->lock);
    {
        _fd_lk_insert_and_merge(lk_ctx, lk);
        print_lock_list(lk_ctx);
    }
    UNLOCK(&lk_ctx->lock);

    fd_lk_ctx_unref(lk_ctx);

    ret = 0;
out:
    return ret;
}

gf_boolean_t
fd_lk_ctx_empty(fd_lk_ctx_t *lk_ctx)
{
    gf_boolean_t verdict = _gf_true;

    if (!lk_ctx)
        return _gf_true;

    LOCK(&lk_ctx->lock);
    {
        verdict = list_empty(&lk_ctx->lk_list);
    }
    UNLOCK(&lk_ctx->lock);

    return verdict;
}
