/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
 *     Copyright 2014 Couchbase, Inc.
 *
 *   Licensed 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 "internal.h"

#define DEFINE_DUMMY_CALLBACK(name, resptype) \
    static void name(lcb_t i, const void *c, lcb_error_t e, const resptype *r) \
    { (void)i;(void)e;(void)c;(void)r; }

static void dummy_error_callback(lcb_t instance,lcb_error_t error, const char *errinfo) {
    lcb_breakout(instance);
    (void)error;
    (void)errinfo;
}

static void dummy_store_callback(lcb_t instance, const void *cookie,
    lcb_storage_t operation, lcb_error_t error, const lcb_store_resp_t *resp) {
    (void)instance; (void)cookie; (void)operation; (void)error; (void)resp;
}


static void dummy_http_callback(lcb_http_request_t request, lcb_t instance,
    const void *cookie, lcb_error_t error, const lcb_http_resp_t *resp) {
    (void)request;(void)instance;(void)cookie;(void)error; (void)resp;
}

static void dummy_configuration_callback(lcb_t instance, lcb_configuration_t val) {
    (void)instance; (void)val;
}

static void dummy_bootstrap_callback(lcb_t instance, lcb_error_t err) {
    (void)instance; (void)err;
}

static void dummy_pktfwd_callback(lcb_t instance, const void *cookie,
    lcb_error_t err, lcb_PKTFWDRESP *resp) {
    (void)instance;(void)cookie;(void)err;(void)resp;
}
static void dummy_pktflushed_callback(lcb_t instance, const void *cookie) {
    (void)instance;(void)cookie;
}

DEFINE_DUMMY_CALLBACK(dummy_stat_callback, lcb_server_stat_resp_t)
DEFINE_DUMMY_CALLBACK(dummy_version_callback, lcb_server_version_resp_t)
DEFINE_DUMMY_CALLBACK(dummy_verbosity_callback, lcb_verbosity_resp_t)
DEFINE_DUMMY_CALLBACK(dummy_get_callback, lcb_get_resp_t)
DEFINE_DUMMY_CALLBACK(dummy_arithmetic_callback, lcb_arithmetic_resp_t)
DEFINE_DUMMY_CALLBACK(dummy_remove_callback, lcb_remove_resp_t)
DEFINE_DUMMY_CALLBACK(dummy_touch_callback, lcb_touch_resp_t)
DEFINE_DUMMY_CALLBACK(dummy_flush_callback, lcb_flush_resp_t)
DEFINE_DUMMY_CALLBACK(dummy_unlock_callback, lcb_unlock_resp_t)
DEFINE_DUMMY_CALLBACK(dummy_observe_callback, lcb_observe_resp_t)
DEFINE_DUMMY_CALLBACK(dummy_durability_callback, lcb_durability_resp_t)

typedef union {
    lcb_RESPBASE base;
    lcb_RESPGET get;
    lcb_RESPSTORE store;
    lcb_RESPUNLOCK unlock;
    lcb_RESPCOUNTER arith;
    lcb_RESPREMOVE del;
    lcb_RESPENDURE endure;
    lcb_RESPUNLOCK unl;
    lcb_RESPFLUSH flush;
    lcb_RESPSTATS stats;
    lcb_RESPMCVERSION mcversion;
    lcb_RESPVERBOSITY verbosity;
    lcb_RESPOBSERVE observe;
    lcb_RESPHTTP http;
} uRESP;

static void
compat_default_callback(lcb_t instance, int cbtype, const lcb_RESPBASE *r3base)
{
    const uRESP *r3 = (uRESP *)r3base;
    const void *cookie = r3base->cookie;
    lcb_error_t err = r3base->rc;

    #define FILL_KVC(dst) \
        (dst)->v.v0.key = r3base->key; \
        (dst)->v.v0.nkey = r3base->nkey; \
        (dst)->v.v0.cas = r3base->cas;

    switch (cbtype) {
    case LCB_CALLBACK_GET:
    case LCB_CALLBACK_GETREPLICA: {
        lcb_get_resp_t r2 = { 0 };
        FILL_KVC(&r2)
        r2.v.v0.bytes = r3->get.value;
        r2.v.v0.nbytes = r3->get.nvalue;
        r2.v.v0.flags = r3->get.itmflags;
        r2.v.v0.datatype = r3->get.datatype;
        instance->callbacks.get(instance, cookie, err, &r2);
        break;
    }
    case LCB_CALLBACK_STORE: {
        lcb_store_resp_t r2 = { 0 };
        FILL_KVC(&r2)
        r2.v.v0.mutation_token = lcb_resp_get_mutation_token(cbtype, r3base);
        instance->callbacks.store(instance, cookie, r3->store.op, err, &r2);
        break;
    }
    case LCB_CALLBACK_COUNTER: {
        lcb_arithmetic_resp_t r2 = { 0 };
        FILL_KVC(&r2);
        r2.v.v0.value = r3->arith.value;
        r2.v.v0.mutation_token = lcb_resp_get_mutation_token(cbtype, r3base);
        instance->callbacks.arithmetic(instance, cookie, err, &r2);
        break;
    }
    case LCB_CALLBACK_REMOVE: {
        lcb_remove_resp_t r2 = { 0 };
        FILL_KVC(&r2)
        r2.v.v0.mutation_token = lcb_resp_get_mutation_token(cbtype, r3base);
        instance->callbacks.remove(instance, cookie, err, &r2);
        break;
    }
    case LCB_CALLBACK_TOUCH: {
        lcb_touch_resp_t r2 = { 0 };
        FILL_KVC(&r2)
        instance->callbacks.touch(instance, cookie, err, &r2);
        break;
    }
    case LCB_CALLBACK_UNLOCK: {
        lcb_unlock_resp_t r2 = { 0 };
        r2.v.v0.key = r3->unl.key;
        r2.v.v0.nkey = r3->unl.nkey;
        instance->callbacks.unlock(instance, cookie, err, &r2);
        break;
    }
    case LCB_CALLBACK_FLUSH: {
        lcb_flush_resp_t r2 = { 0 };
        r2.v.v0.server_endpoint = r3->flush.server;
        instance->callbacks.flush(instance, cookie, err, &r2);
        break;
    }
    case LCB_CALLBACK_VERSIONS: {
        lcb_server_version_resp_t r2 = { 0 };
        r2.v.v0.server_endpoint = r3->mcversion.server;
        r2.v.v0.vstring = r3->mcversion.mcversion;
        r2.v.v0.nvstring = r3->mcversion.nversion;
        instance->callbacks.version(instance, cookie, err, &r2);
        break;
    }
    case LCB_CALLBACK_VERBOSITY: {
        lcb_verbosity_resp_t r2 = { 0 };
        r2.v.v0.server_endpoint = r3->verbosity.server;
        instance->callbacks.verbosity(instance, cookie, err, &r2);
        break;
    }
    case LCB_CALLBACK_STATS: {
        lcb_server_stat_resp_t r2 = { 0 };
        r2.v.v0.key = r3->stats.key;
        r2.v.v0.nkey = r3->stats.nkey;
        r2.v.v0.bytes = r3->stats.value;
        r2.v.v0.nbytes = r3->stats.nvalue;
        r2.v.v0.server_endpoint = r3->stats.server;
        instance->callbacks.stat(instance, cookie, err, &r2);
        break;
    }
    case LCB_CALLBACK_OBSERVE: {
        lcb_observe_resp_t r2 = { 0 };
        FILL_KVC(&r2);
        r2.v.v0.status = r3->observe.status;
        r2.v.v0.from_master = r3->observe.ismaster;
        r2.v.v0.ttp = r3->observe.ttp;
        r2.v.v0.ttr = r3->observe.ttr;
        instance->callbacks.observe(instance, cookie, err, &r2);
        break;
    }
    case LCB_CALLBACK_ENDURE: {
        lcb_durability_resp_t r2 = { 0 };
        FILL_KVC(&r2);
        r2.v.v0.err = r3->endure.rc;
        r2.v.v0.exists_master = r3->endure.exists_master;
        r2.v.v0.persisted_master = r3->endure.persisted_master;
        r2.v.v0.npersisted = r3->endure.npersisted;
        r2.v.v0.nreplicated = r3->endure.nreplicated;
        r2.v.v0.nresponses = r3->endure.nresponses;
        if (err == LCB_SUCCESS) {
            err = r3->endure.rc;
        }
        instance->callbacks.durability(instance, cookie, err, &r2);
        break;
    }
    case LCB_CALLBACK_HTTP: {
        lcb_http_res_callback target;
        lcb_http_resp_t r2 = { 0 };
        r2.v.v0.path = r3->http.key;
        r2.v.v0.npath = r3->http.nkey;
        r2.v.v0.bytes = r3->http.body;
        r2.v.v0.nbytes = r3->http.nbody;
        r2.v.v0.status = r3->http.htstatus;
        r2.v.v0.headers = r3->http.headers;
        if (!(r3base->rflags & LCB_RESP_F_FINAL)) {
            target = instance->callbacks.http_data;
        } else {
            target = instance->callbacks.http_complete;
        }
        target(r3->http._htreq, instance, cookie, err, &r2);
        break;
    }
    case LCB_CALLBACK_OBSEQNO:
    case LCB_CALLBACK_CBFLUSH:
        /* Don't crash! */
        break;

    default:
        abort();
        break;
    }
}

void lcb_initialize_packet_handlers(lcb_t instance)
{
    instance->callbacks.get = dummy_get_callback;
    instance->callbacks.store = dummy_store_callback;
    instance->callbacks.arithmetic = dummy_arithmetic_callback;
    instance->callbacks.remove = dummy_remove_callback;
    instance->callbacks.touch = dummy_touch_callback;
    instance->callbacks.error = dummy_error_callback;
    instance->callbacks.stat = dummy_stat_callback;
    instance->callbacks.version = dummy_version_callback;
    instance->callbacks.http_complete = dummy_http_callback;
    instance->callbacks.http_data = dummy_http_callback;
    instance->callbacks.flush = dummy_flush_callback;
    instance->callbacks.unlock = dummy_unlock_callback;
    instance->callbacks.configuration = dummy_configuration_callback;
    instance->callbacks.observe = dummy_observe_callback;
    instance->callbacks.verbosity = dummy_verbosity_callback;
    instance->callbacks.durability = dummy_durability_callback;
    instance->callbacks.errmap = lcb_errmap_default;
    instance->callbacks.bootstrap = dummy_bootstrap_callback;
    instance->callbacks.pktflushed = dummy_pktflushed_callback;
    instance->callbacks.pktfwd = dummy_pktfwd_callback;
    instance->callbacks.v3callbacks[LCB_CALLBACK_DEFAULT] = compat_default_callback;
}

#define CALLBACK_ACCESSOR(name, cbtype, field) \
LIBCOUCHBASE_API \
cbtype name(lcb_t instance, cbtype cb) { \
    cbtype ret = instance->callbacks.field; \
    if (cb != NULL) { \
        instance->callbacks.field = cb; \
    } \
    return ret; \
}

LIBCOUCHBASE_API
lcb_destroy_callback
lcb_set_destroy_callback(lcb_t instance, lcb_destroy_callback cb)
{
    lcb_destroy_callback ret = LCBT_SETTING(instance, dtorcb);
    if (cb) {
        LCBT_SETTING(instance, dtorcb) = cb;
    }
    return ret;
}

CALLBACK_ACCESSOR(lcb_set_get_callback, lcb_get_callback, get)
CALLBACK_ACCESSOR(lcb_set_store_callback, lcb_store_callback, store)
CALLBACK_ACCESSOR(lcb_set_arithmetic_callback, lcb_arithmetic_callback, arithmetic)
CALLBACK_ACCESSOR(lcb_set_observe_callback, lcb_observe_callback, observe)
CALLBACK_ACCESSOR(lcb_set_remove_callback, lcb_remove_callback, remove)
CALLBACK_ACCESSOR(lcb_set_touch_callback, lcb_touch_callback, touch)
CALLBACK_ACCESSOR(lcb_set_stat_callback, lcb_stat_callback, stat)
CALLBACK_ACCESSOR(lcb_set_version_callback, lcb_version_callback, version)
CALLBACK_ACCESSOR(lcb_set_error_callback, lcb_error_callback, error)
CALLBACK_ACCESSOR(lcb_set_flush_callback, lcb_flush_callback, flush)
CALLBACK_ACCESSOR(lcb_set_http_complete_callback, lcb_http_complete_callback, http_complete)
CALLBACK_ACCESSOR(lcb_set_http_data_callback, lcb_http_data_callback, http_data)
CALLBACK_ACCESSOR(lcb_set_unlock_callback, lcb_unlock_callback, unlock)
CALLBACK_ACCESSOR(lcb_set_configuration_callback, lcb_configuration_callback, configuration)
CALLBACK_ACCESSOR(lcb_set_verbosity_callback, lcb_verbosity_callback, verbosity)
CALLBACK_ACCESSOR(lcb_set_durability_callback, lcb_durability_callback, durability)
CALLBACK_ACCESSOR(lcb_set_errmap_callback, lcb_errmap_callback, errmap)
CALLBACK_ACCESSOR(lcb_set_bootstrap_callback, lcb_bootstrap_callback, bootstrap)
CALLBACK_ACCESSOR(lcb_set_pktfwd_callback, lcb_pktfwd_callback, pktfwd)
CALLBACK_ACCESSOR(lcb_set_pktflushed_callback, lcb_pktflushed_callback, pktflushed)

LIBCOUCHBASE_API
lcb_RESPCALLBACK
lcb_install_callback3(lcb_t instance, int cbtype, lcb_RESPCALLBACK cb)
{
    lcb_RESPCALLBACK ret;
    if (cbtype >= LCB_CALLBACK__MAX) {
        return NULL;
    }

    ret = instance->callbacks.v3callbacks[cbtype];
    instance->callbacks.v3callbacks[cbtype] = cb;
    return ret;
}

LIBCOUCHBASE_API
lcb_RESPCALLBACK
lcb_get_callback3(lcb_t instance, int cbtype)
{
    if (cbtype >= LCB_CALLBACK__MAX) {
        return NULL;
    }
    return instance->callbacks.v3callbacks[cbtype];
}

LIBCOUCHBASE_API
const char *
lcb_strcbtype(int cbtype)
{
    switch (cbtype) {
    case LCB_CALLBACK_GET:
        return "GET";
    case LCB_CALLBACK_STORE:
        return "STORE";
    case LCB_CALLBACK_COUNTER:
        return "COUNTER";
    case LCB_CALLBACK_TOUCH:
        return "TOUCH";
    case LCB_CALLBACK_REMOVE:
        return "REMOVE";
    case LCB_CALLBACK_UNLOCK:
        return "UNLOCK";
    case LCB_CALLBACK_STATS:
        return "STATS";
    case LCB_CALLBACK_VERSIONS:
        return "VERSIONS";
    case LCB_CALLBACK_VERBOSITY:
        return "VERBOSITY";
    case LCB_CALLBACK_FLUSH:
        return "FLUSH";
    case LCB_CALLBACK_OBSERVE:
        return "OBSERVE";
    case LCB_CALLBACK_GETREPLICA:
        return "GETREPLICA";
    case LCB_CALLBACK_ENDURE:
        return "ENDURE";
    case LCB_CALLBACK_HTTP:
        return "HTTP";
    case LCB_CALLBACK_CBFLUSH:
        return "CBFLUSH";
    case LCB_CALLBACK_OBSEQNO:
        return "OBSEQNO";
    case LCB_CALLBACK_STOREDUR:
        return "STOREDUR";
    case LCB_CALLBACK_SDMUTATE:
        return "SDMUTATE";
    case LCB_CALLBACK_SDLOOKUP:
        return "SDLOOKUP";
    default:
        return "UNKNOWN";
    }
}

static void
nocb_fallback(lcb_t instance, int type, const lcb_RESPBASE *response)
{
    (void)instance; (void)type; (void)response;
}
lcb_RESPCALLBACK
lcb_find_callback(lcb_t instance, lcb_CALLBACKTYPE cbtype)
{
    lcb_RESPCALLBACK ret = instance->callbacks.v3callbacks[cbtype];
    if (!ret) {
        ret = instance->callbacks.v3callbacks[LCB_CALLBACK_DEFAULT];
        if (!ret) {
            ret = nocb_fallback;
        }
    }
    return ret;
}
