/* -*- 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"
#include <lcbio/lcbio.h>
#include "bucketconfig/clconfig.h"
#include "hostlist.h"

/**This file contains routines to assist users in retrieving valid nodes */

/* We're gonna try to be extra careful in this function since many SDKs use
 * this to display node and/or host-port information.*/

static std::string&
ensure_scratch(lcb_t instance)
{
    if (!instance->scratch) {
        instance->scratch = new std::string;
    }
    instance->scratch->clear();
    return *instance->scratch;
}

static const char *
mk_scratch_host(lcb_t instance, const lcb_host_t *host)
{
    std::string& s = ensure_scratch(instance);
    s.append(host->host);
    s.append(":");
    s.append(host->port);
    return s.c_str();
}

static const char *
return_badhost(lcb_GETNODETYPE type)
{
    if (type & LCB_NODE_NEVERNULL) {
        return LCB_GETNODE_UNAVAILABLE;
    } else {
        return NULL;
    }
}

LIBCOUCHBASE_API
const char *
lcb_get_node(lcb_t instance, lcb_GETNODETYPE type, unsigned ix)
{
    lcbvb_SVCMODE mode;
    lcbvb_CONFIG *vbc = LCBT_VBCONFIG(instance);

    if (LCBT_SETTING(instance, sslopts) & LCB_SSL_ENABLED) {
        mode = LCBVB_SVCMODE_SSL;
    } else {
        mode = LCBVB_SVCMODE_PLAIN;
    }

    if (type & LCB_NODE_HTCONFIG) {
        if (type & LCB_NODE_CONNECTED) {
            const lcb_host_t *host = lcb_confmon_get_rest_host(instance->confmon);
            if (host) {
                return mk_scratch_host(instance, host);
            } else {
                return return_badhost(type);
            }

        } else {
            /* Retrieve one from the vbucket configuration */
            const char *hp = NULL;

            if (instance->type == LCB_TYPE_BUCKET) {
                if (vbc) {
                    ix %= LCBVB_NSERVERS(vbc);
                    hp = lcbvb_get_hostport(vbc, ix, LCBVB_SVCTYPE_MGMT, mode);

                } else if ((type & LCB_NODE_NEVERNULL) == 0) {
                    return NULL;
                }
            }
            if (hp == NULL && instance->ht_nodes && !instance->ht_nodes->empty()) {
                ix %= instance->ht_nodes->size();
                instance->ht_nodes->ensure_strlist();
                hp = instance->ht_nodes->hoststrs[ix];
            }
            if (!hp) {
                return return_badhost(type);
            }
            return ensure_scratch(instance).append(hp).c_str();
        }
    } else if (type & (LCB_NODE_DATA|LCB_NODE_VIEWS)) {
        ix %= LCBT_NSERVERS(instance);
        const lcb::Server *server = instance->get_server(ix);

        if ((type & LCB_NODE_CONNECTED) && !server->is_connected()) {
            return return_badhost(type);
        }

        /* otherwise, return the actual host:port of the server */
        if (type & LCB_NODE_DATA) {
            return mk_scratch_host(instance, &server->get_host());
        } else {
            return lcbvb_get_hostport(vbc, ix, LCBVB_SVCTYPE_VIEWS, mode);
        }
    } else {
        return NULL; /* don't know the type */
    }
}

LIBCOUCHBASE_API const char * lcb_get_host(lcb_t instance) {
    char *colon;
    const char *rv = lcb_get_node(instance,
        static_cast<lcb_GETNODETYPE>(LCB_NODE_HTCONFIG|LCB_NODE_NEVERNULL), 0);
    if (rv != NULL && (colon = (char *)strstr(rv, ":"))  != NULL) {
        if (instance->scratch && rv == instance->scratch->c_str()) {
            // We have a colon
            size_t colon_pos = instance->scratch->find(':');
            if (colon_pos != std::string::npos) {
                instance->scratch->erase(colon_pos);
            }
        }
    }
    return rv;
}

LIBCOUCHBASE_API const char * lcb_get_port(lcb_t instance) {
    const char *rv = lcb_get_node(instance,
        static_cast<lcb_GETNODETYPE>(LCB_NODE_HTCONFIG|LCB_NODE_NEVERNULL), 0);
    if (rv && (rv = strstr(rv, ":"))) {
        rv++;
    }
    return rv;
}

LIBCOUCHBASE_API
lcb_int32_t lcb_get_num_replicas(lcb_t instance)
{
    if (LCBT_VBCONFIG(instance)) {
        return LCBT_NREPLICAS(instance);
    } else {
        return -1;
    }
}

LIBCOUCHBASE_API
lcb_int32_t lcb_get_num_nodes(lcb_t instance)
{
    if (LCBT_VBCONFIG(instance)) {
        return LCBT_NSERVERS(instance);
    } else {
        return -1;
    }
}

LIBCOUCHBASE_API
const char *const *lcb_get_server_list(lcb_t instance)
{
    return hostlist_strents(instance->ht_nodes);
}

LIBCOUCHBASE_API
const char *
lcb_get_keynode(lcb_t instance, const void *key, size_t nkey)
{
    lcbvb_CONFIG *vbc = LCBT_VBCONFIG(instance);
    int srvix, vbid;

    if (!vbc) {
        return NULL;
    }

    lcbvb_map_key(vbc, key, nkey, &vbid, &srvix);
    if (srvix < 0) {
        return NULL;
    }

    return lcbvb_get_hostname(vbc, srvix);
}
