/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
 *     Copyright 2013 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.
 */

#ifndef LCB_CLCONFIG_H
#define LCB_CLCONFIG_H

#include "hostlist.h"
#include <list>
#include <lcbio/timer-ng.h>
#include <lcbio/timer-cxx.h>

/** @file */

/**
 * @defgroup lcb-confmon Cluster Configuration Management
 *
 * @brief Monitors the retrieval and application of new cluster topology maps
 * (vBucket Configurations)
 *
 * @details
 * This module attempts to implement the 'Configuration Provider' interface
 * described at https://docs.google.com/document/d/1bSMt0Sj1uQtm0OYolQaJDJg4sASfoCEwU6_gjm1he8s/edit
 *
 * The model is fairly complex though significantly more maintainable and
 * testable than the previous model. The basic idea is as follows:
 *
 *
 * <ol>
 *
 * <li>
 * There is a _Configuration Monitor_ object (Confmon) which acts
 * as the configuration supervisor. It is responsible for returning
 * configuration objects to those entities which request it.
 * </li>
 *
 * <li>
 * There are multiple _Configuration Provider_ (Provider) objects.
 * These providers aggregate configurations from multiple sources and
 * implement a common interface to:
 *
 *  <ol>
 *  <li>Return a _quick_ configuration without fetching from network or disk
 *  (see Provider::get_cached())</i>

 *  <li>Schedule a refresh to retrieve the latest configuration from the
 *  network (see Provider::refresh())</li>
 *
 *  <li>Notify the monitor that it has received a new configuration. The
 *  monitor itself will determine whether or not to accept the new
 *  configuration by examining the configuration and determining if it is more
 *  recent than the one currently in used. See lcb_confmon_set_next()</li>
 *  </ol></li>
 *
 * <li>
 * _Configuration Info_ objects. These objects are refcounted wrappers
 * around vbucket configuration handles. They have a refcount and also an
 * integer which can be used to compare with other objects for 'freshness'.
 * See ConfigInfo
 * </li>
 *
 * <li>
 * _Configuration Listeners_. These are registered with the global supervisor
 * and are invoked whenever a new valid configuration is detected. This is
 * really only ever used during bootstrap or testing where we are explicitly
 * waiting for a configuration without having any actual commands to schedule.
 * See Listener
 * </li>
 * </ol>
 */

/**
 *@addtogroup lcb-confmon
 *@{
 */

namespace lcb {
namespace clconfig {

/**
 * @brief Enumeration of the various config providers available.
 * The type of methods available. These are enumerated in order of preference
 */
enum Method {
    /** File-based "configcache" provider. Implemented in bc_file.c */
    CLCONFIG_FILE,
    /** New-style config-over-memcached provider. Implemented in bc_cccp.c */
    CLCONFIG_CCCP,
    /** Old-style streaming HTTP provider. Implemented in bc_http.c */
    CLCONFIG_HTTP,
    /** Raw memcached provided */
    CLCONFIG_MCRAW,
    /** Cluster administration provider. Static config with services */
    CLCONFIG_CLADMIN,

    CLCONFIG_MAX,

    /** Ephemeral source, used for tests */
    CLCONFIG_PHONY
};


/** Event types propagated to listeners */
enum EventType {
    /** Called when a new configuration is being set in confmon */
    CLCONFIG_EVENT_GOT_NEW_CONFIG,

    /** Called when _any_ configuration is received via set_enxt */
    CLCONFIG_EVENT_GOT_ANY_CONFIG,

    /** Called when all providers have been tried */
    CLCONFIG_EVENT_PROVIDERS_CYCLED,

    /** The monitor has stopped */
    CLCONFIG_EVENT_MONITOR_STOPPED
};


/** @brief Possible confmon states */
enum State {
    /** The monitor is idle and not requesting a new configuration */
    CONFMON_S_INACTIVE = 0,

    /** The monitor is actively requesting a configuration */
    CONFMON_S_ACTIVE = 1 << 0,

    /** The monitor is fetching a configuration, but is in a throttle state */
    CONFMON_S_ITERGRACE = 1 << 1
};


struct Provider;
struct Listener;
class ConfigInfo;

/**
 * This object contains the information needed for libcouchbase to deal with
 * when retrieving new configs.
 */
struct Confmon {
    /**
     * @brief Create a new configuration monitor.
     * This function creates a new `confmon` object which can be used to manage
     * configurations and their providers.
     *
     * @param settings
     * @param iot
     *
     * Once the confmon object has been created you may enable or disable various
     * providers (see lcb_confmon_set_provider_active()). Once no more providers
     * remain to be activated you should call lcb_confmon_prepare() once. Then
     * call the rest of the functions.
     */
    Confmon(lcb_settings*, lcbio_pTABLE iot);
    void destroy() { delete this; }
    ~Confmon();

    /**
     * Get the provider following the current provider, or NULL if this is
     * the last provider in the list.
     * @param cur The current provider.
     * @return The next provider, or NULL if no more providers remain.
     */
    Provider *next_active(Provider *cur);

    /**
     * Gets the first active provider.
     * @return the first provider, or NULL if no providers exist.
     */
    Provider *first_active();

    /**
     * Prepares the configuration monitor object for operations. This will insert
     * all the enabled providers into a list. Call this function each time a
     * provider has been enabled.
     * @param mon
     */
    void prepare();

    /**
     * Set a given provider as being 'active'. This will activate the
     * provider as well as call #prepare() to update the list.
     * @param type The ID of the provider to activate
     * @param enabled true for activate, false for deactivate
     */
    void set_active(Method type, bool enabled);

    /**
     * @brief Request a configuration refresh
     *
     * Start traversing the list of current providers, requesting a new
     * configuration for each. This function will asynchronously loop through all
     * providers until one provides a new configuration.
     *
     * You may call #stop() to asynchronously break out of the loop.
     * If the confmon is already in a refreshing state
     * (i.e. #is_refreshing()) returns true then this function does
     * nothing.
     *
     * This function is reentrant safe and may be called at any time.
     *
     * @param mon
     * @see lcb_confmon_add_listener()
     * @see #stop()
     * @see #is_refreshing()
     */
    void start();

    /**
     * @brief Cancel a pending configuration refresh.
     *
     * Stops the monitor. This will call Provider::pause() for each active
     * provider. Typically called before destruction or when a new configuration
     * has been found.
     *
     * This function is safe to call anywhere. If the monitor is already stopped
     * then this function does nothing.
     *
     * @param mon
     * @see #start()
     * @see #is_refreshing()
     */
    void stop();

    /**
     * @brief Check if the monitor is waiting for a new config from a provider
     * @param mon
     * @return true if refreshing, false if idle
     */
    bool is_refreshing() const {
        return (state & CONFMON_S_ACTIVE) != 0;
    }

    /**
     * Get the current configuration
     * @return The configuration
     */
    ConfigInfo* get_config() const {
        return config;
    }

    /**
     * Get the last error which occurred on this object
     * @return The last error
     */
    lcb_error_t get_last_error() const {
        return last_error;
    }

    /**
     * @brief Get the current monitor state
     * @param mon the monitor
     * @return a set of flags consisting of @ref State values.
     */
    int get_state() const {
        return state;
    }

    void stop_real();
    void do_next_provider();
    int do_set_next(ConfigInfo*, bool notify_miss);
    void invoke_listeners(EventType, ConfigInfo*);

    /**
     * @brief Indicate that a provider has failed and advance the monitor
     *
     * Indicate that the current provider has failed to obtain a new configuration.
     * This is always called by a provider and should be invoked when the provider
     * has encountered an internal error which caused it to be unable to fetch
     * the configuration.
     *
     * Note that this function is safe to call from any provider at any time. If
     * the provider is not the current provider then it is treated as an async
     * push notification failure and ignored. This function is _not_ safe to call
     * from consumers of providers
     *
     * Once this is called, the confmon instance will either roll over to the next
     * provider or enter the inactive state depending on the configuration and
     * whether the current provider is the last provider in the list.
     *
     * @param provider
     * @param err
     */
    void provider_failed(Provider *which, lcb_error_t why);

    /**
     * @brief Indicate that a provider has successfuly retrieved a configuration.
     *
     * Indicates that the provider has fetched a new configuration from the network
     * and that confmon should attempt to propagate it. It has similar semantics
     * to lcb_confmon_provider_failed() except that the second argument is a config
     * object rather than an error code. The second argument must not be `NULL`
     *
     * The monitor will compare the new config against the current config.
     * If the new config does not feature any changes from the current config then
     * it is ignored and the confmon instance will proceed to the next provider.
     * This is done through a direct call to provider_failed(provider, LCB_SUCCESS).
     *
     * This function should _not_ be called outside of an asynchronous provider's
     * handler.
     *
     * @param provider the provider which yielded the new configuration
     * @param info the new configuration
     */
    void provider_got_config(Provider *which, ConfigInfo* config);

    /** Dump information about the monitor
     * @param mon the monitor object
     * @param fp the file to which information should be written
     */
    void dump(FILE *fp);

    Provider* get_provider(Method m) const {
        return all_providers[m];
    }

    /**
     * @brief Register a listener to be invoked on state changes and events
     *
     * Adds a 'listener' object to be called at each configuration update. The
     * listener may co-exist with other listeners (though it should never be added
     * twice). When a new configuration is received and accept, the listener's
     * Listener::callback field will be invoked with it.
     *
     * The callback will continue to be invoked for each new configuration received
     * until remove_listener is called. Note that the listener is not allocated
     * by the confmon and its responsibility is the user's
     *
     * @param mon the monitor
     * @param listener the listener. The listener's contents are not copied into
     * confmon and should thus remain valid until it is removed
     */
    void add_listener(Listener* lsn);

    /**
     * @brief Unregister (and remove) a listener added via lcb_confmon_add_listener()
     * @param mon the monitor
     * @param listener the listener
     */
    void remove_listener(Listener *lsn);

    /**Current provider. This provider may either fail or succeed.
     * In either case unless the provider can provide us with a specific
     * config which is newer than the one we have, it will roll over to the
     * next provider. */
    Provider *cur_provider;

    /** All providers we know about. Currently this means the 'builtin' providers */
    Provider* all_providers[CLCONFIG_MAX];

    /** The current configuration pointer. This contains the most recent accepted
     * configuration */
    ConfigInfo * config;

    typedef std::list<Listener*> ListenerList;
    /**  List of listeners for events */
    ListenerList listeners;

    lcb_settings *settings;
    lcb_error_t last_error;
    lcbio_pTABLE iot;

    /** This is the async handle for a reentrant start */
    lcb::io::Timer<Confmon, &Confmon::do_next_provider> as_start;

    /** Async handle for a reentrant stop */
    lcb::io::Timer<Confmon, &Confmon::stop_real> as_stop;

    /* CONFMON_S_* values. Used internally */
    int state;

    /** Last time the provider was stopped. As a microsecond timestamp */
    lcb_uint32_t last_stop_us;

    typedef std::list<Provider*> ProviderList;
    ProviderList active_providers;
};

/**
 * The base structure of a provider. This structure is intended to be
 * 'subclassed' by implementors.
 */
struct Provider {
    Provider(Confmon*, Method type_);

    /** Destroy the resources created by this provider. */
    virtual ~Provider();

    /**
     * Get the current map known to this provider. This should not perform
     * any blocking operations. Providers which use a push model may use
     * this method as an asynchronous return value for a previously-received
     * configuration.
     */
    virtual ConfigInfo* get_cached() = 0;


    /**
     * Request a new configuration. This will be called by the manager when
     * the cached configuration (i.e. 'get_cached') is deemed invalid. Thus
     * this function should unconditionally try to schedule getting the
     * newest configuration it can. When the configuration has been received,
     * the provider may call provider_success or provider_failed.
     *
     * @note
     * The PROVIDER is responsible for terminating its own
     * process. In other words there is no safeguard within confmon itself
     * against a provider taking an excessively long time; therefore a provider
     * should implement a timeout mechanism of its choice to promptly deliver
     * a success or failure.
     *
     * @param pb
     */
    virtual lcb_error_t refresh() = 0;

    /**
     * Callback invoked to the provider to indicate that it should cease
     * performing any "Active" configuration changes. Note that this is only
     * a hint and a provider may perform its own hooking based on this. In any
     * event receiving this callback is indicating that the provider will not
     * be needed again in quite some time. How long this "time" is can range
     * between 0 seconds and several minutes depending on how a user has
     * configured the client.
     * @param pb
     * @return true if actually paused
     */
    virtual bool pause() {
        return false;
    }

    /**
     * Called when a new configuration has been received.
     *
     * @param provider the provider instance
     * @param config the current configuration.
     * Note that this should only update the server list and do nothing
     * else.
     */
    virtual void config_updated(lcbvb_CONFIG*) {
    }

    /**
     * Retrieve the list of nodes from this provider, if applicable
     * @param p the provider
     * @return A list of nodes, or NULL if the provider does not have a list
     */
    virtual const lcb::Hostlist* get_nodes() const {
        return NULL;
    }

    /**
     * Call to change the configured nodes of this provider.
     * @param p The provider
     * @param l The list of nodes to apply
     */
    virtual void configure_nodes(const lcb::Hostlist&) {
    }

    /**
     * Dump state information. This callback is optional
     * @param p the provider
     * @param f the file to write to
     */
    virtual void dump(FILE *) const {
    }

    void enable() {
        enabled = 1;
    }

    virtual void enable(void *) {
        assert("Must be implemented in subclass if used" && 0);
    }

    /** The type of provider */
    const Method type;

    /** Whether this provider has been disabled/enabled explicitly by a user */
    bool enabled;

    /** The parent manager object */
    Confmon *parent;

    lcb_settings& settings() const {
        return *parent->settings;
    }
};

Provider *new_cccp_provider(Confmon*);
Provider *new_file_provider(Confmon*);
Provider *new_http_provider(Confmon*);
Provider *new_mcraw_provider(Confmon*);
Provider *new_cladmin_provider(Confmon*);


/** @brief refcounted object encapsulating a vbucket config */
class ConfigInfo {
public:
    /**
     * Creates a new configuration wrapper object containing the vbucket config
     * pointed to by 'config'. Its initial refcount will be set to 1.
     *
     * @param config a newly parsed configuration
     * @param origin the type of provider from which the config originated.
     * @return a new ConfigInfo object. This should be incref()'d/decref()'d
     * as needed.
     */
    static ConfigInfo* create(lcbvb_CONFIG *vbc, Method origin) {
        return new ConfigInfo(vbc, origin);
    }
    /**
     * @brief Compares two info structures and determine which one is newer
     *
     * This function returns an integer less than
     * zero, zero or greater than zero if the first argument is considered older
     * than, equal to, or later than the second argument.
     * @param a
     * @param b
     * @see lcbvb_get_revision
     * @see ConfigInfo::cmpclock
     */
    int compare(const ConfigInfo&);

    /**
     * @brief Increment the refcount on a config object
     */
    void incref() { refcount++; }

    /**
     * @brief Decrement the refcount on a config object.
     * Decrement the refcount. If the internal refcount reaches 0 then the internal
     * members (including the vbucket config handle itself) will be freed.
     * @param info the configuration
     */
    void decref() {
        if (!--refcount) {
            delete this;
        }
    }

    operator lcbvb_CONFIG*() const {
        return vbc;
    }

    Method get_origin() const {
        return origin;
    }

    /** Actual configuration */
    lcbvb_CONFIG* vbc;

private:
    ConfigInfo(lcbvb_CONFIG *vbc, Method origin);

    ~ConfigInfo();

    /** Comparative clock with which to compare */
    uint64_t cmpclock;

    /** Reference counter */
    unsigned int refcount;

    /** Origin provider type which produced this config */
    Method origin;
};

/**
 * @brief Listener for events
 * One or more listeners may be installed into the confmon which will have
 * a callback invoked on significant vbucket events. See clconfig_event_t
 * for a variety of events the listener can know.
 */
struct Listener {
    virtual ~Listener() {
    }

    /** Linked list node */
    lcb_list_t ll;

    /**
     * Callback invoked for significant events
     *
     * @param event the event which took place
     * @param config the configuration associated with the event. Note that
     * `config` may also be NULL
     */
    virtual void clconfig_lsn(EventType event, ConfigInfo *config) = 0;
};

/* Method-specific setup methods.. */

/**
 * @name File Provider-specific APIs
 * @{
 */

/**
 * Sets the input/output filename for the file provider. This also enables
 * the file provider.
 * @param p the provider
 * @param f the filename (if NULL, a temporary filename will be created)
 * @param ro whether the client will never modify the file
 * @return true on success, false on failure.
 */
bool file_set_filename(Provider *p, const char *f, bool ro);

/**
 * Retrieve the filename for the provider
 * @param p The provider of type LCB_CLCONFIG_FILE
 * @return the current filename being used.
 */
const char* file_get_filename(Provider *p);
void file_set_readonly(Provider *p, bool val);
/**@}*/

/**
 * @name HTTP Provider-specific APIs
 * @{
 */

/**
 * Get the socket representing the current REST connection to the cluster
 * (if applicable)
 * @param mon
 * @return
 */
const lcbio_SOCKET* http_get_conn(const Provider *p);

static inline const lcbio_SOCKET* http_get_conn(Confmon *c) {
    return http_get_conn(c->get_provider(CLCONFIG_HTTP));
}

/**
 * Get the hostname for the current REST connection to the cluster
 * @param mon
 * @return
 */
const lcb_host_t * http_get_host(const Provider *p);
static inline const lcb_host_t* http_get_host(Confmon *c) {
    return http_get_host(c->get_provider(CLCONFIG_HTTP));
}
/**@}*/

/**
 * @name CCCP Provider-specific APIs
 * @{
 */

/**
 * Note, to initialize the CCCP provider, you should use
 * cccp->enable(instance);
 */

/**
 * @brief Notify the CCCP provider about a new configuration from a
 * `NOT_MY_VBUCKET` response
 *
 * This should be called by the packet handler when a configuration has been
 * received as a payload to a response with the error of `NOT_MY_VBUCKET`.
 *
 * @param provider The CCCP provider
 * @param host The hostname (without the port) on which the packet was received
 * @param data The configuration JSON blob
 * @return LCB_SUCCESS, or an error code if the configuration could not be
 * set
 */
lcb_error_t
cccp_update(Provider *provider, const char *host, const char *data);

/**
 * @brief Notify the CCCP provider about a configuration received from a
 * `CMD_GET_CLUSTER_CONFIG` response.
 *
 * @param cookie The cookie object attached to the packet
 * @param err The error code for the reply
 * @param bytes The payload pointer
 * @param nbytes Size of payload
 * @param origin Host object from which the packet was received
 */
void cccp_update(const void *cookie, lcb_error_t err,
    const void *bytes, size_t nbytes, const lcb_host_t *origin);

/**@}*/

/**@name Raw Memcached (MCRAW) Provider-specific APIs
 * @{*/
/**@}*/
/**@}*/

} // clconfig
} // lcb
#endif /* LCB_CLCONFIG_H */
