/** 
 * @file llestateinfomodel.cpp
 * @brief Estate info model
 *
 * $LicenseInfo:firstyear=2011&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2011, 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 "llestateinfomodel.h"

// libs
#include "llregionflags.h"
#include "message.h"

// viewer
#include "llagent.h"
#include "llviewerregion.h"
#include "llcorehttputil.h"

//=========================================================================
namespace 
{
	class LLDispatchEstateUpdateInfo : public LLDispatchHandler
	{
	public:
	    LLDispatchEstateUpdateInfo() {}
	    virtual ~LLDispatchEstateUpdateInfo() {}
	    virtual bool operator()(const LLDispatcher* dispatcher, const std::string& key, 
            const LLUUID& invoice, const sparam_t& strings)
	    {
	        // key = "estateupdateinfo"
	        // strings[0] = estate name
	        // strings[1] = str(owner_id)
	        // strings[2] = str(estate_id)
	        // strings[3] = str(estate_flags)
	        // strings[4] = str((S32)(sun_hour * 1024))
	        // strings[5] = str(parent_estate_id)
	        // strings[6] = str(covenant_id)
	        // strings[7] = str(covenant_timestamp)
	        // strings[8] = str(send_to_agent_only)
	        // strings[9] = str(abuse_email_addr)
	
	        LL_DEBUGS("ESTATEINFOM") << "Received estate update" << LL_ENDL;
	
	        // Update estate info model.
	        // This will call LLPanelEstateInfo::refreshFromEstate().
	        // *TODO: Move estate message handling stuff to llestateinfomodel.cpp.
	        LLEstateInfoModel::instance().updateEstateInfo(strings);
	
	        return true;
	    }
	};
	
	class LLDispatchSetEstateAccess : public LLDispatchHandler
	{
	public:
	    LLDispatchSetEstateAccess() {}
	    virtual ~LLDispatchSetEstateAccess() {}
	    virtual bool operator()(
	        const LLDispatcher* dispatcher, const std::string& key,
	        const LLUUID& invoice, const sparam_t& strings)
	    {
	        // key = "setaccess"
	        // strings[0] = str(estate_id)
	        // strings[1] = str(packed_access_lists)
	        // strings[2] = str(num allowed agent ids)
	        // strings[3] = str(num allowed group ids)
	        // strings[4] = str(num banned agent ids)
	        // strings[5] = str(num estate manager agent ids)
	        // strings[6] = bin(uuid)
	        // strings[7] = bin(uuid)
	        // strings[8] = bin(uuid)
	        // ...

            LLEstateInfoModel::instance().updateAccessInfo(strings);
	
	        return true;
	    }
	
	};

    class LLDispatchSetEstateExperience : public LLDispatchHandler
    {
    public:
        virtual bool operator()(const LLDispatcher* dispatcher, const std::string& key,
                const LLUUID& invoice, const sparam_t& strings)
        {
            // key = "setexperience"
            // strings[0] = str(estate_id)
            // strings[1] = str(send_to_agent_only)
            // strings[2] = str(num blocked)
            // strings[3] = str(num trusted)
            // strings[4] = str(num allowed)
            // strings[8] = bin(uuid) ...
            // ...

            LLEstateInfoModel::instance().updateExperienceInfo(strings);

            return true;
        }

    };

}

//=========================================================================
LLEstateInfoModel::LLEstateInfoModel():	
    mID(0),	
    mFlags(0),	
    mSunHour(0),
    mRegion(nullptr)
{
}

boost::signals2::connection LLEstateInfoModel::setUpdateCallback(const update_signal_t::slot_type& cb)
{
	return mUpdateSignal.connect(cb);
}

boost::signals2::connection LLEstateInfoModel::setUpdateAccessCallback(const update_flaged_signal_t::slot_type& cb)
{
    return mUpdateAccess.connect(cb);
}

boost::signals2::connection LLEstateInfoModel::setUpdateExperienceCallback(const update_signal_t::slot_type& cb)
{
    return mUpdateExperience.connect(cb);
}

boost::signals2::connection LLEstateInfoModel::setCommitCallback(const update_signal_t::slot_type& cb)
{
	return mCommitSignal.connect(cb);
}

void LLEstateInfoModel::setRegion(LLViewerRegion* region)
{
    if (region != mRegion)
    {
        mRegion = region;

        if (mRegion)
        {
	        nextInvoice();
	        sendEstateOwnerMessage("getinfo", strings_t());
        }
    }
}


void LLEstateInfoModel::clearRegion()
{
    mRegion = nullptr;
}

void LLEstateInfoModel::sendEstateInfo()
{
	if (!commitEstateInfoCaps())
	{
		// the caps method failed, try the old way
		nextInvoice();
		commitEstateInfoDataserver();
	}
}

void LLEstateInfoModel::updateEstateInfo(const strings_t& strings)
{
	// NOTE: LLDispatcher extracts strings with an extra \0 at the
	// end.  If we pass the std::string direct to the UI/renderer
	// it draws with a weird character at the end of the string.
	mName		= strings[0].c_str();
	mOwnerID	= LLUUID(strings[1].c_str());
	mID			= strtoul(strings[2].c_str(), NULL, 10);
	mFlags		= strtoul(strings[3].c_str(), NULL, 10);
	mSunHour	= ((F32)(strtod(strings[4].c_str(), NULL)))/1024.0f;

    LL_DEBUGS("ESTATEINFOM") << "Received estate info: "
		<< "is_sun_fixed = " << getUseFixedSun()
		<< ", sun_hour = " << getSunHour() << LL_ENDL;
    LL_DEBUGS("ESTATEINFOM") << getInfoDump() << LL_ENDL;

	// Update region owner.
	LLViewerRegion* regionp = gAgent.getRegion();
	regionp->setOwner(mOwnerID);

	// Let interested parties know that estate info has been updated.
	mUpdateSignal();
}

void LLEstateInfoModel::updateAccessInfo(const strings_t& strings)
{
    S32 index = 1;	// skip estate_id
    U32 access_flags = strtoul(strings[index++].c_str(), NULL, 10);
    S32 num_allowed_agents = strtol(strings[index++].c_str(), NULL, 10);
    S32 num_allowed_groups = strtol(strings[index++].c_str(), NULL, 10);
    S32 num_banned_agents = strtol(strings[index++].c_str(), NULL, 10);
    S32 num_estate_managers = strtol(strings[index++].c_str(), NULL, 10);

    // sanity ckecks
    if (num_allowed_agents > 0
        && !(access_flags & ESTATE_ACCESS_ALLOWED_AGENTS))
    {
        LL_WARNS("ESTATEINFOM") << "non-zero count for allowed agents, but no corresponding flag" << LL_ENDL;
    }
    if (num_allowed_groups > 0
        && !(access_flags & ESTATE_ACCESS_ALLOWED_GROUPS))
    {
        LL_WARNS("ESTATEINFOM") << "non-zero count for allowed groups, but no corresponding flag" << LL_ENDL;
    }
    if (num_banned_agents > 0
        && !(access_flags & ESTATE_ACCESS_BANNED_AGENTS))
    {
        LL_WARNS("ESTATEINFOM") << "non-zero count for banned agents, but no corresponding flag" << LL_ENDL;
    }
    if (num_estate_managers > 0
        && !(access_flags & ESTATE_ACCESS_MANAGERS))
    {
        LL_WARNS("ESTATEINFOM") << "non-zero count for managers, but no corresponding flag" << LL_ENDL;
    }

    // grab the UUID's out of the string fields
    if (access_flags & ESTATE_ACCESS_ALLOWED_AGENTS)
    {
        mAllowedAgents.clear();

        for (S32 i = 0; i < num_allowed_agents && i < ESTATE_MAX_ACCESS_IDS; i++)
        {
            LLUUID id;
            memcpy(id.mData, strings[index++].data(), UUID_BYTES);		/* Flawfinder: ignore */
            mAllowedAgents.insert(id);
        }
    }

    if (access_flags & ESTATE_ACCESS_ALLOWED_GROUPS)
    {
        mAllowedGroups.clear();

        for (S32 i = 0; i < num_allowed_groups && i < ESTATE_MAX_GROUP_IDS; i++)
        {
            LLUUID id;
            memcpy(id.mData, strings[index++].data(), UUID_BYTES);		/* Flawfinder: ignore */
            mAllowedGroups.insert(id);
        }
    }

    if (access_flags & ESTATE_ACCESS_BANNED_AGENTS)
    {
        mBannedAgents.clear();

        for (S32 i = 0; i < num_banned_agents && i < ESTATE_MAX_ACCESS_IDS; i++)
        {
            LLUUID id;
            memcpy(id.mData, strings[index++].data(), UUID_BYTES);		/* Flawfinder: ignore */
            mBannedAgents.insert(id);
        }
    }

    if (access_flags & ESTATE_ACCESS_MANAGERS)
    {
        mEstateManagers.clear();

        // There should be only ESTATE_MAX_MANAGERS people in the list, but if the database gets more (SL-46107) don't 
        // truncate the list unless it's really big.  Go ahead and show the extras so the user doesn't get confused, 
        // and they can still remove them.
        for (S32 i = 0; i < num_estate_managers && i < (ESTATE_MAX_MANAGERS * 4); i++)
        {
            LLUUID id;
            memcpy(id.mData, strings[index++].data(), UUID_BYTES);		/* Flawfinder: ignore */
            mEstateManagers.insert(id);
        }
    }

    // Update the buttons which may change based on the list contents but also needs to account for general access features.
    mUpdateAccess(access_flags);
}

void LLEstateInfoModel::updateExperienceInfo(const strings_t& strings)
{
    strings_t::const_iterator it = strings.begin();
    ++it; // U32 estate_id = strtol((*it).c_str(), NULL, 10);
    ++it; // U32 send_to_agent_only = strtoul((*(++it)).c_str(), NULL, 10);

    LLUUID id;
    S32 num_blocked = strtol((*(it++)).c_str(), NULL, 10);
    S32 num_trusted = strtol((*(it++)).c_str(), NULL, 10);
    S32 num_allowed = strtol((*(it++)).c_str(), NULL, 10);

    mExperienceAllowed.clear();
    mExperienceTrusted.clear();
    mExperienceBlocked.clear();

    while (num_blocked-- > 0)
    {
        memcpy(id.mData, (*(it++)).data(), UUID_BYTES);
        mExperienceBlocked.insert(id);
    }

    while (num_trusted-- > 0)
    {
        memcpy(id.mData, (*(it++)).data(), UUID_BYTES);
        mExperienceTrusted.insert(id);
    }

    while (num_allowed-- > 0)
    {
        memcpy(id.mData, (*(it++)).data(), UUID_BYTES);
        mExperienceAllowed.insert(id);
    }

    mUpdateExperience();
}

void LLEstateInfoModel::notifyCommit()
{
	mCommitSignal();
}

void LLEstateInfoModel::initSingleton()
{
    gMessageSystem->setHandlerFunc("EstateOwnerMessage", &processEstateOwnerRequest);

    //	name.assign("setowner");
    //	static LLDispatchSetEstateOwner set_owner;
    //	dispatch.addHandler(name, &set_owner);

    static LLDispatchEstateUpdateInfo estate_update_info;
    mDispatch.addHandler("estateupdateinfo", &estate_update_info);

    static LLDispatchSetEstateAccess set_access;
    mDispatch.addHandler("setaccess", &set_access);

    static LLDispatchSetEstateExperience set_experience;
    mDispatch.addHandler("setexperience", &set_experience);

}

void LLEstateInfoModel::sendEstateOwnerMessage(const std::string& request, const strings_t& strings)
{
    if (!mRegion)
    {
        LL_WARNS("ESTATEINFOM") << "No selected region." << LL_ENDL;
        return;
    }
    LLMessageSystem* msg(gMessageSystem);
    LLUUID invoice(LLEstateInfoModel::instance().getLastInvoice());

    LL_INFOS() << "Sending estate request '" << request << "'" << LL_ENDL;
    msg->newMessage("EstateOwnerMessage");
    msg->nextBlockFast(_PREHASH_AgentData);
    msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
    msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
    msg->addUUIDFast(_PREHASH_TransactionID, LLUUID::null); //not used
    msg->nextBlock("MethodData");
    msg->addString("Method", request);
    msg->addUUID("Invoice", invoice);
    if (strings.empty())
    {
        msg->nextBlock("ParamList");
        msg->addString("Parameter", NULL);
    }
    else
    {
        strings_t::const_iterator it = strings.begin();
        strings_t::const_iterator end = strings.end();
        for (; it != end; ++it)
        {
            msg->nextBlock("ParamList");
            msg->addString("Parameter", *it);
        }
    }
    msg->sendReliable(mRegion->getHost());
}

//== PRIVATE STUFF ============================================================

// tries to send estate info using a cap; returns true if it succeeded
bool LLEstateInfoModel::commitEstateInfoCaps()
{
    if (!mRegion)
    {
        LL_WARNS("ESTATEINFOM") << "Attempt to update estate caps with no anchor region! Don't do that!" << LL_ENDL;
        return false;
    }
    std::string url = mRegion->getCapability("EstateChangeInfo");

	if (url.empty())
	{
        LL_WARNS("ESTATEINFOM") << "No EstateChangeInfo cap from region." << LL_ENDL;
		// whoops, couldn't find the cap, so bail out
		return false;
	}

    LLCoros::instance().launch("LLEstateInfoModel::commitEstateInfoCapsCoro",
        boost::bind(&LLEstateInfoModel::commitEstateInfoCapsCoro, this, url));

    return true;
}

void LLEstateInfoModel::commitEstateInfoCapsCoro(std::string url)
{
    LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
    LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t
        httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("EstateChangeInfo", httpPolicy));
    LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest);

    LLSD body;
    body["estate_name"] = getName();
    body["sun_hour"] = getSunHour();

    body["is_sun_fixed"] = getUseFixedSun();
    body["is_externally_visible"] = getIsExternallyVisible();
    body["allow_direct_teleport"] = getAllowDirectTeleport();
    body["deny_anonymous"] = getDenyAnonymous();
    body["deny_age_unverified"] = getDenyAgeUnverified();
    body["allow_voice_chat"] = getAllowVoiceChat();
    body["override_public_access"] = getAllowAccessOverride();
    body["override_environment"] = getAllowEnvironmentOverride();

    body["invoice"] = getLastInvoice();

    LL_DEBUGS("ESTATEINFOM") << "Sending estate caps: "
        << "is_sun_fixed = " << getUseFixedSun()
        << ", sun_hour = " << getSunHour() << LL_ENDL;
    LL_DEBUGS("ESTATEINFOM") << body << LL_ENDL;

    LLSD result = httpAdapter->postAndSuspend(httpRequest, url, body);

    LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
    LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);

    if (status)
    {
        LL_INFOS("ESTATEINFOM") << "Committed estate info" << LL_ENDL;
        LLEstateInfoModel::instance().notifyCommit();
    }
    else
    {
        LL_WARNS("ESTATEINFOM") << "Failed to commit estate info " << LL_ENDL;
    }
}

/* This is the old way of doing things, is deprecated, and should be
   deleted when the dataserver model can be removed */
// key = "estatechangeinfo"
// strings[0] = str(estate_id) (added by simulator before relay - not here)
// strings[1] = estate_name
// strings[2] = str(estate_flags)
// strings[3] = str((S32)(sun_hour * 1024.f))
void LLEstateInfoModel::commitEstateInfoDataserver()
{
    if (!mRegion)
    {
        LL_WARNS("ESTATEINFOM") << "No selected region." << LL_ENDL;
        return;
    }
    LL_DEBUGS("ESTATEINFOM") << "Sending estate info: "
		<< "is_sun_fixed = " << getUseFixedSun()
		<< ", sun_hour = " << getSunHour() << LL_ENDL;
    LL_DEBUGS("ESTATEINFOM") << getInfoDump() << LL_ENDL;

	LLMessageSystem* msg = gMessageSystem;
	msg->newMessage("EstateOwnerMessage");
	msg->nextBlockFast(_PREHASH_AgentData);
	msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
	msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
	msg->addUUIDFast(_PREHASH_TransactionID, LLUUID::null); //not used

	msg->nextBlock("MethodData");
	msg->addString("Method", "estatechangeinfo");
	msg->addUUID("Invoice", getLastInvoice());

	msg->nextBlock("ParamList");
	msg->addString("Parameter", getName());

	msg->nextBlock("ParamList");
	msg->addString("Parameter", llformat("%u", getFlags()));

	msg->nextBlock("ParamList");
	msg->addString("Parameter", llformat("%d", (S32) (getSunHour() * 1024.0f)));

    msg->sendReliable(mRegion->getHost());
}

std::string LLEstateInfoModel::getInfoDump()
{
	LLSD dump;
	dump["estate_name"          ] = getName();
	dump["sun_hour"             ] = getSunHour();

	dump["is_sun_fixed"         ] = getUseFixedSun();
	dump["is_externally_visible"] = getIsExternallyVisible();
	dump["allow_direct_teleport"] = getAllowDirectTeleport();
	dump["deny_anonymous"       ] = getDenyAnonymous();
	dump["deny_age_unverified"  ] = getDenyAgeUnverified();
	dump["allow_voice_chat"     ] = getAllowVoiceChat();
    dump["override_public_access"] = getAllowAccessOverride();
    dump["override_environment"]  = getAllowEnvironmentOverride();

	std::stringstream dump_str;
	dump_str << dump;
	return dump_str.str();
}

// static
void LLEstateInfoModel::processEstateOwnerRequest(LLMessageSystem* msg, void**)
{
    // unpack the message
    std::string request;
    LLUUID invoice;
    LLDispatcher::sparam_t strings;
    LLDispatcher::unpackMessage(msg, request, invoice, strings);
    if (invoice != LLEstateInfoModel::instance().getLastInvoice())
    {
        LL_WARNS("ESTATEINFOM") << "Mismatched Estate message: " << request << LL_ENDL;
        return;
    }

    //dispatch the message
    LLEstateInfoModel::instance().mDispatch.dispatch(request, invoice, strings);
}