/*
   Copyright (c) 2013 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 <sys/uio.h>

#include <glusterfs/xlator.h>
#include <glusterfs/defaults.h>
#include <glusterfs/logging.h>

#include "cdc.h"
#include "cdc-mem-types.h"

static void
cdc_cleanup_iobref(cdc_info_t *ci)
{
    assert(ci->iobref != NULL);
    iobref_clear(ci->iobref);
}

int32_t
cdc_readv_cbk(call_frame_t *frame, void *cookie, xlator_t *this, int32_t op_ret,
              int32_t op_errno, struct iovec *vector, int32_t count,
              struct iatt *stbuf, struct iobref *iobref, dict_t *xdata)
{
    int ret = -1;
    cdc_priv_t *priv = NULL;
    cdc_info_t ci = {
        0,
    };

    GF_VALIDATE_OR_GOTO("cdc", this, default_out);
    GF_VALIDATE_OR_GOTO(this->name, frame, default_out);

    priv = this->private;

    if (op_ret <= 0)
        goto default_out;

    if ((priv->min_file_size != 0) && (op_ret < priv->min_file_size))
        goto default_out;

    ci.count = count;
    ci.ibytes = op_ret;
    ci.vector = vector;
    ci.buf = NULL;
    ci.iobref = NULL;
    ci.ncount = 0;
    ci.crc = 0;
    ci.buffer_size = GF_CDC_DEF_BUFFERSIZE;

    /* A readv compresses on the server side and decompresses on the client side
     */
    if (priv->op_mode == GF_CDC_MODE_SERVER) {
        ret = cdc_compress(this, priv, &ci, &xdata);
    } else if (priv->op_mode == GF_CDC_MODE_CLIENT) {
        ret = cdc_decompress(this, priv, &ci, xdata);
    } else {
        gf_log(this->name, GF_LOG_ERROR, "Invalid operation mode (%d)",
               priv->op_mode);
    }

    if (ret)
        goto default_out;

    STACK_UNWIND_STRICT(readv, frame, ci.nbytes, op_errno, ci.vec, ci.ncount,
                        stbuf, iobref, xdata);
    cdc_cleanup_iobref(&ci);
    return 0;

default_out:
    STACK_UNWIND_STRICT(readv, frame, op_ret, op_errno, vector, count, stbuf,
                        iobref, xdata);
    return 0;
}

int32_t
cdc_readv(call_frame_t *frame, xlator_t *this, fd_t *fd, size_t size,
          off_t offset, uint32_t flags, dict_t *xdata)
{
    fop_readv_cbk_t cbk = NULL;

#ifdef HAVE_LIB_Z
    cbk = cdc_readv_cbk;
#else
    cbk = default_readv_cbk;
#endif
    STACK_WIND(frame, cbk, FIRST_CHILD(this), FIRST_CHILD(this)->fops->readv,
               fd, size, offset, flags, xdata);
    return 0;
}

int32_t
cdc_writev_cbk(call_frame_t *frame, void *cookie, xlator_t *this,
               int32_t op_ret, int32_t op_errno, struct iatt *prebuf,
               struct iatt *postbuf, dict_t *xdata)
{
    STACK_UNWIND_STRICT(writev, frame, op_ret, op_errno, prebuf, postbuf,
                        xdata);
    return 0;
}

int32_t
cdc_writev(call_frame_t *frame, xlator_t *this, fd_t *fd, struct iovec *vector,
           int32_t count, off_t offset, uint32_t flags, struct iobref *iobref,
           dict_t *xdata)
{
    int ret = -1;
    cdc_priv_t *priv = NULL;
    cdc_info_t ci = {
        0,
    };
    size_t isize = 0;

    GF_VALIDATE_OR_GOTO("cdc", this, err);
    GF_VALIDATE_OR_GOTO(this->name, frame, err);

    priv = this->private;

    isize = iov_length(vector, count);

    if (isize <= 0)
        goto default_out;

    if ((priv->min_file_size != 0) && (isize < priv->min_file_size))
        goto default_out;

    ci.count = count;
    ci.ibytes = isize;
    ci.vector = vector;
    ci.buf = NULL;
    ci.iobref = NULL;
    ci.ncount = 0;
    ci.crc = 0;
    ci.buffer_size = GF_CDC_DEF_BUFFERSIZE;

    /* A writev compresses on the client side and decompresses on the server
     * side
     */
    if (priv->op_mode == GF_CDC_MODE_CLIENT) {
        ret = cdc_compress(this, priv, &ci, &xdata);
    } else if (priv->op_mode == GF_CDC_MODE_SERVER) {
        ret = cdc_decompress(this, priv, &ci, xdata);
    } else {
        gf_log(this->name, GF_LOG_ERROR, "Invalid operation mode (%d) ",
               priv->op_mode);
    }

    if (ret)
        goto default_out;

    STACK_WIND(frame, cdc_writev_cbk, FIRST_CHILD(this),
               FIRST_CHILD(this)->fops->writev, fd, ci.vec, ci.ncount, offset,
               flags, iobref, xdata);

    cdc_cleanup_iobref(&ci);
    return 0;

default_out:
    STACK_WIND(frame, cdc_writev_cbk, FIRST_CHILD(this),
               FIRST_CHILD(this)->fops->writev, fd, vector, count, offset,
               flags, iobref, xdata);
    return 0;
err:
    STACK_UNWIND_STRICT(writev, frame, -1, EINVAL, NULL, NULL, NULL);
    return 0;
}

int32_t
mem_acct_init(xlator_t *this)
{
    int ret = -1;

    if (!this)
        return ret;

    ret = xlator_mem_acct_init(this, gf_cdc_mt_end);

    if (ret != 0) {
        gf_log(this->name, GF_LOG_ERROR,
               "Memory accounting init"
               "failed");
        return ret;
    }

    return ret;
}

int32_t
init(xlator_t *this)
{
    int ret = -1;
    char *temp_str = NULL;
    cdc_priv_t *priv = NULL;

    GF_VALIDATE_OR_GOTO("cdc", this, err);

    if (!this->children || this->children->next) {
        gf_log(this->name, GF_LOG_ERROR, "Need subvolume == 1");
        goto err;
    }

    if (!this->parents) {
        gf_log(this->name, GF_LOG_WARNING, "Dangling volume. Check volfile");
    }

    priv = GF_CALLOC(1, sizeof(*priv), gf_cdc_mt_priv_t);
    if (!priv) {
        goto err;
    }

    /* Check if debug mode is turned on */
    GF_OPTION_INIT("debug", priv->debug, bool, err);
    if (priv->debug) {
        gf_log(this->name, GF_LOG_DEBUG, "CDC debug option turned on");
    }

    /* Set Gzip Window Size */
    GF_OPTION_INIT("window-size", priv->window_size, int32, err);
    if ((priv->window_size > GF_CDC_MAX_WINDOWSIZE) ||
        (priv->window_size < GF_CDC_DEF_WINDOWSIZE)) {
        gf_log(this->name, GF_LOG_WARNING,
               "Invalid gzip window size (%d), using default",
               priv->window_size);
        priv->window_size = GF_CDC_DEF_WINDOWSIZE;
    }

    /* Set Gzip (De)Compression Level */
    GF_OPTION_INIT("compression-level", priv->cdc_level, int32, err);
    if (((priv->cdc_level < 1) || (priv->cdc_level > 9)) &&
        (priv->cdc_level != GF_CDC_DEF_COMPRESSION)) {
        gf_log(this->name, GF_LOG_WARNING,
               "Invalid gzip (de)compression level (%d),"
               " using default",
               priv->cdc_level);
        priv->cdc_level = GF_CDC_DEF_COMPRESSION;
    }

    /* Set Gzip Memory Level */
    GF_OPTION_INIT("mem-level", priv->mem_level, int32, err);
    if ((priv->mem_level < 1) || (priv->mem_level > 9)) {
        gf_log(this->name, GF_LOG_WARNING,
               "Invalid gzip memory level, using the default");
        priv->mem_level = GF_CDC_DEF_MEMLEVEL;
    }

    /* Set min file size to enable compression */
    GF_OPTION_INIT("min-size", priv->min_file_size, int32, err);

    /* Mode of operation - Server/Client */
    ret = dict_get_str(this->options, "mode", &temp_str);
    if (ret) {
        gf_log(this->name, GF_LOG_CRITICAL, "Operation mode not specified !!");
        goto err;
    }

    if (GF_CDC_MODE_IS_CLIENT(temp_str)) {
        priv->op_mode = GF_CDC_MODE_CLIENT;
    } else if (GF_CDC_MODE_IS_SERVER(temp_str)) {
        priv->op_mode = GF_CDC_MODE_SERVER;
    } else {
        gf_log(this->name, GF_LOG_CRITICAL,
               "Bogus operation mode (%s) specified", temp_str);
        goto err;
    }

    this->private = priv;
    gf_log(this->name, GF_LOG_DEBUG, "CDC xlator loaded in (%s) mode",
           temp_str);
    return 0;

err:
    if (priv)
        GF_FREE(priv);

    return -1;
}

void
fini(xlator_t *this)
{
    cdc_priv_t *priv = this->private;

    if (priv)
        GF_FREE(priv);
    this->private = NULL;
    return;
}

struct xlator_fops fops = {
    .readv = cdc_readv,
    .writev = cdc_writev,
};

struct xlator_cbks cbks = {};

struct volume_options options[] = {
    {.key = {"window-size"},
     .default_value = "-15",
     .type = GF_OPTION_TYPE_INT,
     .description = "Size of the zlib history buffer."},
    {.key = {"mem-level"},
     .default_value = "8",
     .type = GF_OPTION_TYPE_INT,
     .description = "Memory allocated for internal compression state. "
                    "1 uses minimum memory but is slow and reduces "
                    "compression ratio; memLevel=9 uses maximum memory "
                    "for optimal speed. The default value is 8."},
    {.key = {"compression-level"},
     .default_value = "-1",
     .type = GF_OPTION_TYPE_INT,
     .description = "Compression levels \n"
                    "0 : no compression, 1 : best speed, \n"
                    "9 : best compression, -1 : default compression "},
    {.key = {"min-size"},
     .default_value = "0",
     .type = GF_OPTION_TYPE_INT,
     .description = "Data is compressed only when its size exceeds this."},
    {.key = {"mode"},
     .value = {"server", "client"},
     .type = GF_OPTION_TYPE_STR,
     .description = "Set on the basis of where the xlator is loaded. "
                    "This option should NOT be configured by user."},
    {.key = {"debug"},
     .default_value = "false",
     .type = GF_OPTION_TYPE_BOOL,
     .description = "This is used in testing. Will dump compressed data "
                    "to disk as a gzip file."},
    {.key = {NULL}},
};

xlator_api_t xlator_api = {
    .init = init,
    .fini = fini,
    .mem_acct_init = mem_acct_init,
    .op_version = {GD_OP_VERSION_3_9_0},
    .fops = &fops,
    .cbks = &cbks,
    .options = options,
    .identifier = "cdc",
    .category = GF_TECH_PREVIEW,
};
