/**
 * @file   llviewercontrollistener.cpp
 * @author Brad Kittenbrink
 * @date   2009-07-09
 * @brief  Implementation for llviewercontrollistener.
 *
 * $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$
 */

#include "llviewerprecompiledheaders.h"

#include "llviewercontrollistener.h"

#include "llviewercontrol.h"
#include "llcontrol.h"
#include "llerror.h"
#include "llsdutil.h"
#include "stringize.h"
#include <sstream>

namespace {

LLViewerControlListener sSavedSettingsListener;

} // unnamed namespace

LLViewerControlListener::LLViewerControlListener()
    : LLEventAPI("LLViewerControl",
                 "LLViewerControl listener: set, toggle or set default for various controls")
{
    std::ostringstream groupnames;
    groupnames << "[\"group\"] is one of ";
    const char* delim = "";
    for (const auto& key : LLControlGroup::key_snapshot())
    {
        groupnames << delim << '"' << key << '"';
        delim = ", ";
    }
    groupnames << '\n';
    std::string grouphelp(groupnames.str());
    std::string replyhelp("If [\"reply\"] requested, send new [\"value\"] on specified LLEventPump\n");

    add("set",
        std::string("Set [\"group\"] control [\"key\"] to optional value [\"value\"]\n"
                    "If [\"value\"] omitted, set to control's defined default value\n") +
        grouphelp + replyhelp,
        &LLViewerControlListener::set,
        LLSDMap("group", LLSD())("key", LLSD()));
    add("toggle",
        std::string("Toggle [\"group\"] control [\"key\"], if boolean\n") + grouphelp + replyhelp,
        &LLViewerControlListener::toggle,
        LLSDMap("group", LLSD())("key", LLSD()));
    add("get",
        std::string("Query [\"group\"] control [\"key\"], replying on LLEventPump [\"reply\"]\n") +
        grouphelp,
        &LLViewerControlListener::get,
        LLSDMap("group", LLSD())("key", LLSD())("reply", LLSD()));
    add("groups",
        "Send on LLEventPump [\"reply\"] an array [\"groups\"] of valid group names",
        &LLViewerControlListener::groups,
        LLSDMap("reply", LLSD()));
    add("vars",
        std::string("For [\"group\"], send on LLEventPump [\"reply\"] an array [\"vars\"],\n"
                    "each of whose entries looks like:\n"
                    "  [\"name\"], [\"type\"], [\"value\"], [\"comment\"]\n") + grouphelp,
        &LLViewerControlListener::vars,
        LLSDMap("group", LLSD())("reply", LLSD()));
}

struct Info
{
    Info(const LLSD& request):
        response(LLSD(), request),
        groupname(request["group"]),
        group(LLControlGroup::getInstance(groupname)),
        key(request["key"]),
        control(NULL)
    {
        if (! group)
        {
            response.error(STRINGIZE("Unrecognized group '" << groupname << "'"));
            return;
        }

        control = group->getControl(key);
        if (! control)
        {
            response.error(STRINGIZE("In group '" << groupname
                                     << "', unrecognized control key '" << key << "'"));
        }
    }

    ~Info()
    {
        // If in fact the request passed to our constructor names a valid
        // group and key, grab the final value of the indicated control and
        // stuff it in our response. Since this outer destructor runs before
        // the contained Response destructor, this data will go into the
        // response we send.
        if (control)
        {
            response["name"]    = control->getName();
            response["type"]    = LLControlGroup::typeEnumToString(control->type());
            response["value"]   = control->get();
            response["comment"] = control->getComment();
        }
    }

    LLEventAPI::Response response;
    std::string groupname;
    LLControlGroup::ptr_t group;
    std::string key;
    LLControlVariable* control;
};

//static
void LLViewerControlListener::set(LLSD const & request)
{
    Info info(request);
    if (! info.control)
        return;

    if (request.has("value"))
    {
        LL_WARNS("LLViewerControlListener") << "Changing debug setting " << std::quoted(info.key) << " to " << request["value"] << LL_ENDL;
        info.control->setValue(request["value"], false);
    }
    else
    {
        info.control->resetToDefault();
    }
}

//static
void LLViewerControlListener::toggle(LLSD const & request)
{
    Info info(request);
    if (! info.control)
        return;

    if (info.control->isType(TYPE_BOOLEAN))
    {
        bool value = !info.control->get().asBoolean();
        LL_WARNS("LLViewerControlListener") << "Toggling debug setting " << std::quoted(info.key) << " to " << value << LL_ENDL;
        info.control->set(value, false);
    }
    else
    {
        info.response.error(STRINGIZE("toggle of non-boolean '" << info.groupname
                                      << "' control '" << info.key
                                      << "', type is "
                                      << LLControlGroup::typeEnumToString(info.control->type())));
    }
}

void LLViewerControlListener::get(LLSD const & request)
{
    // The Info constructor and destructor actually do all the work here.
    Info info(request);
}

void LLViewerControlListener::groups(LLSD const & request)
{
    // No Info, we're not looking up either a group or a control name.
    Response response(LLSD(), request);
    for (const auto& key : LLControlGroup::key_snapshot())
    {
        response["groups"].append(key);
    }
}

struct CollectVars: public LLControlGroup::ApplyFunctor
{
    CollectVars(LLControlGroup::ptr_t g):
        mGroup(g)
    {}

    virtual void apply(const std::string& name, LLControlVariable* control)
    {
        vars.append(LLSDMap
                    ("name", name)
                    ("type", LLControlGroup::typeEnumToString(control->type()))
                    ("value", control->get())
                    ("comment", control->getComment()));
    }

    LLControlGroup::ptr_t mGroup;
    LLSD vars;
};

void LLViewerControlListener::vars(LLSD const & request)
{
    // This method doesn't use Info, because we're not looking up a specific
    // control name.
    Response response(LLSD(), request);
    std::string groupname(request["group"]);
    auto group(LLControlGroup::getInstance(groupname));
    if (! group)
    {
        return response.error(STRINGIZE("Unrecognized group '" << groupname << "'"));
    }

    CollectVars collector(group);
    group->applyToAll(&collector);
    response["vars"] = collector.vars;
}