/**
 * @file   lleventdispatcher.cpp
 * @author Nat Goodspeed
 * @date   2009-06-18
 * @brief  Implementation for lleventdispatcher.
 * 
 * $LicenseInfo:firstyear=2009&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 * 
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

#if LL_WINDOWS
#pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally
#endif

// Precompiled header
#include "linden_common.h"
// associated header
#include "lleventdispatcher.h"
// STL headers
// std headers
// external library headers
// other Linden headers
#include "llevents.h"
#include "llerror.h"
#include "llexception.h"
#include "llsdutil.h"
#include "stringize.h"
#include <memory>                   // std::auto_ptr

/*****************************************************************************
*   DispatchError
*****************************************************************************/
struct DispatchError: public LLException
{
    DispatchError(const std::string& what): LLException(what) {}
};

/*****************************************************************************
*   LLSDArgsSource
*****************************************************************************/
/**
 * Store an LLSD array, producing its elements one at a time. It is an error
 * if the consumer requests more elements than the array contains.
 */
class LL_COMMON_API LLSDArgsSource
{
public:
    LLSDArgsSource(const std::string function, const LLSD& args);
    ~LLSDArgsSource();

    LLSD next();

    void done() const;

private:
    std::string _function;
    LLSD _args;
    LLSD::Integer _index;
};

LLSDArgsSource::LLSDArgsSource(const std::string function, const LLSD& args):
    _function(function),
    _args(args),
    _index(0)
{
    if (! (_args.isUndefined() || _args.isArray()))
    {
        LLTHROW(DispatchError(stringize(_function, " needs an args array instead of ", _args)));
    }
}

LLSDArgsSource::~LLSDArgsSource()
{
    done();
}

LLSD LLSDArgsSource::next()
{
    if (_index >= _args.size())
    {
        LLTHROW(DispatchError(stringize(_function, " requires more arguments than the ",
                                        _args.size(), " provided: ", _args)));
    }
    return _args[_index++];
}

void LLSDArgsSource::done() const
{
    if (_index < _args.size())
    {
        LL_WARNS("LLSDArgsSource") << _function << " only consumed " << _index
                                   << " of the " << _args.size() << " arguments provided: "
                                   << _args << LL_ENDL;
    }
}

/*****************************************************************************
*   LLSDArgsMapper
*****************************************************************************/
/**
 * From a formal parameters description and a map of arguments, construct an
 * arguments array.
 *
 * That is, given:
 * - an LLSD array of length n containing parameter-name strings,
 *   corresponding to the arguments of a function of interest
 * - an LLSD collection specifying default parameter values, either:
 *   - an LLSD array of length m <= n, matching the rightmost m params, or
 *   - an LLSD map explicitly stating default name=value pairs
 * - an LLSD map of parameter names and actual values for a particular
 *   function call
 * construct an LLSD array of actual argument values for this function call.
 *
 * The parameter-names array and the defaults collection describe the function
 * being called. The map might vary with every call, providing argument values
 * for the described parameters.
 *
 * The array of parameter names must match the number of parameters expected
 * by the function of interest.
 *
 * If you pass a map of default parameter values, it provides default values
 * as you might expect. It is an error to specify a default value for a name
 * not listed in the parameters array.
 *
 * If you pass an array of default parameter values, it is mapped to the
 * rightmost m of the n parameter names. It is an error if the default-values
 * array is longer than the parameter-names array. Consider the following
 * parameter names: ["a", "b", "c", "d"].
 *
 * - An empty array of default values (or an isUndefined() value) asserts that
 *   every one of the above parameter names is required.
 * - An array of four default values [1, 2, 3, 4] asserts that every one of
 *   the above parameters is optional. If the current parameter map is empty,
 *   they will be passed to the function as [1, 2, 3, 4].
 * - An array of two default values [11, 12] asserts that parameters "a" and
 *   "b" are required, while "c" and "d" are optional, having default values
 *   "c"=11 and "d"=12.
 *
 * The arguments array is constructed as follows:
 *
 * - Arguments-map keys not found in the parameter-names array are ignored.
 * - Entries from the map provide values for an improper subset of the
 *   parameters named in the parameter-names array. This results in a
 *   tentative values array with "holes." (size of map) + (number of holes) =
 *   (size of names array)
 * - Holes are filled with the default values.
 * - Any remaining holes constitute an error.
 */
class LL_COMMON_API LLSDArgsMapper
{
public:
    /// Accept description of function: function name, param names, param
    /// default values
    LLSDArgsMapper(const std::string& function, const LLSD& names, const LLSD& defaults);

    /// Given arguments map, return LLSD::Array of parameter values, or
    /// trigger error.
    LLSD map(const LLSD& argsmap) const;

private:
    static std::string formatlist(const LLSD&);

    // The function-name string is purely descriptive. We want error messages
    // to be able to indicate which function's LLSDArgsMapper has the problem.
    std::string _function;
    // Store the names array pretty much as given.
    LLSD _names;
    // Though we're handed an array of name strings, it's more useful to us to
    // store it as a map from name string to position index. Of course that's
    // easy to generate from the incoming names array, but why do it more than
    // once?
    typedef std::map<LLSD::String, LLSD::Integer> IndexMap;
    IndexMap _indexes;
    // Generated array of default values, aligned with the array of param names.
    LLSD _defaults;
    // Indicate whether we have a default value for each param.
    typedef std::vector<char> FilledVector;
    FilledVector _has_dft;
};

LLSDArgsMapper::LLSDArgsMapper(const std::string& function,
                               const LLSD& names, const LLSD& defaults):
    _function(function),
    _names(names),
    _has_dft(names.size())
{
    if (! (_names.isUndefined() || _names.isArray()))
    {
        LLTHROW(DispatchError(stringize(function, " names must be an array, not ", names)));
    }
    LLSD::Integer nparams(_names.size());
    // From _names generate _indexes.
    for (LLSD::Integer ni = 0, nend = _names.size(); ni < nend; ++ni)
    {
        _indexes[_names[ni]] = ni;
    }

    // Presize _defaults() array so we don't have to resize it more than once.
    // All entries are initialized to LLSD(); but since _has_dft is still all
    // 0, they're all "holes" for now.
    if (nparams)
    {
        _defaults[nparams - 1] = LLSD();
    }

    if (defaults.isUndefined() || defaults.isArray())
    {
        LLSD::Integer ndefaults = defaults.size();
        // defaults is a (possibly empty) array. Right-align it with names.
        if (ndefaults > nparams)
        {
            LLTHROW(DispatchError(stringize(function, " names array ", names,
                                            " shorter than defaults array ", defaults)));
        }

        // Offset by which we slide defaults array right to right-align with
        // _names array
        LLSD::Integer offset = nparams - ndefaults;
        // Fill rightmost _defaults entries from defaults, and mark them as
        // filled
        for (LLSD::Integer i = 0, iend = ndefaults; i < iend; ++i)
        {
            _defaults[i + offset] = defaults[i];
            _has_dft[i + offset] = 1;
        }
    }
    else if (defaults.isMap())
    {
        // defaults is a map. Use it to populate the _defaults array.
        LLSD bogus;
        for (LLSD::map_const_iterator mi(defaults.beginMap()), mend(defaults.endMap());
             mi != mend; ++mi)
        {
            IndexMap::const_iterator ixit(_indexes.find(mi->first));
            if (ixit == _indexes.end())
            {
                bogus.append(mi->first);
                continue;
            }

            LLSD::Integer pos = ixit->second;
            // Store default value at that position in the _defaults array.
            _defaults[pos] = mi->second;
            // Don't forget to record the fact that we've filled this
            // position.
            _has_dft[pos] = 1;
        }
        if (bogus.size())
        {
            LLTHROW(DispatchError(stringize(function, " defaults specified for nonexistent params ",
                                            formatlist(bogus))));
        }
    }
    else
    {
        LLTHROW(DispatchError(stringize(function, " defaults must be a map or an array, not ",
                                        defaults)));
    }
}

LLSD LLSDArgsMapper::map(const LLSD& argsmap) const
{
    if (! (argsmap.isUndefined() || argsmap.isMap() || argsmap.isArray()))
    {
        LLTHROW(DispatchError(stringize(_function, " map() needs a map or array, not ",
                                        argsmap)));
    }
    // Initialize the args array. Indexing a non-const LLSD array grows it
    // to appropriate size, but we don't want to resize this one on each
    // new operation. Just make it as big as we need before we start
    // stuffing values into it.
    LLSD args(LLSD::emptyArray());
    if (_defaults.size() == 0)
    {
        // If this function requires no arguments, fast exit. (Don't try to
        // assign to args[-1].)
        return args;
    }
    args[_defaults.size() - 1] = LLSD();

    // Get a vector of chars to indicate holes. It's tempting to just scan
    // for LLSD::isUndefined() values after filling the args array from
    // the map, but it's plausible for caller to explicitly pass
    // isUndefined() as the value of some parameter name. That's legal
    // since isUndefined() has well-defined conversions (default value)
    // for LLSD data types. So use a whole separate array for detecting
    // holes. (Avoid std::vector<bool> which is known to be odd -- can we
    // iterate?)
    FilledVector filled(args.size());

    if (argsmap.isArray())
    {
        // Fill args from array. If there are too many args in passed array,
        // ignore the rest.
        LLSD::Integer size(argsmap.size());
        if (size > args.size())
        {
            // We don't just use std::min() because we want to sneak in this
            // warning if caller passes too many args.
            LL_WARNS("LLSDArgsMapper") << _function << " needs " << args.size()
                                       << " params, ignoring last " << (size - args.size())
                                       << " of passed " << size << ": " << argsmap << LL_ENDL;
            size = args.size();
        }
        for (LLSD::Integer i(0); i < size; ++i)
        {
            // Copy the actual argument from argsmap
            args[i] = argsmap[i];
            // Note that it's been filled
            filled[i] = 1;
        }
    }
    else
    {
        // argsmap is in fact a map. Walk the map.
        for (LLSD::map_const_iterator mi(argsmap.beginMap()), mend(argsmap.endMap());
             mi != mend; ++mi)
        {
            // mi->first is a parameter-name string, with mi->second its
            // value. Look up the name's position index in _indexes.
            IndexMap::const_iterator ixit(_indexes.find(mi->first));
            if (ixit == _indexes.end())
            {
                // Allow for a map containing more params than were passed in
                // our names array. Caller typically receives a map containing
                // the function name, cruft such as reqid, etc. Ignore keys
                // not defined in _indexes.
                LL_DEBUGS("LLSDArgsMapper") << _function << " ignoring "
                                            << mi->first << "=" << mi->second << LL_ENDL;
                continue;
            }
            LLSD::Integer pos = ixit->second;
            // Store the value at that position in the args array.
            args[pos] = mi->second;
            // Don't forget to record the fact that we've filled this
            // position.
            filled[pos] = 1;
        }
    }

    // Fill any remaining holes from _defaults.
    LLSD unfilled(LLSD::emptyArray());
    for (LLSD::Integer i = 0, iend = args.size(); i < iend; ++i)
    {
        if (! filled[i])
        {
            // If there's no default value for this parameter, that's an
            // error.
            if (! _has_dft[i])
            {
                unfilled.append(_names[i]);
            }
            else
            {
                args[i] = _defaults[i];
            }
        }
    }
    // If any required args -- args without defaults -- were left unfilled
    // by argsmap, that's a problem.
    if (unfilled.size())
    {
        LLTHROW(DispatchError(stringize(_function, " missing required arguments ",
                                        formatlist(unfilled), " from ", argsmap)));
    }

    // done
    return args;
}

std::string LLSDArgsMapper::formatlist(const LLSD& list)
{
    std::ostringstream out;
    const char* delim = "";
    for (LLSD::array_const_iterator li(list.beginArray()), lend(list.endArray());
         li != lend; ++li)
    {
        out << delim << li->asString();
        delim = ", ";
    }
    return out.str();
}

LLEventDispatcher::LLEventDispatcher(const std::string& desc, const std::string& key):
    mDesc(desc),
    mKey(key)
{
}

/**
 * DispatchEntry subclass used for callables accepting(const LLSD&)
 */
struct LLEventDispatcher::LLSDDispatchEntry: public LLEventDispatcher::DispatchEntry
{
    LLSDDispatchEntry(const std::string& desc, const Callable& func, const LLSD& required):
        DispatchEntry(desc),
        mFunc(func),
        mRequired(required)
    {}

    Callable mFunc;
    LLSD mRequired;

    virtual void call(const std::string& desc, const LLSD& event) const
    {
        // Validate the syntax of the event itself.
        std::string mismatch(llsd_matches(mRequired, event));
        if (! mismatch.empty())
        {
            LLTHROW(DispatchError(stringize(desc, ": bad request: ", mismatch)));
        }
        // Event syntax looks good, go for it!
        mFunc(event);
    }

    virtual LLSD addMetadata(LLSD meta) const
    {
        meta["required"] = mRequired;
        return meta;
    }
};

/**
 * DispatchEntry subclass for passing LLSD to functions accepting
 * arbitrary argument types (convertible via LLSDParam)
 */
struct LLEventDispatcher::ParamsDispatchEntry: public LLEventDispatcher::DispatchEntry
{
    ParamsDispatchEntry(const std::string& desc, const invoker_function& func):
        DispatchEntry(desc),
        mInvoker(func)
    {}

    invoker_function mInvoker;

    virtual void call(const std::string& desc, const LLSD& event) const
    {
        LLSDArgsSource src(desc, event);
        mInvoker(boost::bind(&LLSDArgsSource::next, boost::ref(src)));
    }
};

/**
 * DispatchEntry subclass for dispatching LLSD::Array to functions accepting
 * arbitrary argument types (convertible via LLSDParam)
 */
struct LLEventDispatcher::ArrayParamsDispatchEntry: public LLEventDispatcher::ParamsDispatchEntry
{
    ArrayParamsDispatchEntry(const std::string& desc, const invoker_function& func,
                             LLSD::Integer arity):
        ParamsDispatchEntry(desc, func),
        mArity(arity)
    {}

    LLSD::Integer mArity;

    virtual LLSD addMetadata(LLSD meta) const
    {
        LLSD array(LLSD::emptyArray());
        // Resize to number of arguments required
        if (mArity)
            array[mArity - 1] = LLSD();
        llassert_always(array.size() == mArity);
        meta["required"] = array;
        return meta;
    }
};

/**
 * DispatchEntry subclass for dispatching LLSD::Map to functions accepting
 * arbitrary argument types (convertible via LLSDParam)
 */
struct LLEventDispatcher::MapParamsDispatchEntry: public LLEventDispatcher::ParamsDispatchEntry
{
    MapParamsDispatchEntry(const std::string& name, const std::string& desc,
                           const invoker_function& func,
                           const LLSD& params, const LLSD& defaults):
        ParamsDispatchEntry(desc, func),
        mMapper(name, params, defaults),
        mRequired(LLSD::emptyMap())
    {
        // Build the set of all param keys, then delete the ones that are
        // optional. What's left are the ones that are required.
        for (LLSD::array_const_iterator pi(params.beginArray()), pend(params.endArray());
             pi != pend; ++pi)
        {
            mRequired[pi->asString()] = LLSD();
        }

        if (defaults.isArray() || defaults.isUndefined())
        {
            // Right-align the params and defaults arrays.
            LLSD::Integer offset = params.size() - defaults.size();
            // Now the name of every defaults[i] is at params[i + offset].
            for (LLSD::Integer i(0), iend(defaults.size()); i < iend; ++i)
            {
                // Erase this optional param from mRequired.
                mRequired.erase(params[i + offset].asString());
                // Instead, make an entry in mOptional with the default
                // param's name and value.
                mOptional[params[i + offset].asString()] = defaults[i];
            }
        }
        else if (defaults.isMap())
        {
            // if defaults is already a map, then it's already in the form we
            // intend to deliver in metadata
            mOptional = defaults;
            // Just delete from mRequired every key appearing in mOptional.
            for (LLSD::map_const_iterator mi(mOptional.beginMap()), mend(mOptional.endMap());
                 mi != mend; ++mi)
            {
                mRequired.erase(mi->first);
            }
        }
    }

    LLSDArgsMapper mMapper;
    LLSD mRequired;
    LLSD mOptional;

    virtual void call(const std::string& desc, const LLSD& event) const
    {
        // Just convert from LLSD::Map to LLSD::Array using mMapper, then pass
        // to base-class call() method.
        ParamsDispatchEntry::call(desc, mMapper.map(event));
    }

    virtual LLSD addMetadata(LLSD meta) const
    {
        meta["required"] = mRequired;
        meta["optional"] = mOptional;
        return meta;
    }
};

void LLEventDispatcher::addArrayParamsDispatchEntry(const std::string& name,
                                                    const std::string& desc,
                                                    const invoker_function& invoker,
                                                    LLSD::Integer arity)
{
    mDispatch.insert(
        DispatchMap::value_type(name, std::static_pointer_cast<DispatchEntry>(
                                    std::make_shared<ArrayParamsDispatchEntry>(desc, invoker, arity))));
}

void LLEventDispatcher::addMapParamsDispatchEntry(const std::string& name,
                                                  const std::string& desc,
                                                  const invoker_function& invoker,
                                                  const LLSD& params,
                                                  const LLSD& defaults)
{
    mDispatch.insert(
        DispatchMap::value_type(name, std::static_pointer_cast<DispatchEntry>(
                                    std::make_shared<MapParamsDispatchEntry>(name, desc, invoker, params, defaults))));
}

/// Register a callable by name
void LLEventDispatcher::add(const std::string& name, const std::string& desc,
                            const Callable& callable, const LLSD& required)
{
    mDispatch.insert(
        DispatchMap::value_type(name, std::static_pointer_cast<
                                    DispatchEntry>(std::make_shared<LLSDDispatchEntry>(desc, callable, required))));
}

void LLEventDispatcher::addFail(const std::string& name, const std::string& classname) const
{
    LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << ")::add(" << name
                                 << "): " << classname << " is not a subclass "
                                 << "of LLEventDispatcher" << LL_ENDL;
}

/// Unregister a callable
bool LLEventDispatcher::remove(const std::string& name)
{
    DispatchMap::iterator found = mDispatch.find(name);
    if (found == mDispatch.end())
    {
        return false;
    }
    mDispatch.erase(found);
    return true;
}

/// Call a registered callable with an explicitly-specified name. It is an
/// error if no such callable exists.
void LLEventDispatcher::operator()(const std::string& name, const LLSD& event) const
{
    std::string error{ try_call_log(std::string(), name, event) };
    if (! error.empty())
    {
        callFail(event, error);
    }
}

/// Extract the @a key value from the incoming @a event, and call the callable
/// whose name is specified by that map @a key. It is an error if no such
/// callable exists.
void LLEventDispatcher::operator()(const LLSD& event) const
{
    std::string error{ try_call_log(mKey, event[mKey], event) };
    if (! error.empty())
    {
        callFail(event, error);
    }
}

void LLEventDispatcher::callFail(const LLSD& event, const std::string& msg) const
{
    static LLSD::String key{ "reply" };
    if (event.has(key))
    {
        // Oh good, the incoming event specifies a reply pump -- pass back a
        // response that includes an "error" key with the message.
        sendReply(llsd::map("error", msg), event, key);
    }
}

bool LLEventDispatcher::try_call(const LLSD& event) const
{
    return try_call_log(mKey, event[mKey], event).empty();
}

bool LLEventDispatcher::try_call(const std::string& name, const LLSD& event) const
{
    return try_call_log(std::string(), name, event).empty();
}

std::string LLEventDispatcher::try_call_log(const std::string& key, const std::string& name,
                                            const LLSD& event) const
{
    std::string error{ try_call(key, name, event) };
    if (! error.empty())
    {
        LL_WARNS("LLEventDispatcher") << error << LL_ENDL;
    }
    return error;
}

// This internal method returns empty string if the call succeeded, else
// non-empty error message.
std::string LLEventDispatcher::try_call(const std::string& key, const std::string& name,
                                        const LLSD& event) const
{
    DispatchMap::const_iterator found = mDispatch.find(name);
    if (found == mDispatch.end())
    {
        if (key.empty())
        {
            return stringize("LLEventDispatcher(", mDesc, "): '", name, "' not found");
        }
        else
        {
            return stringize("LLEventDispatcher(", mDesc, "): bad ", key, " value '", name, "'");
        }
    }

    try
    {
        // Found the name, so it's plausible to even attempt the call.
        found->second->call(stringize("LLEventDispatcher(", mDesc, ") calling '", name, "'"),
                            event);
    }
    catch (const DispatchError& err)
    {
        return err.what();
    }
    return {};                      // tell caller we were able to call
}

LLSD LLEventDispatcher::getMetadata(const std::string& name) const
{
    DispatchMap::const_iterator found = mDispatch.find(name);
    if (found == mDispatch.end())
    {
        return LLSD();
    }
    LLSD meta;
    meta["name"] = name;
    meta["desc"] = found->second->mDesc;
    return found->second->addMetadata(meta);
}

LLDispatchListener::LLDispatchListener(const std::string& pumpname, const std::string& key):
    LLEventDispatcher(pumpname, key),
    mPump(pumpname, true),          // allow tweaking for uniqueness
    mBoundListener(mPump.listen("self", boost::bind(&LLDispatchListener::process, this, _1)))
{
}

bool LLDispatchListener::process(const LLSD& event)
{
    (*this)(event);
    return false;
}

LLEventDispatcher::DispatchEntry::DispatchEntry(const std::string& desc):
    mDesc(desc)
{}

