/** 
 * @file llfloaterexperiences.cpp
 * @brief LLFloaterExperiences class implementation
 *
 * $LicenseInfo:firstyear=2012&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2012, 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 "llfloaterexperiences.h"
#include "llfloaterreg.h"

#include "llagent.h"
#include "llevents.h"
#include "llexperiencecache.h"
#include "llfloaterregioninfo.h"
#include "llnotificationsutil.h"
#include "llpanelexperiencelog.h"
#include "llpanelexperiencepicker.h"
#include "llpanelexperiences.h"
#include "lltabcontainer.h"
#include "lltrans.h"
#include "llviewerregion.h"


#define SHOW_RECENT_TAB (0)
LLFloaterExperiences::LLFloaterExperiences(const LLSD& data)
	:LLFloater(data)
{
}

LLPanelExperiences* LLFloaterExperiences::addTab(const std::string& name, bool select)
{
	LLPanelExperiences* newPanel = LLPanelExperiences::create(name);
    getChild<LLTabContainer>("xp_tabs")->addTabPanel(LLTabContainer::TabPanelParams().
        panel(newPanel).
        label(LLTrans::getString(name)).
        select_tab(select));

	return newPanel;
}

BOOL LLFloaterExperiences::postBuild()
{
	getChild<LLTabContainer>("xp_tabs")->addTabPanel(new LLPanelExperiencePicker());
    addTab("Allowed_Experiences_Tab", true);
    addTab("Blocked_Experiences_Tab", false);
    addTab("Admin_Experiences_Tab", false);
    addTab("Contrib_Experiences_Tab", false);
	LLPanelExperiences* owned = addTab("Owned_Experiences_Tab", false);
	owned->setButtonAction("acquire", boost::bind(&LLFloaterExperiences::sendPurchaseRequest, this));
	owned->enableButton(false);
#if SHOW_RECENT_TAB
	addTab("Recent_Experiences_Tab", false);
#endif //SHOW_RECENT_TAB
	getChild<LLTabContainer>("xp_tabs")->addTabPanel(new LLPanelExperienceLog());
    resizeToTabs();

   	return TRUE;
}


void LLFloaterExperiences::clearFromRecent(const LLSD& ids)
{
#if SHOW_RECENT_TAB
    LLTabContainer* tabs = getChild<LLTabContainer>("xp_tabs");

    LLPanelExperiences* tab = (LLPanelExperiences*)tabs->getPanelByName("Recent_Experiences_Tab");
    if(!tab)
        return;

    tab->removeExperiences(ids);
#endif // SHOW_RECENT_TAB
}

void LLFloaterExperiences::setupRecentTabs()
{
#if SHOW_RECENT_TAB
    LLTabContainer* tabs = getChild<LLTabContainer>("xp_tabs");

    LLPanelExperiences* tab = (LLPanelExperiences*)tabs->getPanelByName("Recent_Experiences_Tab");
    if(!tab)
        return;

    LLSD recent;

    const LLExperienceCache::cache_t& experiences = LLExperienceCache::getCached();

    LLExperienceCache::cache_t::const_iterator it = experiences.begin();
    while( it != experiences.end() )
    {
        if(!it->second.has(LLExperienceCache::MISSING))
        {
            recent.append(it->first);
        }
        ++it;
    }

    tab->setExperienceList(recent);
#endif // SHOW_RECENT_TAB
}


void LLFloaterExperiences::resizeToTabs()
{
    const S32 TAB_WIDTH_PADDING = 16;

    LLTabContainer* tabs = getChild<LLTabContainer>("xp_tabs");
    LLRect rect = getRect();
    if(rect.getWidth() < tabs->getTotalTabWidth() + TAB_WIDTH_PADDING)
    {
        rect.mRight = rect.mLeft + tabs->getTotalTabWidth() + TAB_WIDTH_PADDING;
    }
    reshape(rect.getWidth(), rect.getHeight(), FALSE);
}

void LLFloaterExperiences::refreshContents()
{
    setupRecentTabs();

    LLViewerRegion* region = gAgent.getRegion();

    if (region)
    {
        NameMap_t tabMap;
        LLHandle<LLFloaterExperiences> handle = getDerivedHandle<LLFloaterExperiences>();

        tabMap["experiences"]="Allowed_Experiences_Tab";
        tabMap["blocked"]="Blocked_Experiences_Tab";
        tabMap["experience_ids"]="Owned_Experiences_Tab";

        retrieveExperienceList(region->getCapability("GetExperiences"), handle, tabMap);

        updateInfo("GetAdminExperiences","Admin_Experiences_Tab");
        updateInfo("GetCreatorExperiences","Contrib_Experiences_Tab");

        retrieveExperienceList(region->getCapability("AgentExperiences"), handle, tabMap, 
            "ExperienceAcquireFailed", boost::bind(&LLFloaterExperiences::checkPurchaseInfo, this, _1, _2));
    }
}

void LLFloaterExperiences::onOpen( const LLSD& key )
{
    LLEventPumps::instance().obtain("experience_permission").stopListening("LLFloaterExperiences");
    LLEventPumps::instance().obtain("experience_permission").listen("LLFloaterExperiences",
        boost::bind(&LLFloaterExperiences::updatePermissions, this, _1));

    LLViewerRegion* region = gAgent.getRegion();
    if(region)
    {
        if(region->capabilitiesReceived())
        {
            refreshContents();
            return;
        }
        region->setCapabilitiesReceivedCallback(boost::bind(&LLFloaterExperiences::refreshContents, this));
        return;
    }
}

bool LLFloaterExperiences::updatePermissions( const LLSD& permission )
{
    LLTabContainer* tabs = getChild<LLTabContainer>("xp_tabs");
    LLUUID experience;
    std::string permission_string;
    if(permission.has("experience"))
    {
        experience = permission["experience"].asUUID();
        permission_string = permission[experience.asString()]["permission"].asString();

    }
    LLPanelExperiences* tab = (LLPanelExperiences*)tabs->getPanelByName("Allowed_Experiences_Tab");
    if(tab)
    {
        if(permission.has("experiences"))
        {
            tab->setExperienceList(permission["experiences"]);
        }
        else if(experience.notNull())
        {
            if(permission_string != "Allow")
            {
                tab->removeExperience(experience);
            }
            else
            {
                tab->addExperience(experience);
            }
        }
    }
    
    tab = (LLPanelExperiences*)tabs->getPanelByName("Blocked_Experiences_Tab");
    if(tab)
    {
        if(permission.has("blocked"))
        {
            tab->setExperienceList(permission["blocked"]);
        }
        else if(experience.notNull())
        {
            if(permission_string != "Block")
            {
                tab->removeExperience(experience);
            }
            else
            {
                tab->addExperience(experience);
            }
        }
    }
    return false;
}

void LLFloaterExperiences::onClose( bool app_quitting )
{
    LLEventPumps::instance().obtain("experience_permission").stopListening("LLFloaterExperiences");
    LLFloater::onClose(app_quitting);
}

void LLFloaterExperiences::checkPurchaseInfo(LLPanelExperiences* panel, const LLSD& content) const
{
	panel->enableButton(content.has("purchase"));

	LLFloaterExperiences::findInstance()->updateInfo("GetAdminExperiences","Admin_Experiences_Tab");
	LLFloaterExperiences::findInstance()->updateInfo("GetCreatorExperiences","Contrib_Experiences_Tab");
}

void LLFloaterExperiences::checkAndOpen(LLPanelExperiences* panel, const LLSD& content) const
{
    checkPurchaseInfo(panel, content);

    // determine new item
    const LLSD& response_ids = content["experience_ids"];

    if (mPrepurchaseIds.size() + 1 == response_ids.size())
    {
        // we have a new element
        for (LLSD::array_const_iterator it = response_ids.beginArray(); it != response_ids.endArray(); ++it)
        {
            LLUUID experience_id = it->asUUID();
            if (std::find(mPrepurchaseIds.begin(), mPrepurchaseIds.end(), experience_id) == mPrepurchaseIds.end())
            {
                // new element found, open it
                LLSD args;
                args["experience_id"] = experience_id;
                args["edit_experience"] = true;
                LLFloaterReg::showInstance("experience_profile", args, true);
                break;
            }
        }
    }
}

void LLFloaterExperiences::updateInfo(std::string experienceCap, std::string tab)
{
	LLViewerRegion* region = gAgent.getRegion();
	if (region)
	{
        NameMap_t tabMap;
        LLHandle<LLFloaterExperiences> handle = getDerivedHandle<LLFloaterExperiences>();

        tabMap["experience_ids"] = tab;

        retrieveExperienceList(region->getCapability(experienceCap), handle, tabMap);
    }
}

void LLFloaterExperiences::sendPurchaseRequest() 
{
    LLViewerRegion* region = gAgent.getRegion();

    if (region)
    {
        NameMap_t tabMap;
        const std::string tab_owned_name = "Owned_Experiences_Tab";
        LLHandle<LLFloaterExperiences> handle = getDerivedHandle<LLFloaterExperiences>();

        tabMap["experience_ids"] = tab_owned_name;

        // extract ids for experiences that we already have
        LLTabContainer* tabs = getChild<LLTabContainer>("xp_tabs");
        LLPanelExperiences* tab_owned = (LLPanelExperiences*)tabs->getPanelByName(tab_owned_name);
        mPrepurchaseIds.clear();
        if (tab_owned)
        {
            tab_owned->getExperienceIdsList(mPrepurchaseIds);
        }

        requestNewExperience(region->getCapability("AgentExperiences"), handle, tabMap, "ExperienceAcquireFailed",
            boost::bind(&LLFloaterExperiences::checkAndOpen, this, _1, _2));
    }
}

LLFloaterExperiences* LLFloaterExperiences::findInstance()
{
	return LLFloaterReg::findTypedInstance<LLFloaterExperiences>("experiences");
}


void LLFloaterExperiences::retrieveExperienceList(const std::string &url,
    const LLHandle<LLFloaterExperiences> &hparent, const NameMap_t &tabMapping,
    const std::string &errorNotify, Callback_t cback)

{
    invokationFn_t getFn = boost::bind(
        // Humans ignore next line.  It is just a cast to specify which LLCoreHttpUtil::HttpCoroutineAdapter routine overload.
        static_cast<LLSD(LLCoreHttpUtil::HttpCoroutineAdapter::*)(LLCore::HttpRequest::ptr_t, const std::string &, LLCore::HttpOptions::ptr_t, LLCore::HttpHeaders::ptr_t)>
        //----
        // _1 -> httpAdapter
        // _2 -> httpRequest
        // _3 -> url
        // _4 -> httpOptions
        // _5 -> httpHeaders
        (&LLCoreHttpUtil::HttpCoroutineAdapter::getAndSuspend), _1, _2, _3, _4, _5);

    LLCoros::instance().launch("LLFloaterExperiences::retrieveExperienceList",
        boost::bind(&LLFloaterExperiences::retrieveExperienceListCoro,
        url, hparent, tabMapping, errorNotify, cback, getFn));

}

void LLFloaterExperiences::requestNewExperience(const std::string &url,
    const LLHandle<LLFloaterExperiences> &hparent, const NameMap_t &tabMapping,
    const std::string &errorNotify, Callback_t cback)
{
    invokationFn_t postFn = boost::bind(
        // Humans ignore next line.  It is just a cast to specify which LLCoreHttpUtil::HttpCoroutineAdapter routine overload.
        static_cast<LLSD(LLCoreHttpUtil::HttpCoroutineAdapter::*)(LLCore::HttpRequest::ptr_t, const std::string &, const LLSD &, LLCore::HttpOptions::ptr_t, LLCore::HttpHeaders::ptr_t)>
        //----
        // _1 -> httpAdapter
        // _2 -> httpRequest
        // _3 -> url
        // _4 -> httpOptions
        // _5 -> httpHeaders
        (&LLCoreHttpUtil::HttpCoroutineAdapter::postAndSuspend), _1, _2, _3, LLSD(), _4, _5);

    LLCoros::instance().launch("LLFloaterExperiences::requestNewExperience",
        boost::bind(&LLFloaterExperiences::retrieveExperienceListCoro,
        url, hparent, tabMapping, errorNotify, cback, postFn));

}


void LLFloaterExperiences::retrieveExperienceListCoro(std::string url, 
    LLHandle<LLFloaterExperiences> hparent, NameMap_t tabMapping, 
    std::string errorNotify, Callback_t cback, invokationFn_t invoker)
{
    LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
    LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t
        httpAdapter(std::make_shared<LLCoreHttpUtil::HttpCoroutineAdapter>("retrieveExperienceListCoro", httpPolicy));
    LLCore::HttpRequest::ptr_t httpRequest(std::make_shared<LLCore::HttpRequest>());
    LLCore::HttpOptions::ptr_t httpOptions(std::make_shared<LLCore::HttpOptions>());
    LLCore::HttpHeaders::ptr_t httpHeaders(std::make_shared<LLCore::HttpHeaders>());


    if (url.empty())
    {
        LL_WARNS() << "retrieveExperienceListCoro called with empty capability!" << LL_ENDL;
        return;
    }

    LLSD result = invoker(httpAdapter, httpRequest, url, httpOptions, httpHeaders);

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

    if (!status)
    {
        LLSD subs;
        subs["ERROR_MESSAGE"] = status.getType();
        LLNotificationsUtil::add(errorNotify, subs);

        return;
    }

    if (hparent.isDead())
        return;

    LLFloaterExperiences* parent = hparent.get();
    LLTabContainer* tabs = parent->getChild<LLTabContainer>("xp_tabs");

    for (NameMap_t::iterator it = tabMapping.begin(); it != tabMapping.end(); ++it)
    {
        if (result.has(it->first))
        {
            LLPanelExperiences* tab = (LLPanelExperiences*)tabs->getPanelByName(it->second);
            if (tab)
            {
                const LLSD& ids = result[it->first];
                tab->setExperienceList(ids);
                if (!cback.empty())
                {
                    cback(tab, result);
                }
            }
        }
    }

}
