#include <glusterfs/api/glfs.h>
#include <glusterfs/api/glfs-handles.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>

/* Few rules:
 * 1. A client may have multiple lease keys, but a lease key cannot be shared by
 * multiple clients.
 * 2. Lease key can be set before open, or in glfs_lease request. A lease key
 * set like this is valid for the lifetime of the fd, i.e. a fd cannot have
 * multiple lease key. But a lease key can be shared across multiple fds.
 */
glfs_t *client1 = NULL, *client2 = NULL, *client3 = NULL, *client4 = NULL;
glfs_fd_t *fd1 = NULL, *fd2 = NULL, *fd3 = NULL, *fd4 = NULL;
FILE *log_file = NULL;
char lid1[GLFS_LEASE_ID_SIZE] = "lid1-clnt1",
     lid2[GLFS_LEASE_ID_SIZE] = "lid2-clnt2";
char lid3[GLFS_LEASE_ID_SIZE] = "lid3-clnt2", lid4[GLFS_LEASE_ID_SIZE] = {
                                                  0,
};
char *volname = NULL, *glfs_log_file = NULL;

#define MAX_CLIENTS 4
#define MAX_FDS 4
#define TEST_FILE "/test/lease"
#define SHUD_PASS 0
#define SHUD_FAIL -1
#define NONE 0

static void
recall_cbk(struct glfs_lease lease, void *data);

static int
set_read_lease(glfs_fd_t *fd, char ld[])
{
    struct glfs_lease lease = {
        0,
    };
    int ret = 0;

    memset(&lease, 0, sizeof(lease));
    lease.cmd = GLFS_SET_LEASE;
    lease.lease_type = GLFS_RD_LEASE;
    memcpy(&lease.lease_id, ld, GLFS_LEASE_ID_SIZE);
    ret = glfs_lease(fd, &lease, &recall_cbk, fd);
    if (ret < 0) {
        fprintf(log_file, "\n    RD_LEASE failed with ret: %d (%s)", ret,
                strerror(errno));
        return -1;
    }
    fprintf(log_file, "\n    Took RD_LEASE");
    return ret;
}

static int
set_write_lease(glfs_fd_t *fd, char ld[])
{
    struct glfs_lease lease = {
        0,
    };
    int ret = 0;

    memset(&lease, 0, sizeof(lease));
    lease.cmd = GLFS_SET_LEASE;
    lease.lease_type = GLFS_RW_LEASE;
    memcpy(&lease.lease_id, ld, GLFS_LEASE_ID_SIZE);
    ret = glfs_lease(fd, &lease, &recall_cbk, NULL);
    if (ret < 0) {
        fprintf(log_file, "\n    RW_LEASE failed with ret: %d (%s)", ret,
                strerror(errno));
        return -1;
    }
    fprintf(log_file, "\n    Took RW_LEASE");
    return ret;
}

static int
get_lease(glfs_fd_t *fd, char ld[])
{
    struct glfs_lease lease = {
        0,
    };
    int ret = 0;

    memset(&lease, 0, sizeof(lease));
    lease.cmd = GLFS_GET_LEASE;
    lease.lease_type = -1;
    memcpy(&lease.lease_id, ld, GLFS_LEASE_ID_SIZE);
    ret = glfs_lease(fd, &lease, &recall_cbk, NULL);
    if (ret < 0) {
        fprintf(log_file, "\n    GET_LEASE failed with ret: %d (%s)", ret,
                strerror(errno));
        return -1;
    }
    if (lease.lease_type == GLFS_RD_LEASE)
        fprintf(log_file, "\n    Esisting Lease: RD_LEASE");
    else if (lease.lease_type == GLFS_RW_LEASE)
        fprintf(log_file, "\n    Esisting Lease: RW_LEASE");
    else if (lease.lease_type == 3)
        fprintf(log_file, "\n    Esisting Lease: RD_LEASE|RW_LEASE");
    else if (lease.lease_type == 0)
        fprintf(log_file, "\n    Esisting Lease: NONE");
    else
        fprintf(log_file, "\n    Existing lease type:%d", lease.lease_type);
    return lease.lease_type;
}

static int
unlk_write_lease(glfs_fd_t *fd, char ld[])
{
    struct glfs_lease lease = {
        0,
    };
    int ret = 0;

    memset(&lease, 0, sizeof(lease));
    lease.cmd = GLFS_UNLK_LEASE;
    lease.lease_type = GLFS_RW_LEASE;
    memcpy(&lease.lease_id, ld, GLFS_LEASE_ID_SIZE);
    ret = glfs_lease(fd, &lease, &recall_cbk, NULL);
    if (ret < 0) {
        fprintf(log_file, "\n    Unlock RW_LESAE failed with ret: %d (%s)", ret,
                strerror(errno));
        return -1;
    }
    fprintf(log_file, "\n    Unlocked RW_LEASE");
    return ret;
}

static int
unlk_read_lease(glfs_fd_t *fd, char ld[])
{
    struct glfs_lease lease = {
        0,
    };
    int ret = 0;

    memset(&lease, 0, sizeof(lease));
    lease.cmd = GLFS_UNLK_LEASE;
    lease.lease_type = GLFS_RD_LEASE;
    memcpy(&lease.lease_id, ld, GLFS_LEASE_ID_SIZE);

    ret = glfs_lease(fd, &lease, &recall_cbk, NULL);
    if (ret < 0) {
        fprintf(log_file, "\n    Unlock RD_LEASE failed with ret: %d (%s)", ret,
                strerror(errno));
        return -1;
    }
    fprintf(log_file, "\n    Unlocked RD_LEASE");
    return ret;
}

glfs_t *
setup_new_client(char *volname, char *log_fileile)
{
    int ret = 0;
    glfs_t *fs = NULL;

    fs = glfs_new(volname);
    if (!fs) {
        fprintf(log_file, "\nglfs_new: returned NULL (%s)\n", strerror(errno));
        goto error;
    }

    ret = glfs_set_volfile_server(fs, "tcp", "localhost", 24007);
    if (ret < 0) {
        fprintf(log_file, "\nglfs_set_volfile_server failed ret:%d (%s)\n", ret,
                strerror(errno));
        goto error;
    }

    ret = glfs_set_logging(fs, log_fileile, 7);
    if (ret < 0) {
        fprintf(log_file, "\nglfs_set_logging failed with ret: %d (%s)\n", ret,
                strerror(errno));
        goto error;
    }

    ret = glfs_init(fs);
    if (ret < 0) {
        fprintf(log_file, "\nglfs_init failed with ret: %d (%s)\n", ret,
                strerror(errno));
        goto error;
    }
    return fs;
error:
    return NULL;
}

#define OPEN(client, flags, fd, lease_id)                                      \
    do {                                                                       \
        int ret_val = 0;                                                       \
        ret_val = glfs_setfsleaseid(lease_id);                                 \
        if (ret_val) {                                                         \
            fprintf(log_file,                                                  \
                    "\nglfs_setfsleaseid failed with ret: %d (%s)\n", ret,     \
                    strerror(errno));                                          \
            return -1;                                                         \
        }                                                                      \
        fd = glfs_open(client, TEST_FILE, flags);                              \
        if (fd == NULL) {                                                      \
            fprintf(log_file, "\nglfs_open failed with ret: %d (%s)\n", ret,   \
                    strerror(errno));                                          \
            return -1;                                                         \
        }                                                                      \
    } while (0)

#define VERIFY_RESULT(test_case, ret, value)                                   \
    do {                                                                       \
        if (ret != value) {                                                    \
            fprintf(log_file,                                                  \
                    "\n    Testcase %d failed, ret = %d, value=%d\n",          \
                    test_case, ret, value);                                    \
            goto error; /*test unsuccessful*/                                  \
        }                                                                      \
        fprintf(log_file, "\n    Testcase %d Succeeded\n", test_case);         \
    } while (0)

static void
recall_cbk(struct glfs_lease lease, void *data)
{
    int ret = -1;
    char ld[GLFS_LEASE_ID_SIZE] = "";

    fprintf(log_file, "\nRECALL received on lease_id:(%s)", lease.lease_id);
    memcpy(ld, lease.lease_id, GLFS_LEASE_ID_SIZE);
    ret = unlk_write_lease((glfs_fd_t *)data, ld);
    VERIFY_RESULT(500, ret, SHUD_PASS);
error:
    return;
}

static int
testcase1_rd_lease()
{
    glfs_fd_t *fd1 = NULL;
    int ret = 0;

    fprintf(log_file, "\n Basic test case for Read lease:");
    /* Open fd on client 1 in RD mode */
    OPEN(client1, O_RDONLY, fd1, lid1);
    ret = set_write_lease(fd1, lid1);
    VERIFY_RESULT(1, ret, SHUD_FAIL);

    ret = set_read_lease(fd1, lid1);
    VERIFY_RESULT(2, ret, SHUD_PASS);

    ret = get_lease(fd1, lid1);
    VERIFY_RESULT(3, ret, GLFS_RD_LEASE);

    ret = unlk_write_lease(fd1, lid1);
    VERIFY_RESULT(4, ret, SHUD_FAIL);

    ret = unlk_read_lease(fd1, lid1);
    VERIFY_RESULT(5, ret, SHUD_PASS);

    ret = get_lease(fd1, lid1);
    VERIFY_RESULT(6, ret, NONE);

    ret = unlk_read_lease(fd1, lid1);
    VERIFY_RESULT(7, ret, SHUD_PASS);

    ret = glfs_close(fd1);
    VERIFY_RESULT(8, ret, SHUD_PASS);

    return 0;
error:
    return -1;
}

static int
testcase2_wr_lease()
{
    glfs_fd_t *fd1 = NULL;
    int ret = 0;

    fprintf(log_file, "\n Basic test case for Write lease:");
    /* Open fd on client 1 in WRonly mode */
    OPEN(client1, O_WRONLY, fd1, lid1);
    ret = set_read_lease(fd1, lid1);
    VERIFY_RESULT(1, ret, SHUD_FAIL);

    ret = unlk_write_lease(fd1, lid1);
    VERIFY_RESULT(2, ret, SHUD_PASS);

    ret = set_write_lease(fd1, lid1);
    VERIFY_RESULT(3, ret, SHUD_PASS);

    ret = get_lease(fd1, lid1);
    VERIFY_RESULT(4, ret, GLFS_RW_LEASE);

    ret = unlk_write_lease(fd1, lid1);
    VERIFY_RESULT(5, ret, SHUD_PASS);

    ret = get_lease(fd1, lid1);
    VERIFY_RESULT(6, ret, NONE);

    ret = unlk_read_lease(fd1, lid1);
    VERIFY_RESULT(7, ret, SHUD_FAIL);

    ret = glfs_close(fd1);
    VERIFY_RESULT(8, ret, SHUD_PASS);

    return 0;
error:
    return -1;
}

static int
testcase3_rd_wr_lease()
{
    glfs_fd_t *fd1 = NULL;
    int ret = 0;

    fprintf(log_file, "\n Basic test case for Read Write lease:");
    /* Open fd on client 1 in WRonly mode */
    OPEN(client1, O_RDWR, fd1, lid1);
    ret = set_read_lease(fd1, lid1);
    VERIFY_RESULT(1, ret, SHUD_PASS);

    ret = set_write_lease(fd1, lid1);
    VERIFY_RESULT(2, ret, SHUD_PASS);

    ret = get_lease(fd1, lid1);
    VERIFY_RESULT(3, ret, (GLFS_RW_LEASE | GLFS_RD_LEASE));

    ret = unlk_write_lease(fd1, lid1);
    VERIFY_RESULT(4, ret, SHUD_PASS);

    ret = get_lease(fd1, lid1);
    VERIFY_RESULT(5, ret, GLFS_RD_LEASE);

    ret = unlk_read_lease(fd1, lid1);
    VERIFY_RESULT(6, ret, SHUD_PASS);

    ret = get_lease(fd1, lid1);
    VERIFY_RESULT(7, ret, NONE);

    ret = glfs_close(fd1);
    VERIFY_RESULT(8, ret, SHUD_PASS);

    return 0;
error:
    return -1;
}

static int
testcase4_rd_lease_multi_clnt()
{
    glfs_fd_t *fd1 = NULL;
    glfs_fd_t *fd2 = NULL;
    int ret = 0;

    fprintf(log_file, "\n Basic test case for multi client Read lease:");

    /* Open fd on client 1 in RD mode */
    OPEN(client1, O_RDONLY, fd1, lid1);

    /* Open fd on client 2 in RW mode */
    OPEN(client2, O_RDONLY, fd2, lid2);

    ret = set_read_lease(fd1, lid1);
    VERIFY_RESULT(1, ret, SHUD_PASS);

    ret = set_read_lease(fd2, lid2);
    VERIFY_RESULT(2, ret, SHUD_PASS);

    ret = get_lease(fd1, lid1);
    VERIFY_RESULT(3, ret, GLFS_RD_LEASE);

    ret = unlk_read_lease(fd1, lid1);
    VERIFY_RESULT(4, ret, SHUD_PASS);

    ret = unlk_read_lease(fd2, lid2);
    VERIFY_RESULT(5, ret, SHUD_PASS);

    ret = get_lease(fd1, lid1);
    VERIFY_RESULT(6, ret, NONE);

    ret = get_lease(fd2, lid2);
    VERIFY_RESULT(7, ret, NONE);

    ret = glfs_close(fd1);
    VERIFY_RESULT(8, ret, SHUD_PASS);

    ret = glfs_close(fd2);
    VERIFY_RESULT(9, ret, SHUD_PASS);

    return 0;
error:
    return -1;
}

static int
testcase5_openfd_multi_lid()
{
    glfs_fd_t *fd1 = NULL;
    glfs_fd_t *fd2 = NULL;
    glfs_fd_t *fd3 = NULL;
    int ret = 0;

    fprintf(log_file, "\n Basic test case for multi lid openfd check:");

    /* Open fd on client 1 in RD mode */
    OPEN(client1, O_RDONLY, fd1, lid1);

    /* Open fd on client 2 in RW mode */
    OPEN(client2, O_RDWR, fd2, lid2);
    OPEN(client2, O_RDWR, fd3, lid2);

    ret = set_read_lease(fd1, lid1);
    VERIFY_RESULT(
        1, ret,
        SHUD_FAIL); /*As there are other openfds in WR mode from diff lid*/

    ret = set_write_lease(fd2, lid2);
    VERIFY_RESULT(
        2, ret, SHUD_FAIL); /*As thers is another fd in RD mode from diff lid */

    ret = glfs_close(fd1);
    VERIFY_RESULT(3, ret, SHUD_PASS);

    ret = set_write_lease(fd2, lid2);
    VERIFY_RESULT(4, ret, SHUD_PASS);

    ret = unlk_write_lease(fd2, lid2);
    VERIFY_RESULT(5, ret, SHUD_PASS);

    ret = glfs_close(fd2);
    VERIFY_RESULT(6, ret, SHUD_PASS);

    ret = glfs_close(fd3);
    VERIFY_RESULT(7, ret, SHUD_PASS);

    return 0;
error:
    return -1;
}

static int
testcase6_openfd_same_lid()
{
    glfs_fd_t *fd1 = NULL;
    glfs_fd_t *fd2 = NULL;
    glfs_fd_t *fd3 = NULL;
    int ret = 0;

    fprintf(log_file, "\n Basic test case for same lid openfd check:");

    /* Open fd on client 2 in RW mode */
    OPEN(client1, O_RDWR, fd1, lid2);
    OPEN(client1, O_RDWR, fd2, lid2);

    ret = set_write_lease(fd1, lid2);
    VERIFY_RESULT(4, ret, SHUD_PASS);

    ret = set_write_lease(fd2, lid2);
    VERIFY_RESULT(4, ret, SHUD_PASS);

    ret = set_read_lease(fd2, lid2);
    VERIFY_RESULT(4, ret, SHUD_PASS);

    ret = unlk_write_lease(fd1, lid2);
    VERIFY_RESULT(5, ret, SHUD_PASS);

    ret = unlk_read_lease(fd2, lid2);
    VERIFY_RESULT(5, ret, SHUD_PASS);

    ret = unlk_write_lease(fd2, lid2);
    VERIFY_RESULT(5, ret, SHUD_PASS);

    ret = glfs_close(fd1);
    VERIFY_RESULT(6, ret, SHUD_PASS);

    ret = glfs_close(fd2);
    VERIFY_RESULT(7, ret, SHUD_PASS);

    return 0;
error:
    return -1;
}

static int
testcase7_rd_multi_lid()
{
    glfs_fd_t *fd1 = NULL;
    glfs_fd_t *fd2 = NULL;
    int ret = 0;

    fprintf(log_file, "\n Basic test case for multi lease id Read lease:");

    /* Open fd on client 1 in RD mode */
    OPEN(client2, O_RDONLY, fd1, lid2);

    /* Open fd on client 2 in RD mode */
    OPEN(client2, O_RDONLY, fd2, lid3);

    ret = set_read_lease(fd1, lid2);
    VERIFY_RESULT(1, ret, SHUD_PASS);

    ret = set_read_lease(fd2, lid3);
    VERIFY_RESULT(2, ret, SHUD_PASS);

    ret = get_lease(fd1, lid2);
    VERIFY_RESULT(3, ret, GLFS_RD_LEASE);

    ret = unlk_read_lease(fd1, lid2);
    VERIFY_RESULT(4, ret, SHUD_PASS);

    ret = unlk_read_lease(fd2, lid3);
    VERIFY_RESULT(5, ret, SHUD_PASS);

    ret = get_lease(fd1, lid2);
    VERIFY_RESULT(6, ret, NONE);

    ret = get_lease(fd2, lid3);
    VERIFY_RESULT(7, ret, NONE);

    ret = glfs_close(fd1);
    VERIFY_RESULT(8, ret, SHUD_PASS);

    ret = glfs_close(fd2);
    VERIFY_RESULT(9, ret, SHUD_PASS);

    return 0;
error:
    return -1;
}

static int
testcase8_client_disconnect()
{
    glfs_fd_t *fd1 = NULL;
    glfs_fd_t *fd2 = NULL;
    int ret = 0;

    fprintf(log_file, "\n Basic test case for client disconnect cleanup");

    /* Open fd on client 1 in RD mode */
    OPEN(client1, O_RDWR, fd1, lid1);

    ret = set_read_lease(fd1, lid1);
    VERIFY_RESULT(1, ret, SHUD_PASS);

    ret = get_lease(fd1, lid1);
    VERIFY_RESULT(2, ret, GLFS_RD_LEASE);

    ret = set_write_lease(fd1, lid1);
    VERIFY_RESULT(3, ret, SHUD_PASS);

    ret = get_lease(fd1, lid1);
    VERIFY_RESULT(4, ret, (GLFS_RD_LEASE | GLFS_RW_LEASE));

    ret = glfs_fini(client1);
    VERIFY_RESULT(5, ret, SHUD_PASS);

    /* Open fd on client 2 in RD mode */
    OPEN(client2, O_RDONLY, fd2, lid3);

    ret = get_lease(fd2, lid3);
    VERIFY_RESULT(6, ret, NONE);

    ret = glfs_close(fd2);
    VERIFY_RESULT(7, ret, SHUD_PASS);

    client1 = setup_new_client(volname, glfs_log_file);

    return 0;
error:
    return -1;
}

static int
testcase9_recall_conflict_lease()
{
    struct glfs_object *obj = NULL;
    glfs_fd_t *fd1 = NULL;
    int ret = 0;
    struct glfs_lease lease = {
        0,
    };

    fprintf(log_file,
            "\n Basic test case for conflicting lease causing recall");

    memset(&lease, 0, sizeof(lease));
    lease.cmd = GLFS_SET_LEASE;
    lease.lease_type = GLFS_RD_LEASE;
    memcpy(&lease.lease_id, lid2, GLFS_LEASE_ID_SIZE);
    /* Open fd on client 1 in RD mode */
    OPEN(client1, O_RDWR, fd1, lid1);
    ret = set_write_lease(fd1, lid1);
    VERIFY_RESULT(1, ret, SHUD_PASS);

    obj = glfs_h_lookupat(client2, NULL, TEST_FILE, NULL, 0);
    ret = glfs_h_lease(client2, obj, &lease);
    VERIFY_RESULT(2, ret, SHUD_FAIL);

    ret = unlk_write_lease(fd1, lid1);
    VERIFY_RESULT(5, ret, SHUD_PASS);

    sleep(3);
    ret = glfs_h_close(obj);
    VERIFY_RESULT(3, ret, SHUD_PASS);
    ret = glfs_close(fd1);
    VERIFY_RESULT(4, ret, SHUD_PASS);

    return 0;
error:
    return -1;
}

static int
testcase10_recall_open_conflict()
{
    glfs_fd_t *fd1 = NULL;
    glfs_fd_t *fd2 = NULL;
    int ret = 0;

    fprintf(log_file, "\n Basic test case for conflicting open causing recall");

    /* Open fd on client 1 in RW mode */
    OPEN(client1, O_RDWR, fd1, lid1);

    ret = set_write_lease(fd1, lid1);
    VERIFY_RESULT(1, ret, SHUD_PASS);

    /* Open fd on client 1 in RW mode */
    OPEN(client2, O_RDWR, fd2, lid2);

    /* TODO: Check for recall cbk functionality */
    ret = glfs_close(fd1);
    VERIFY_RESULT(2, ret, SHUD_PASS);

    ret = glfs_close(fd2);
    VERIFY_RESULT(3, ret, SHUD_PASS);

    return 0;
error:
    return -1;
}

int
main(int argc, char *argv[])
{
    int ret = 0;
    int i = 0;
    glfs_fd_t *fd = NULL;
    glfs_fd_t *fd1 = NULL;
    char *topdir = "topdir", *filename = "file1";
    char *buf = NULL;
    int x = 0;
    ssize_t xattr_size = -1;

    if (argc != 4) {
        fprintf(stderr,
                "Expect following args %s <Vol> <glfs client log file> "
                "<testcase log file>\n",
                argv[0]);
        return -1;
    }

    log_file = fopen(argv[3], "w");
    if (!log_file)
        goto error;

    volname = argv[1];
    glfs_log_file = argv[2];

    /* Setup 3 clients */
    client1 = setup_new_client(volname, glfs_log_file);
    client2 = setup_new_client(volname, glfs_log_file);
    client3 = setup_new_client(volname, glfs_log_file);

    ret = testcase1_rd_lease();
    VERIFY_RESULT(101, ret, SHUD_PASS);

    ret = testcase2_wr_lease();
    VERIFY_RESULT(102, ret, SHUD_PASS);

    ret = testcase3_rd_wr_lease();
    VERIFY_RESULT(103, ret, SHUD_PASS);

    ret = testcase4_rd_lease_multi_clnt();
    VERIFY_RESULT(104, ret, SHUD_PASS);

    ret = testcase5_openfd_multi_lid();
    VERIFY_RESULT(105, ret, SHUD_PASS);

    ret = testcase6_openfd_same_lid();
    VERIFY_RESULT(106, ret, SHUD_PASS);

    ret = testcase7_rd_multi_lid();
    VERIFY_RESULT(107, ret, SHUD_PASS);

    ret = testcase8_client_disconnect();
    VERIFY_RESULT(108, ret, SHUD_PASS);

    ret = testcase9_recall_conflict_lease();
    VERIFY_RESULT(109, ret, SHUD_PASS);

    ret = testcase10_recall_open_conflict();
    VERIFY_RESULT(110, ret, SHUD_PASS);

    glfs_fini(client1);
    glfs_fini(client2);
    glfs_fini(client3);

    fclose(log_file);
    return 0;
error:
    return -1;
}
