/**
* @file llnotifications.cpp
* @brief Non-UI queue manager for keeping a prioritized list of notifications
*
* $LicenseInfo:firstyear=2008&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 "linden_common.h"

#include "llnotifications.h"
#include "llnotificationtemplate.h"
#include "llnotificationvisibilityrule.h"

#include "llavatarnamecache.h"
#include "llinstantmessage.h"
#include "llcachename.h"
#include "llxmlnode.h"
#include "lluictrl.h"
#include "lluictrlfactory.h"
#include "lldir.h"
#include "llsdserialize.h"
#include "lltrans.h"
#include "llstring.h"
#include "llsdparam.h"
#include "llsdutil.h"

#include <algorithm>
#include <boost/regex.hpp>


const std::string NOTIFICATION_PERSIST_VERSION = "0.93";

void NotificationPriorityValues::declareValues()
{
    declare("low", NOTIFICATION_PRIORITY_LOW);
    declare("normal", NOTIFICATION_PRIORITY_NORMAL);
    declare("high", NOTIFICATION_PRIORITY_HIGH);
    declare("critical", NOTIFICATION_PRIORITY_CRITICAL);
}

LLNotificationForm::FormElementBase::FormElementBase()
:   name("name"),
    enabled("enabled", true)
{}

LLNotificationForm::FormIgnore::FormIgnore()
:   text("text"),
    control("control"),
    invert_control("invert_control", false),
    save_option("save_option", false),
    session_only("session_only", false),
    checkbox_only("checkbox_only", false)
{}

LLNotificationForm::FormButton::FormButton()
:   index("index"),
    text("text"),
    ignore("ignore"),
    is_default("default"),
    width("width", 0),
    type("type")
{
    // set type here so it gets serialized
    type = "button";
}

LLNotificationForm::FormInput::FormInput()
:   type("type"),
    text("text"),
    max_length_chars("max_length_chars"),
    allow_emoji("allow_emoji"),
    width("width", 0),
    value("value")
{}

LLNotificationForm::FormElement::FormElement()
:   button("button"),
    input("input")
{}

LLNotificationForm::FormElements::FormElements()
:   elements("")
{}

LLNotificationForm::Params::Params()
:   name("name"),
    ignore("ignore"),
    form_elements("")
{}



bool filterIgnoredNotifications(LLNotificationPtr notification)
{
    LLNotificationFormPtr form = notification->getForm();
    // Check to see if the user wants to ignore this alert
    return !notification->getForm()->getIgnored();
}

bool handleIgnoredNotification(const LLSD& payload)
{
    if (payload["sigtype"].asString() == "add")
    {
        LLNotificationPtr pNotif = LLNotifications::instance().find(payload["id"].asUUID());
        if (!pNotif) return false;

        LLNotificationFormPtr form = pNotif->getForm();
        LLSD response;
        switch(form->getIgnoreType())
        {
        case LLNotificationForm::IGNORE_WITH_DEFAULT_RESPONSE:
        case LLNotificationForm::IGNORE_WITH_DEFAULT_RESPONSE_SESSION_ONLY:
            response = pNotif->getResponseTemplate(LLNotification::WITH_DEFAULT_BUTTON);
            break;
        case LLNotificationForm::IGNORE_WITH_LAST_RESPONSE:
            response = LLUI::getInstance()->mSettingGroups["ignores"]->getLLSD("Default" + pNotif->getName());
            break;
        case LLNotificationForm::IGNORE_SHOW_AGAIN:
            break;
        default:
            return false;
        }
        pNotif->setIgnored(true);
        pNotif->respond(response);
        return true;    // don't process this item any further
    }
    return false;
}

bool defaultResponse(const LLSD& payload)
{
    if (payload["sigtype"].asString() == "add")
    {
        LLNotificationPtr pNotif = LLNotifications::instance().find(payload["id"].asUUID());
        if (pNotif)
        {
            // supply default response
            pNotif->respond(pNotif->getResponseTemplate(LLNotification::WITH_DEFAULT_BUTTON));
        }
    }
    return false;
}

bool visibilityRuleMached(const LLSD& payload)
{
    // This is needed because LLNotifications::isVisibleByRules may have cancelled the notification.
    // Returning true here makes LLNotificationChannelBase::updateItem do an early out, which prevents things from happening in the wrong order.
    return true;
}


namespace LLNotificationFilters
{
    // a sample filter
    bool includeEverything(LLNotificationPtr p)
    {
        return true;
    }
};

LLNotificationForm::LLNotificationForm()
:   mIgnore(IGNORE_NO)
{
}

LLNotificationForm::LLNotificationForm( const LLNotificationForm& other )
{
    mFormData      = other.mFormData;
    mIgnore        = other.mIgnore;
    mIgnoreMsg     = other.mIgnoreMsg;
    mIgnoreSetting = other.mIgnoreSetting;
    mInvertSetting = other.mInvertSetting;
}

LLNotificationForm::LLNotificationForm(const std::string& name, const LLNotificationForm::Params& p)
:   mIgnore(IGNORE_NO),
    mInvertSetting(false) // ignore settings by default mean true=show, false=ignore
{
    if (p.ignore.isProvided())
    {
        // For all cases but IGNORE_CHECKBOX_ONLY this is name for use in preferences
        mIgnoreMsg = p.ignore.text;

        LLUI *ui_inst = LLUI::getInstance();
        if (p.ignore.checkbox_only)
        {
            mIgnore = IGNORE_CHECKBOX_ONLY;
        }
        else if (!p.ignore.save_option)
        {
            mIgnore = p.ignore.session_only ? IGNORE_WITH_DEFAULT_RESPONSE_SESSION_ONLY : IGNORE_WITH_DEFAULT_RESPONSE;
        }
        else
        {
            // remember last option chosen by user and automatically respond with that in the future
            mIgnore = IGNORE_WITH_LAST_RESPONSE;
            ui_inst->mSettingGroups["ignores"]->declareLLSD(std::string("Default") + name, "", std::string("Default response for notification " + name));
        }

        BOOL show_notification = TRUE;
        if (p.ignore.control.isProvided())
        {
            mIgnoreSetting = ui_inst->mSettingGroups["config"]->getControl(p.ignore.control);
            mInvertSetting = p.ignore.invert_control;
        }
        else if (mIgnore > IGNORE_NO)
        {
            ui_inst->mSettingGroups["ignores"]->declareBOOL(name, show_notification, "Show notification with this name", LLControlVariable::PERSIST_NONDFT);
            mIgnoreSetting = ui_inst->mSettingGroups["ignores"]->getControl(name);
        }
    }

    LLParamSDParser parser;
    parser.writeSD(mFormData, p.form_elements);

    for (LLSD::array_iterator it = mFormData.beginArray(), end_it = mFormData.endArray();
        it != end_it;
        ++it)
    {
        // lift contents of form element up a level, since element type is already encoded in "type" param
        if (it->isMap() && it->beginMap() != it->endMap())
        {
            *it = it->beginMap()->second;
        }
    }

    LL_DEBUGS("Notifications") << name << LL_ENDL;
    LL_DEBUGS("Notifications") << ll_pretty_print_sd(mFormData) << LL_ENDL;
}

LLNotificationForm::LLNotificationForm(const LLSD& sd)
    : mIgnore(IGNORE_NO)
{
    if (sd.isArray())
    {
        mFormData = sd;
    }
    else
    {
        LL_WARNS("Notifications") << "Invalid form data " << sd << LL_ENDL;
        mFormData = LLSD::emptyArray();
    }
}

LLSD LLNotificationForm::asLLSD() const
{
    return mFormData;
}

LLSD LLNotificationForm::getElement(const std::string& element_name)
{
    for (LLSD::array_const_iterator it = mFormData.beginArray();
        it != mFormData.endArray();
        ++it)
    {
        if ((*it)["name"].asString() == element_name) return (*it);
    }
    return LLSD();
}


bool LLNotificationForm::hasElement(const std::string& element_name) const
{
    for (LLSD::array_const_iterator it = mFormData.beginArray();
        it != mFormData.endArray();
        ++it)
    {
        if ((*it)["name"].asString() == element_name) return true;
    }
    return false;
}

void LLNotificationForm::getElements(LLSD& elements, S32 offset)
{
    //Finds elements that the template did not add
    LLSD::array_const_iterator it = mFormData.beginArray() + offset;

    //Keeps track of only the dynamic elements
    for(; it != mFormData.endArray(); ++it)
    {
        elements.append(*it);
    }
}

bool LLNotificationForm::getElementEnabled(const std::string& element_name) const
{
    for (LLSD::array_const_iterator it = mFormData.beginArray();
        it != mFormData.endArray();
        ++it)
    {
        if ((*it)["name"].asString() == element_name)
        {
            return (*it)["enabled"].asBoolean();
        }
    }

    return false;
}

void LLNotificationForm::setElementEnabled(const std::string& element_name, bool enabled)
{
    for (LLSD::array_iterator it = mFormData.beginArray();
        it != mFormData.endArray();
        ++it)
    {
        if ((*it)["name"].asString() == element_name)
        {
            (*it)["enabled"] = enabled;
        }
    }
}


void LLNotificationForm::addElement(const std::string& type, const std::string& name, const LLSD& value, bool enabled)
{
    LLSD element;
    element["type"] = type;
    element["name"] = name;
    element["text"] = name;
    element["value"] = value;
    element["index"] = LLSD::Integer(mFormData.size());
    element["enabled"] = enabled;
    mFormData.append(element);
}

void LLNotificationForm::append(const LLSD& sub_form)
{
    if (sub_form.isArray())
    {
        for (LLSD::array_const_iterator it = sub_form.beginArray();
            it != sub_form.endArray();
            ++it)
        {
            mFormData.append(*it);
        }
    }
}

void LLNotificationForm::formatElements(const LLSD& substitutions)
{
    for (LLSD::array_iterator it = mFormData.beginArray();
        it != mFormData.endArray();
        ++it)
    {
        // format "text" component of each form element
        if ((*it).has("text"))
        {
            std::string text = (*it)["text"].asString();
            LLStringUtil::format(text, substitutions);
            (*it)["text"] = text;
        }
        if ((*it)["type"].asString() == "text" && (*it).has("value"))
        {
            std::string value = (*it)["value"].asString();
            LLStringUtil::format(value, substitutions);
            (*it)["value"] = value;
        }
    }
}

std::string LLNotificationForm::getDefaultOption()
{
    for (LLSD::array_const_iterator it = mFormData.beginArray();
        it != mFormData.endArray();
        ++it)
    {
        if ((*it)["default"]) return (*it)["name"].asString();
    }
    return "";
}

LLControlVariablePtr LLNotificationForm::getIgnoreSetting()
{
    return mIgnoreSetting;
}

bool LLNotificationForm::getIgnored()
{
    bool show = true;
    if (mIgnore > LLNotificationForm::IGNORE_NO
        && mIgnoreSetting)
    {
        show = mIgnoreSetting->getValue().asBoolean();
        if (mInvertSetting) show = !show;
    }
    return !show;
}

void LLNotificationForm::setIgnored(bool ignored)
{
    if (mIgnoreSetting)
    {
        if (mInvertSetting) ignored = !ignored;
        mIgnoreSetting->setValue(!ignored);
    }
}

LLNotificationTemplate::LLNotificationTemplate(const LLNotificationTemplate::Params& p)
:   mName(p.name),
    mType(p.type),
    mMessage(p.value),
    mFooter(p.footer.value),
    mLabel(p.label),
    mIcon(p.icon),
    mURL(p.url.value),
    mExpireSeconds(p.duration),
    mExpireOption(p.expire_option),
    mURLOption(p.url.option),
    mURLTarget(p.url.target),
    mForceUrlsExternal(p.force_urls_external),
    mUnique(p.unique.isProvided()),
    mCombineBehavior(p.unique.combine),
    mPriority(p.priority),
    mPersist(p.persist),
    mDefaultFunctor(p.functor.isProvided() ? p.functor() : p.name()),
    mLogToChat(p.log_to_chat),
    mLogToIM(p.log_to_im),
    mShowToast(p.show_toast),
    mFadeToast(p.fade_toast),
    mSoundName("")
{
    if (p.sound.isProvided()
        && LLUI::getInstance()->mSettingGroups["config"]->controlExists(p.sound))
    {
        mSoundName = p.sound;
    }

    for (const LLNotificationTemplate::UniquenessContext& context : p.unique.contexts)
    {
        mUniqueContext.push_back(context.value);
    }

    LL_DEBUGS("Notifications") << "notification \"" << mName << "\": tag count is " << p.tags.size() << LL_ENDL;

    for (const LLNotificationTemplate::Tag& tag : p.tags)
    {
        LL_DEBUGS("Notifications") << "    tag \"" << std::string(tag.value) << "\"" << LL_ENDL;
        mTags.push_back(tag.value);
    }

    mForm = LLNotificationFormPtr(new LLNotificationForm(p.name, p.form_ref.form));
}

LLNotificationVisibilityRule::LLNotificationVisibilityRule(const LLNotificationVisibilityRule::Rule &p)
{
    if (p.show.isChosen())
    {
        mType = p.show.type;
        mTag = p.show.tag;
        mName = p.show.name;
        mVisible = true;
    }
    else if (p.hide.isChosen())
    {
        mType = p.hide.type;
        mTag = p.hide.tag;
        mName = p.hide.name;
        mVisible = false;
    }
    else if (p.respond.isChosen())
    {
        mType = p.respond.type;
        mTag = p.respond.tag;
        mName = p.respond.name;
        mVisible = false;
        mResponse = p.respond.response;
    }
}

LLNotification::LLNotification(const LLSDParamAdapter<Params>& p) :
    mTimestamp(p.time_stamp),
    mSubstitutions(p.substitutions),
    mPayload(p.payload),
    mExpiresAt(p.expiry),
    mTemporaryResponder(false),
    mRespondedTo(false),
    mPriority(p.priority),
    mCancelled(false),
    mIgnored(false),
    mResponderObj(NULL),
    mId(p.id.isProvided() ? p.id : LLUUID::generateNewID()),
    mOfferFromAgent(p.offer_from_agent),
    mIsDND(p.is_dnd)
{
    if (p.functor.name.isChosen())
    {
        mResponseFunctorName = p.functor.name;
    }
    else if (p.functor.function.isChosen())
    {
        mResponseFunctorName = LLUUID::generateNewID().asString();
        LLNotificationFunctorRegistry::instance().registerFunctor(mResponseFunctorName, p.functor.function());

        mTemporaryResponder = true;
    }
    else if(p.functor.responder.isChosen())
    {
        mResponder = p.functor.responder;
    }

    if(p.responder.isProvided())
    {
        mResponderObj = p.responder;
    }

    init(p.name, p.form_elements);
}


LLSD LLNotification::asLLSD(bool excludeTemplateElements)
{
    LLParamSDParser parser;

    Params p;
    p.id = mId;
    p.name = mTemplatep->mName;
    p.substitutions = mSubstitutions;
    p.payload = mPayload;
    p.time_stamp = mTimestamp;
    p.expiry = mExpiresAt;
    p.priority = mPriority;

    LLNotificationFormPtr templateForm = mTemplatep->mForm;
    LLSD formElements = mForm->asLLSD();

    //All form elements (dynamic or not)
    if(!excludeTemplateElements)
    {
        p.form_elements = formElements;
    }
    //Only dynamic form elements (exclude template elements)
    else if(templateForm->getNumElements() < formElements.size())
    {
        LLSD dynamicElements;
        //Offset to dynamic elements and store them
        mForm->getElements(dynamicElements, templateForm->getNumElements());
        p.form_elements = dynamicElements;
    }

    if(mResponder)
    {
        p.functor.responder_sd = mResponder->asLLSD();
    }

    if(!mResponseFunctorName.empty())
    {
        p.functor.name = mResponseFunctorName;
    }

    LLSD output;
    parser.writeSD(output, p);
    return output;
}

void LLNotification::update()
{
    LLNotifications::instance().update(shared_from_this());
}

void LLNotification::updateFrom(LLNotificationPtr other)
{
    // can only update from the same notification type
    if (mTemplatep != other->mTemplatep) return;

    // NOTE: do NOT change the ID, since it is the key to
    // this given instance, just update all the metadata
    //mId = other->mId;

    mPayload = other->mPayload;
    mSubstitutions = other->mSubstitutions;
    mTimestamp = other->mTimestamp;
    mExpiresAt = other->mExpiresAt;
    mCancelled = other->mCancelled;
    mIgnored = other->mIgnored;
    mPriority = other->mPriority;
    mForm = other->mForm;
    mResponseFunctorName = other->mResponseFunctorName;
    mRespondedTo = other->mRespondedTo;
    mResponse = other->mResponse;
    mTemporaryResponder = other->mTemporaryResponder;

    update();
}

const LLNotificationFormPtr LLNotification::getForm()
{
    return mForm;
}

void LLNotification::cancel()
{
    mCancelled = true;
}

LLSD LLNotification::getResponseTemplate(EResponseTemplateType type)
{
    LLSD response = LLSD::emptyMap();
    for (S32 element_idx = 0;
        element_idx < mForm->getNumElements();
        ++element_idx)
    {
        LLSD element = mForm->getElement(element_idx);
        if (element.has("name"))
        {
            response[element["name"].asString()] = element["value"];
        }

        if ((type == WITH_DEFAULT_BUTTON)
            && element["default"].asBoolean())
        {
            response[element["name"].asString()] = true;
        }
    }
    return response;
}

//static
S32 LLNotification::getSelectedOption(const LLSD& notification, const LLSD& response)
{
    LLNotificationForm form(notification["form"]);

    for (S32 element_idx = 0;
        element_idx < form.getNumElements();
        ++element_idx)
    {
        LLSD element = form.getElement(element_idx);

        // only look at buttons
        if (element["type"].asString() == "button"
            && response[element["name"].asString()].asBoolean())
        {
            return element["index"].asInteger();
        }
    }

    return -1;
}

//static
std::string LLNotification::getSelectedOptionName(const LLSD& response)
{
    for (LLSD::map_const_iterator response_it = response.beginMap();
        response_it != response.endMap();
        ++response_it)
    {
        if (response_it->second.isBoolean() && response_it->second.asBoolean())
        {
            return response_it->first;
        }
    }
    return "";
}


void LLNotification::respond(const LLSD& response)
{
    // *TODO may remove mRespondedTo and use mResponce.isDefined() in isRespondedTo()
    mRespondedTo = true;
    mResponse = response;

    if(mResponder)
    {
        mResponder->handleRespond(asLLSD(), response);
    }
    else if (!mResponseFunctorName.empty())
    {
        // look up the functor
        LLNotificationFunctorRegistry::ResponseFunctor functor =
            LLNotificationFunctorRegistry::instance().getFunctor(mResponseFunctorName);
        // and then call it
        functor(asLLSD(), response);
    }
    else if (mCombinedNotifications.empty())
    {
        // no registered responder
        return;
    }

    if (mTemporaryResponder)
    {
        LLNotificationFunctorRegistry::instance().unregisterFunctor(mResponseFunctorName);
        mResponseFunctorName = "";
        mTemporaryResponder = false;
    }

    if (mForm->getIgnoreType() > LLNotificationForm::IGNORE_NO)
    {
        mForm->setIgnored(mIgnored);
        if (mIgnored && mForm->getIgnoreType() == LLNotificationForm::IGNORE_WITH_LAST_RESPONSE)
        {
            LLUI::getInstance()->mSettingGroups["ignores"]->setLLSD("Default" + getName(), response);
        }
    }

    for (std::vector<LLNotificationPtr>::const_iterator it = mCombinedNotifications.begin(); it != mCombinedNotifications.end(); ++it)
    {
        if ((*it))
        {
            (*it)->respond(response);
        }
    }

    update();
}

void LLNotification::respondWithDefault()
{
    respond(getResponseTemplate(WITH_DEFAULT_BUTTON));
}


const std::string& LLNotification::getName() const
{
    return mTemplatep->mName;
}

const std::string& LLNotification::getIcon() const
{
    return mTemplatep->mIcon;
}


bool LLNotification::isPersistent() const
{
    return mTemplatep->mPersist;
}

std::string LLNotification::getType() const
{
    return (mTemplatep ? mTemplatep->mType : "");
}

S32 LLNotification::getURLOption() const
{
    return (mTemplatep ? mTemplatep->mURLOption : -1);
}

S32 LLNotification::getURLOpenExternally() const
{
    return(mTemplatep? mTemplatep->mURLTarget == "_external": -1);
}

bool LLNotification::getForceUrlsExternal() const
{
    return (mTemplatep ? mTemplatep->mForceUrlsExternal : false);
}

bool LLNotification::hasUniquenessConstraints() const
{
    return (mTemplatep ? mTemplatep->mUnique : false);
}

bool LLNotification::matchesTag(const std::string& tag)
{
    bool result = false;

    if(mTemplatep)
    {
        std::list<std::string>::iterator it;
        for(it = mTemplatep->mTags.begin(); it != mTemplatep->mTags.end(); it++)
        {
            if((*it) == tag)
            {
                result = true;
                break;
            }
        }
    }

    return result;
}

void LLNotification::setIgnored(bool ignore)
{
    mIgnored = ignore;
}

void LLNotification::setResponseFunctor(std::string const &responseFunctorName)
{
    if (mTemporaryResponder)
        // get rid of the old one
        LLNotificationFunctorRegistry::instance().unregisterFunctor(mResponseFunctorName);
    mResponseFunctorName = responseFunctorName;
    mTemporaryResponder = false;
}

void LLNotification::setResponseFunctor(const LLNotificationFunctorRegistry::ResponseFunctor& cb)
{
    if(mTemporaryResponder)
    {
        LLNotificationFunctorRegistry::instance().unregisterFunctor(mResponseFunctorName);
    }

    LLNotificationFunctorRegistry::instance().registerFunctor(mResponseFunctorName, cb);
}

void LLNotification::setResponseFunctor(const LLNotificationResponderPtr& responder)
{
    mResponder = responder;
}

bool LLNotification::isEquivalentTo(LLNotificationPtr that) const
{
    if (this->mTemplatep->mName != that->mTemplatep->mName)
    {
        return false; // must have the same template name or forget it
    }
    if (this->mTemplatep->mUnique)
    {
        const LLSD& these_substitutions = this->getSubstitutions();
        const LLSD& those_substitutions = that->getSubstitutions();
        const LLSD& this_payload = this->getPayload();
        const LLSD& that_payload = that->getPayload();

        // highlander bit sez there can only be one of these
        for (std::vector<std::string>::const_iterator it = mTemplatep->mUniqueContext.begin(), end_it = mTemplatep->mUniqueContext.end();
            it != end_it;
            ++it)
        {
            // if templates differ in either substitution strings or payload with the given field name
            // then they are considered inequivalent
            // use of get() avoids converting the LLSD value to a map as the [] operator would
            if (these_substitutions.get(*it).asString() != those_substitutions.get(*it).asString()
                || this_payload.get(*it).asString() != that_payload.get(*it).asString())
            {
                return false;
            }
        }
        return true;
    }

    return false;
}

void LLNotification::init(const std::string& template_name, const LLSD& form_elements)
{
    mTemplatep = LLNotifications::instance().getTemplate(template_name);
    if (!mTemplatep) return;

    // add default substitutions
    const LLStringUtil::format_map_t& default_args = LLTrans::getDefaultArgs();
    for (LLStringUtil::format_map_t::const_iterator iter = default_args.begin();
         iter != default_args.end(); ++iter)
    {
        mSubstitutions[iter->first] = iter->second;
    }
    mSubstitutions["_URL"] = getURL();
    mSubstitutions["_NAME"] = template_name;
    // TODO: something like this so that a missing alert is sensible:
    //mSubstitutions["_ARGS"] = get_all_arguments_as_text(mSubstitutions);

    mForm = LLNotificationFormPtr(new LLNotificationForm(*mTemplatep->mForm));
    mForm->append(form_elements);

    // apply substitution to form labels
    mForm->formatElements(mSubstitutions);

    mIgnored = mForm->getIgnored();

    LLDate rightnow = LLDate::now();
    if (mTemplatep->mExpireSeconds)
    {
        mExpiresAt = LLDate(rightnow.secondsSinceEpoch() + mTemplatep->mExpireSeconds);
    }

    if (mPriority == NOTIFICATION_PRIORITY_UNSPECIFIED)
    {
        mPriority = mTemplatep->mPriority;
    }
}

std::string LLNotification::summarize() const
{
    std::string s = "Notification(";
    s += getName();
    s += ") : ";
    s += mTemplatep ? mTemplatep->mMessage : "";
    // should also include timestamp and expiration time (but probably not payload)
    return s;
}

std::string LLNotification::getMessage() const
{
    // all our callers cache this result, so it gives us more flexibility
    // to do the substitution at call time rather than attempting to
    // cache it in the notification
    if (!mTemplatep)
        return std::string();

    std::string message = mTemplatep->mMessage;
    LLStringUtil::format(message, mSubstitutions);
    return message;
}

std::string LLNotification::getFooter() const
{
    if (!mTemplatep)
        return std::string();

    std::string footer = mTemplatep->mFooter;
    LLStringUtil::format(footer, mSubstitutions);
    return footer;
}

std::string LLNotification::getLabel() const
{
    std::string label = mTemplatep->mLabel;
    LLStringUtil::format(label, mSubstitutions);
    return (mTemplatep ? label : "");
}

std::string LLNotification::getURL() const
{
    if (!mTemplatep)
        return std::string();
    std::string url = mTemplatep->mURL;
    LLStringUtil::format(url, mSubstitutions);
    return (mTemplatep ? url : "");
}

bool LLNotification::canLogToChat() const
{
    return mTemplatep->mLogToChat;
}

bool LLNotification::canLogToIM() const
{
    return mTemplatep->mLogToIM;
}

bool LLNotification::canShowToast() const
{
    return mTemplatep->mShowToast;
}

bool LLNotification::canFadeToast() const
{
    return mTemplatep->mFadeToast;
}

bool LLNotification::hasFormElements() const
{
    return mTemplatep->mForm->getNumElements() != 0;
}

void LLNotification::playSound()
{
    make_ui_sound(mTemplatep->mSoundName.c_str());
}

LLNotification::ECombineBehavior LLNotification::getCombineBehavior() const
{
    return mTemplatep->mCombineBehavior;
}

void LLNotification::updateForm( const LLNotificationFormPtr& form )
{
    mForm = form;
}

void LLNotification::repost()
{
    mRespondedTo = false;
    LLNotifications::instance().update(shared_from_this());
}



// =========================================================
// LLNotificationChannel implementation
// ---
LLBoundListener LLNotificationChannelBase::connectChangedImpl(const LLEventListener& slot)
{
    // when someone wants to connect to a channel, we first throw them
    // all of the notifications that are already in the channel
    // we use a special signal called "load" in case the channel wants to care
    // only about new notifications
    LLMutexLock lock(&mItemsMutex);
    for (LLNotificationSet::iterator it = mItems.begin(); it != mItems.end(); ++it)
    {
        slot(LLSD().with("sigtype", "load").with("id", (*it)->id()));
    }
    // and then connect the signal so that all future notifications will also be
    // forwarded.
    return mChanged.connect(slot);
}

LLBoundListener LLNotificationChannelBase::connectAtFrontChangedImpl(const LLEventListener& slot)
{
    for (LLNotificationSet::iterator it = mItems.begin(); it != mItems.end(); ++it)
    {
        slot(LLSD().with("sigtype", "load").with("id", (*it)->id()));
    }
    return mChanged.connect(slot, boost::signals2::at_front);
}

LLBoundListener LLNotificationChannelBase::connectPassedFilterImpl(const LLEventListener& slot)
{
    // these two filters only fire for notifications added after the current one, because
    // they don't participate in the hierarchy.
    return mPassedFilter.connect(slot);
}

LLBoundListener LLNotificationChannelBase::connectFailedFilterImpl(const LLEventListener& slot)
{
    return mFailedFilter.connect(slot);
}

// external call, conforms to our standard signature
bool LLNotificationChannelBase::updateItem(const LLSD& payload)
{
    // first check to see if it's in the master list
    LLNotificationPtr pNotification  = LLNotifications::instance().find(payload["id"]);
    if (!pNotification)
        return false;   // not found

    return updateItem(payload, pNotification);
}


//FIX QUIT NOT WORKING


// internal call, for use in avoiding lookup
bool LLNotificationChannelBase::updateItem(const LLSD& payload, LLNotificationPtr pNotification)
{
    std::string cmd = payload["sigtype"];
    LLNotificationSet::iterator foundItem = mItems.find(pNotification);
    bool wasFound = (foundItem != mItems.end());
    bool passesFilter = mFilter ? mFilter(pNotification) : true;

    // first, we offer the result of the filter test to the simple
    // signals for pass/fail. One of these is guaranteed to be called.
    // If either signal returns true, the change processing is NOT performed
    // (so don't return true unless you know what you're doing!)
    bool abortProcessing = false;
    if (passesFilter)
    {
        onFilterPass(pNotification);
        abortProcessing = mPassedFilter(payload);
    }
    else
    {
        onFilterFail(pNotification);
        abortProcessing = mFailedFilter(payload);
    }

    if (abortProcessing)
    {
        return true;
    }

    if (cmd == "load")
    {
        // should be no reason we'd ever get a load if we already have it
        // if passes filter send a load message, else do nothing
        assert(!wasFound);
        if (passesFilter)
        {
            // not in our list, add it and say so
            mItems.insert(pNotification);
            onLoad(pNotification);
            abortProcessing = mChanged(payload);
        }
    }
    else if (cmd == "change")
    {
        // if it passes filter now and was found, we just send a change message
        // if it passes filter now and wasn't found, we have to add it
        // if it doesn't pass filter and wasn't found, we do nothing
        // if it doesn't pass filter and was found, we need to delete it
        if (passesFilter)
        {
            if (wasFound)
            {
                // it already existed, so this is a change
                // since it changed in place, all we have to do is resend the signal
                onChange(pNotification);
                abortProcessing = mChanged(payload);
            }
            else
            {
                // not in our list, add it and say so
                mItems.insert(pNotification);
                onChange(pNotification);
                // our payload is const, so make a copy before changing it
                LLSD newpayload = payload;
                newpayload["sigtype"] = "add";
                abortProcessing = mChanged(newpayload);
            }
        }
        else
        {
            if (wasFound)
            {
                // it already existed, so this is a delete
                mItems.erase(pNotification);
                onChange(pNotification);
                // our payload is const, so make a copy before changing it
                LLSD newpayload = payload;
                newpayload["sigtype"] = "delete";
                abortProcessing = mChanged(newpayload);
            }
            // didn't pass, not on our list, do nothing
        }
    }
    else if (cmd == "add")
    {
        // should be no reason we'd ever get an add if we already have it
        // if passes filter send an add message, else do nothing
        assert(!wasFound);
        if (passesFilter)
        {
            // not in our list, add it and say so
            mItems.insert(pNotification);
            onAdd(pNotification);
            abortProcessing = mChanged(payload);
        }
    }
    else if (cmd == "delete")
    {
        // if we have it in our list, pass on the delete, then delete it, else do nothing
        if (wasFound)
        {
            onDelete(pNotification);
            abortProcessing = mChanged(payload);
            mItems.erase(pNotification);
        }
    }
    return abortProcessing;
}

LLNotificationChannel::LLNotificationChannel(const Params& p)
:   LLNotificationChannelBase(p.filter()),
    LLInstanceTracker<LLNotificationChannel, std::string>(p.name.isProvided() ? p.name : LLUUID::generateNewID().asString()),
    mName(p.name.isProvided() ? p.name : LLUUID::generateNewID().asString())
{
    for (const std::string& source : p.sources)
    {
        connectToChannel(source);
    }
}


LLNotificationChannel::LLNotificationChannel(const std::string& name,
                                             const std::string& parent,
                                             LLNotificationFilter filter)
:   LLNotificationChannelBase(filter),
    LLInstanceTracker<LLNotificationChannel, std::string>(name),
    mName(name)
{
    // bind to notification broadcast
    connectToChannel(parent);
}

LLNotificationChannel::~LLNotificationChannel()
{
    for (LLBoundListener &listener : mListeners)
    {
        listener.disconnect();
    }
}

bool LLNotificationChannel::isEmpty() const
{
    return mItems.empty();
}

S32 LLNotificationChannel::size() const
{
    return mItems.size();
}

size_t LLNotificationChannel::size()
{
    return mItems.size();
}

void LLNotificationChannel::forEachNotification(NotificationProcess process)
{
    LLMutexLock lock(&mItemsMutex);
    std::for_each(mItems.begin(), mItems.end(), process);
}

std::string LLNotificationChannel::summarize()
{
    std::string s("Channel '");
    s += mName;
    s += "'\n  ";
    LLMutexLock lock(&mItemsMutex);
    for (LLNotificationChannel::Iterator it = mItems.begin(); it != mItems.end(); ++it)
    {
        s += (*it)->summarize();
        s += "\n  ";
    }
    return s;
}

void LLNotificationChannel::connectToChannel( const std::string& channel_name )
{
    if (channel_name.empty())
    {
        mListeners.push_back(LLNotifications::instance().connectChanged(
            boost::bind(&LLNotificationChannelBase::updateItem, this, _1)));
    }
    else
    {
        mParents.push_back(channel_name);
        LLNotificationChannelPtr p = LLNotifications::instance().getChannel(channel_name);
        mListeners.push_back(p->connectChanged(boost::bind(&LLNotificationChannelBase::updateItem, this, _1)));
    }
}

// ---
// END OF LLNotificationChannel implementation
// =========================================================


// ==============================================   ===========
// LLNotifications implementation
// ---
LLNotifications::LLNotifications()
:   LLNotificationChannelBase(LLNotificationFilters::includeEverything),
    mIgnoreAllNotifications(false)
{
        mListener.reset(new LLNotificationsListener(*this));
    LLUICtrl::CommitCallbackRegistry::currentRegistrar().add("Notification.Show", boost::bind(&LLNotifications::addFromCallback, this, _2));

    // touch the instance tracker for notification channels, so that it will still be around in our destructor
    LLInstanceTracker<LLNotificationChannel, std::string>::instanceCount();
}

void LLNotifications::clear()
{
   mDefaultChannels.clear();
}

// The expiration channel gets all notifications that are cancelled
bool LLNotifications::expirationFilter(LLNotificationPtr pNotification)
{
    return pNotification->isCancelled() || pNotification->isRespondedTo();
}

bool LLNotifications::expirationHandler(const LLSD& payload)
{
    if (payload["sigtype"].asString() != "delete")
    {
        // anything added to this channel actually should be deleted from the master
        cancel(find(payload["id"]));
        return true;    // don't process this item any further
    }
    return false;
}

bool LLNotifications::uniqueFilter(LLNotificationPtr pNotif)
{
    if (!pNotif->hasUniquenessConstraints())
    {
        return true;
    }

    // checks against existing unique notifications
    for (LLNotificationMap::iterator existing_it = mUniqueNotifications.find(pNotif->getName());
        existing_it != mUniqueNotifications.end();
        ++existing_it)
    {
        LLNotificationPtr existing_notification = existing_it->second;
        if (pNotif != existing_notification
            && pNotif->isEquivalentTo(existing_notification))
        {
            if (pNotif->getCombineBehavior() == LLNotification::CANCEL_OLD)
            {
                cancel(existing_notification);
                return true;
            }
            else
            {
                return false;
            }
        }
    }

    return true;
}

bool LLNotifications::uniqueHandler(const LLSD& payload)
{
    std::string cmd = payload["sigtype"];

    LLNotificationPtr pNotif = LLNotifications::instance().find(payload["id"].asUUID());
    if (pNotif && pNotif->hasUniquenessConstraints())
    {
        if (cmd == "add")
        {
            // not a duplicate according to uniqueness criteria, so we keep it
            // and store it for future uniqueness checks
            mUniqueNotifications.insert(std::make_pair(pNotif->getName(), pNotif));
        }
        else if (cmd == "delete")
        {
            mUniqueNotifications.erase(pNotif->getName());
        }
    }

    return false;
}

bool LLNotifications::failedUniquenessTest(const LLSD& payload)
{
    LLNotificationPtr pNotif = LLNotifications::instance().find(payload["id"].asUUID());

    std::string cmd = payload["sigtype"];

    if (!pNotif || cmd != "add")
    {
        return false;
    }

    switch(pNotif->getCombineBehavior())
    {
    case  LLNotification::REPLACE_WITH_NEW:
        // Update the existing unique notification with the data from this particular instance...
        // This guarantees that duplicate notifications will be collapsed to the one
        // most recently triggered
        for (LLNotificationMap::iterator existing_it = mUniqueNotifications.find(pNotif->getName());
            existing_it != mUniqueNotifications.end();
            ++existing_it)
        {
            LLNotificationPtr existing_notification = existing_it->second;
            if (pNotif != existing_notification
                && pNotif->isEquivalentTo(existing_notification))
            {
                // copy notification instance data over to oldest instance
                // of this unique notification and update it
                existing_notification->updateFrom(pNotif);
                // then delete the new one
                cancel(pNotif);
            }
        }
        break;
    case  LLNotification::COMBINE_WITH_NEW:
        // Add to the existing unique notification with the data from this particular instance...
        // This guarantees that duplicate notifications will be collapsed to the one
        // most recently triggered
        for (LLNotificationMap::iterator existing_it = mUniqueNotifications.find(pNotif->getName());
            existing_it != mUniqueNotifications.end();
            ++existing_it)
        {
            LLNotificationPtr existing_notification = existing_it->second;
            if (pNotif != existing_notification
                && pNotif->isEquivalentTo(existing_notification))
            {
                // copy the notifications from the newest instance into the oldest
                existing_notification->mCombinedNotifications.push_back(pNotif);
                existing_notification->mCombinedNotifications.insert(existing_notification->mCombinedNotifications.end(),
                    pNotif->mCombinedNotifications.begin(), pNotif->mCombinedNotifications.end());

                // pop up again
                existing_notification->update();
            }
        }
        break;
    case LLNotification::KEEP_OLD:
        break;
    case LLNotification::CANCEL_OLD:
        // already handled by filter logic
        break;
    default:
        break;
    }

    return false;
}

LLNotificationChannelPtr LLNotifications::getChannel(const std::string& channelName)
{
    return LLNotificationChannelPtr(LLNotificationChannel::getInstance(channelName).get());
}


// this function is called once at construction time, after the object is constructed.
void LLNotifications::initSingleton()
{
    loadTemplates();
    loadVisibilityRules();
    createDefaultChannels();
}

void LLNotifications::cleanupSingleton()
{
    clear();
}

void LLNotifications::createDefaultChannels()
{
    LL_INFOS("Notifications") << "Generating default notification channels" << LL_ENDL;
    // now construct the various channels AFTER loading the notifications,
    // because the history channel is going to rewrite the stored notifications file
    mDefaultChannels.push_back(new LLNotificationChannel("Enabled", "",
        !boost::bind(&LLNotifications::getIgnoreAllNotifications, this)));
    mDefaultChannels.push_back(new LLNotificationChannel("Expiration", "Enabled",
        boost::bind(&LLNotifications::expirationFilter, this, _1)));
    mDefaultChannels.push_back(new LLNotificationChannel("Unexpired", "Enabled",
        !boost::bind(&LLNotifications::expirationFilter, this, _1))); // use negated bind
    mDefaultChannels.push_back(new LLNotificationChannel("Unique", "Unexpired",
        boost::bind(&LLNotifications::uniqueFilter, this, _1)));
    mDefaultChannels.push_back(new LLNotificationChannel("Ignore", "Unique",
        filterIgnoredNotifications));
    mDefaultChannels.push_back(new LLNotificationChannel("VisibilityRules", "Ignore",
        boost::bind(&LLNotifications::isVisibleByRules, this, _1)));
    mDefaultChannels.push_back(new LLNotificationChannel("Visible", "VisibilityRules",
        &LLNotificationFilters::includeEverything));
    mDefaultChannels.push_back(new LLPersistentNotificationChannel());

    // connect action methods to these channels
    getChannel("Enabled")->connectFailedFilter(&defaultResponse);
    getChannel("Expiration")->connectChanged(boost::bind(&LLNotifications::expirationHandler, this, _1));
    // uniqueHandler slot should be added as first slot of the signal due to
    // usage LLStopWhenHandled combiner in LLStandardSignal
    getChannel("Unique")->connectAtFrontChanged(boost::bind(&LLNotifications::uniqueHandler, this, _1));
    getChannel("Unique")->connectFailedFilter(boost::bind(&LLNotifications::failedUniquenessTest, this, _1));
    getChannel("Ignore")->connectFailedFilter(&handleIgnoredNotification);
    getChannel("VisibilityRules")->connectFailedFilter(&visibilityRuleMached);
}


LLNotificationTemplatePtr LLNotifications::getTemplate(const std::string& name)
{
    if (mTemplates.count(name))
    {
        return mTemplates[name];
    }
    else
    {
        return mTemplates["MissingAlert"];
    }
}

bool LLNotifications::templateExists(const std::string& name)
{
    return (mTemplates.count(name) != 0);
}

void LLNotifications::forceResponse(const LLNotification::Params& params, S32 option)
{
    LLNotificationPtr temp_notify(new LLNotification(params));
    LLSD response = temp_notify->getResponseTemplate();
    LLSD selected_item = temp_notify->getForm()->getElement(option);

    if (selected_item.isUndefined())
    {
        LL_WARNS("Notifications") << "Invalid option" << option << " for notification " << (std::string)params.name << LL_ENDL;
        return;
    }
    response[selected_item["name"].asString()] = true;

    temp_notify->respond(response);
}

LLNotifications::TemplateNames LLNotifications::getTemplateNames() const
{
    TemplateNames names;
    for (TemplateMap::const_iterator it = mTemplates.begin(); it != mTemplates.end(); ++it)
    {
        names.push_back(it->first);
    }
    return names;
}

typedef std::map<std::string, std::string> StringMap;
void replaceSubstitutionStrings(LLXMLNodePtr node, StringMap& replacements)
{
    // walk the list of attributes looking for replacements
    for (LLXMLAttribList::iterator it=node->mAttributes.begin();
         it != node->mAttributes.end(); ++it)
    {
        std::string value = it->second->getValue();
        if (value[0] == '$')
        {
            value.erase(0, 1);  // trim off the $
            std::string replacement;
            StringMap::const_iterator found = replacements.find(value);
            if (found != replacements.end())
            {
                replacement = found->second;
                LL_DEBUGS("Notifications") << "replaceSubstitutionStrings: value: \"" << value << "\" repl: \"" << replacement << "\"." << LL_ENDL;
                it->second->setValue(replacement);
            }
            else
            {
                LL_WARNS("Notifications") << "replaceSubstitutionStrings FAILURE: could not find replacement \"" << value << "\"." << LL_ENDL;
            }
        }
    }

    // now walk the list of children and call this recursively.
    for (LLXMLNodePtr child = node->getFirstChild();
         child.notNull(); child = child->getNextSibling())
    {
        replaceSubstitutionStrings(child, replacements);
    }
}

void replaceFormText(LLNotificationForm::Params& form, const std::string& pattern, const std::string& replace)
{
    if (form.ignore.isProvided() && form.ignore.text() == pattern)
    {
        form.ignore.text = replace;
    }

    for (LLNotificationForm::FormElement& element : form.form_elements.elements)
    {
        if (element.button.isChosen() && element.button.text() == pattern)
        {
            element.button.text = replace;
        }
    }
}

void addPathIfExists(const std::string& new_path, std::vector<std::string>& paths)
{
    if (gDirUtilp->fileExists(new_path))
    {
        paths.push_back(new_path);
    }
}

bool LLNotifications::loadTemplates()
{
    LL_INFOS("Notifications") << "Reading notifications template" << LL_ENDL;
    // Passing findSkinnedFilenames(constraint=LLDir::ALL_SKINS) makes it
    // output all relevant pathnames instead of just the ones from the most
    // specific skin.
    std::vector<std::string> search_paths =
        gDirUtilp->findSkinnedFilenames(LLDir::XUI, "notifications.xml", LLDir::ALL_SKINS);
    if (search_paths.empty())
    {
        LLError::LLUserWarningMsg::show(LLTrans::getString("MBMissingFile"));
        LL_ERRS() << "Problem finding notifications.xml" << LL_ENDL;
    }

    std::string base_filename = search_paths.front();
    LLXMLNodePtr root;
    BOOL success  = LLXMLNode::getLayeredXMLNode(root, search_paths);

    if (!success || root.isNull() || !root->hasName( "notifications" ))
    {
        LLError::LLUserWarningMsg::show(LLTrans::getString("MBMissingFile"));
        LL_ERRS() << "Problem reading XML from UI Notifications file: " << base_filename << LL_ENDL;
        return false;
    }

    LLNotificationTemplate::Notifications params;
    LLXUIParser parser;
    parser.readXUI(root, params, base_filename);

    if(!params.validateBlock())
    {
        LLError::LLUserWarningMsg::show(LLTrans::getString("MBMissingFile"));
        LL_ERRS() << "Problem reading XUI from UI Notifications file: " << base_filename << LL_ENDL;
        return false;
    }

    mTemplates.clear();

    for (const LLNotificationTemplate::GlobalString& string : params.strings)
    {
        mGlobalStrings[string.name] = string.value;
    }

    std::map<std::string, LLNotificationForm::Params> form_templates;

    for (const LLNotificationTemplate::Template& notification_template : params.templates)
    {
        form_templates[notification_template.name] = notification_template.form;
    }

    for (LLNotificationTemplate::Params& notification : params.notifications)
    {
        if (notification.form_ref.form_template.isChosen())
        {
            // replace form contents from template
            notification.form_ref.form = form_templates[notification.form_ref.form_template.name];
            if(notification.form_ref.form_template.yes_text.isProvided())
            {
                replaceFormText(notification.form_ref.form, "$yestext", notification.form_ref.form_template.yes_text);
            }
            if(notification.form_ref.form_template.no_text.isProvided())
            {
                replaceFormText(notification.form_ref.form, "$notext", notification.form_ref.form_template.no_text);
            }
            if(notification.form_ref.form_template.cancel_text.isProvided())
            {
                replaceFormText(notification.form_ref.form, "$canceltext", notification.form_ref.form_template.cancel_text);
            }
            if(notification.form_ref.form_template.help_text.isProvided())
            {
                replaceFormText(notification.form_ref.form, "$helptext", notification.form_ref.form_template.help_text);
            }
            if(notification.form_ref.form_template.ignore_text.isProvided())
            {
                replaceFormText(notification.form_ref.form, "$ignoretext", notification.form_ref.form_template.ignore_text);
            }
        }
        mTemplates[notification.name] = LLNotificationTemplatePtr(new LLNotificationTemplate(notification));
    }

    LL_INFOS("Notifications") << "...done" << LL_ENDL;

    return true;
}

bool LLNotifications::loadVisibilityRules()
{
    const std::string xml_filename = "notification_visibility.xml";
    // Note that here we're looking for the "en" version, the default
    // language, rather than the most localized version of this file.
    std::string full_filename = gDirUtilp->findSkinnedFilenameBaseLang(LLDir::XUI, xml_filename);

    LLNotificationVisibilityRule::Rules params;
    LLSimpleXUIParser parser;
    parser.readXUI(full_filename, params);

    if(!params.validateBlock())
    {
        LLError::LLUserWarningMsg::show(LLTrans::getString("MBMissingFile"));
        LL_ERRS() << "Problem reading UI Notification Visibility Rules file: " << full_filename << LL_ENDL;
        return false;
    }

    mVisibilityRules.clear();

    for (const LLNotificationVisibilityRule::Rule& rule : params.rules)
    {
        mVisibilityRules.push_back(LLNotificationVisibilityRulePtr(new LLNotificationVisibilityRule(rule)));
    }

    return true;
}

// Add a simple notification (from XUI)
void LLNotifications::addFromCallback(const LLSD& name)
{
    add(name.asString(), LLSD(), LLSD());
}

LLNotificationPtr LLNotifications::add(const std::string& name, const LLSD& substitutions, const LLSD& payload)
{
    LLNotification::Params::Functor functor_p;
    functor_p.name = name;
    return add(LLNotification::Params().name(name).substitutions(substitutions).payload(payload).functor(functor_p));
}

LLNotificationPtr LLNotifications::add(const std::string& name, const LLSD& substitutions, const LLSD& payload, const std::string& functor_name)
{
    LLNotification::Params::Functor functor_p;
    functor_p.name = functor_name;
    return add(LLNotification::Params().name(name)
                                        .substitutions(substitutions)
                                        .payload(payload)
                                        .functor(functor_p));
}

//virtual
LLNotificationPtr LLNotifications::add(const std::string& name, const LLSD& substitutions, const LLSD& payload, LLNotificationFunctorRegistry::ResponseFunctor functor)
{
    LLNotification::Params::Functor functor_p;
    functor_p.function = functor;
    return add(LLNotification::Params().name(name)
                                        .substitutions(substitutions)
                                        .payload(payload)
                                        .functor(functor_p));
}

// generalized add function that takes a parameter block object for more complex instantiations
LLNotificationPtr LLNotifications::add(const LLNotification::Params& p)
{
    LLNotificationPtr pNotif(new LLNotification(p));
    add(pNotif);
    return pNotif;
}


void LLNotifications::add(const LLNotificationPtr pNotif)
{
    if (pNotif == NULL) return;

    // first see if we already have it -- if so, that's a problem
    LLNotificationSet::iterator it=mItems.find(pNotif);
    if (it != mItems.end())
    {
        LL_ERRS() << "Notification added a second time to the master notification channel." << LL_ENDL;
    }

    updateItem(LLSD().with("sigtype", "add").with("id", pNotif->id()), pNotif);
}

void LLNotifications::load(const LLNotificationPtr pNotif)
{
    if (pNotif == NULL) return;

    // first see if we already have it -- if so, that's a problem
    LLNotificationSet::iterator it=mItems.find(pNotif);
    if (it != mItems.end())
    {
        LL_ERRS() << "Notification loaded a second time to the master notification channel." << LL_ENDL;
    }

    updateItem(LLSD().with("sigtype", "load").with("id", pNotif->id()), pNotif);
}

void LLNotifications::cancel(LLNotificationPtr pNotif)
{
    if (pNotif == NULL || pNotif->isCancelled()) return;

    LLNotificationSet::iterator it=mItems.find(pNotif);
    if (it != mItems.end())
    {
        pNotif->cancel();
        updateItem(LLSD().with("sigtype", "delete").with("id", pNotif->id()), pNotif);
    }
}

void LLNotifications::cancelByName(const std::string& name)
{
    LLMutexLock lock(&mItemsMutex);
    std::vector<LLNotificationPtr> notifs_to_cancel;
    for (LLNotificationSet::iterator it=mItems.begin(), end_it = mItems.end();
        it != end_it;
        ++it)
    {
        LLNotificationPtr pNotif = *it;
        if (pNotif->getName() == name)
        {
            notifs_to_cancel.push_back(pNotif);
        }
    }

    for (std::vector<LLNotificationPtr>::iterator it = notifs_to_cancel.begin(), end_it = notifs_to_cancel.end();
        it != end_it;
        ++it)
    {
        LLNotificationPtr pNotif = *it;
        pNotif->cancel();
        updateItem(LLSD().with("sigtype", "delete").with("id", pNotif->id()), pNotif);
    }
}

void LLNotifications::cancelByOwner(const LLUUID ownerId)
{
    LLMutexLock lock(&mItemsMutex);
    std::vector<LLNotificationPtr> notifs_to_cancel;
    for (LLNotificationSet::iterator it = mItems.begin(), end_it = mItems.end();
         it != end_it;
         ++it)
    {
        LLNotificationPtr pNotif = *it;
        if (pNotif && pNotif->getPayload().get("owner_id").asUUID() == ownerId)
        {
            notifs_to_cancel.push_back(pNotif);
        }
    }

    for (std::vector<LLNotificationPtr>::iterator it = notifs_to_cancel.begin(), end_it = notifs_to_cancel.end();
         it != end_it;
         ++it)
    {
        LLNotificationPtr pNotif = *it;
        pNotif->cancel();
        updateItem(LLSD().with("sigtype", "delete").with("id", pNotif->id()), pNotif);
    }
}

void LLNotifications::update(const LLNotificationPtr pNotif)
{
    LLNotificationSet::iterator it=mItems.find(pNotif);
    if (it != mItems.end())
    {
        updateItem(LLSD().with("sigtype", "change").with("id", pNotif->id()), pNotif);
    }
}


LLNotificationPtr LLNotifications::find(LLUUID uuid)
{
    LLNotificationPtr target = LLNotificationPtr(new LLNotification(LLNotification::Params().id(uuid)));
    LLNotificationSet::iterator it=mItems.find(target);
    if (it == mItems.end())
    {
        LL_DEBUGS("Notifications") << "Tried to dereference uuid '" << uuid << "' as a notification key but didn't find it." << LL_ENDL;
        return LLNotificationPtr((LLNotification*)NULL);
    }
    else
    {
        return *it;
    }
}

std::string LLNotifications::getGlobalString(const std::string& key) const
{
    GlobalStringMap::const_iterator it = mGlobalStrings.find(key);
    if (it != mGlobalStrings.end())
    {
        return it->second;
    }
    else
    {
        // if we don't have the key as a global, return the key itself so that the error
        // is self-diagnosing.
        return key;
    }
}

void LLNotifications::setIgnoreAllNotifications(bool setting)
{
    mIgnoreAllNotifications = setting;
}
bool LLNotifications::getIgnoreAllNotifications()
{
    return mIgnoreAllNotifications;
}

void LLNotifications::setIgnored(const std::string& name, bool ignored)
{
    LLNotificationTemplatePtr templatep = getTemplate(name);
    templatep->mForm->setIgnored(ignored);
}

bool LLNotifications::getIgnored(const std::string& name)
{
    LLNotificationTemplatePtr templatep = getTemplate(name);
    return (mIgnoreAllNotifications) || ( (templatep->mForm->getIgnoreType() != LLNotificationForm::IGNORE_NO) && (templatep->mForm->getIgnored()) );
}

bool LLNotifications::isVisibleByRules(LLNotificationPtr n)
{
    if(n->isRespondedTo())
    {
        // This avoids infinite recursion in the case where the filter calls respond()
        return true;
    }

    VisibilityRuleList::iterator it;

    for(it = mVisibilityRules.begin(); it != mVisibilityRules.end(); it++)
    {
        // An empty type/tag/name string will match any notification, so only do the comparison when the string is non-empty in the rule.
        LL_DEBUGS("Notifications")
            << "notification \"" << n->getName() << "\" "
            << "testing against " << ((*it)->mVisible?"show":"hide") << " rule, "
            << "name = \"" << (*it)->mName << "\" "
            << "tag = \"" << (*it)->mTag << "\" "
            << "type = \"" << (*it)->mType << "\" "
            << LL_ENDL;

        if(!(*it)->mType.empty())
        {
            if((*it)->mType != n->getType())
            {
                // Type doesn't match, so skip this rule.
                continue;
            }
        }

        if(!(*it)->mTag.empty())
        {
            // check this notification's tag(s) against it->mTag and continue if no match is found.
            if(!n->matchesTag((*it)->mTag))
            {
                // This rule's non-empty tag didn't match one of the notification's tags.  Skip this rule.
                continue;
            }
        }

        if(!(*it)->mName.empty())
        {
            // check this notification's name against the notification's name and continue if no match is found.
            if((*it)->mName != n->getName())
            {
                // This rule's non-empty name didn't match the notification.  Skip this rule.
                continue;
            }
        }

        // If we got here, the rule matches.  Don't evaluate subsequent rules.
        if(!(*it)->mVisible)
        {
            // This notification is being hidden.

            if((*it)->mResponse.empty())
            {
                // Response property is empty.  Cancel this notification.
                LL_DEBUGS("Notifications") << "cancelling notification " << n->getName() << LL_ENDL;

                cancel(n);
            }
            else
            {
                // Response property is not empty.  Return the specified response.
                LLSD response = n->getResponseTemplate(LLNotification::WITHOUT_DEFAULT_BUTTON);
                // TODO: verify that the response template has an item with the correct name
                response[(*it)->mResponse] = true;

                LL_DEBUGS("Notifications") << "responding to notification " << n->getName() << " with response = " << response << LL_ENDL;

                n->respond(response);
            }

            return false;
        }

        // If we got here, exit the loop and return true.
        break;
    }

    LL_DEBUGS("Notifications") << "allowing notification " << n->getName() << LL_ENDL;

    return true;
}


// ---
// END OF LLNotifications implementation
// =========================================================

std::ostream& operator<<(std::ostream& s, const LLNotification& notification)
{
    s << notification.summarize();
    return s;
}

void LLPostponedNotification::lookupName(const LLUUID& id,
                                         bool is_group)
{
    if (is_group)
    {
        gCacheName->getGroup(id,
            boost::bind(&LLPostponedNotification::onGroupNameCache,
                this, _1, _2, _3));
    }
    else
    {
        fetchAvatarName(id);
    }
}

void LLPostponedNotification::onGroupNameCache(const LLUUID& id,
                                               const std::string& full_name,
                                               bool is_group)
{
    finalizeName(full_name);
}

void LLPostponedNotification::fetchAvatarName(const LLUUID& id)
{
    if (id.notNull())
    {
        if (mAvatarNameCacheConnection.connected())
        {
            mAvatarNameCacheConnection.disconnect();
        }

        mAvatarNameCacheConnection = LLAvatarNameCache::get(id, boost::bind(&LLPostponedNotification::onAvatarNameCache, this, _1, _2));
    }
}

void LLPostponedNotification::onAvatarNameCache(const LLUUID& agent_id,
                                                const LLAvatarName& av_name)
{
    mAvatarNameCacheConnection.disconnect();

    std::string name = av_name.getCompleteName();

    // from PE merge - we should figure out if this is the right thing to do
    if (name.empty())
    {
        LL_WARNS("Notifications") << "Empty name received for Id: " << agent_id << LL_ENDL;
        name = SYSTEM_FROM;
    }

    finalizeName(name);
}

void LLPostponedNotification::finalizeName(const std::string& name)
{
    mName = name;
    modifyNotificationParams();
    LLNotifications::instance().add(mParams);
    cleanup();
}