/**
 * @file LLAccountingQuotaManager.cpp
 * @ Handles the setting and accessing for costs associated with mesh
 *
 * $LicenseInfo:firstyear=2001&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 "llaccountingcostmanager.h"
#include "llagent.h"
#include "httpcommon.h"
#include "llcoros.h"
#include "lleventcoro.h"
#include "llcorehttputil.h"
#include "llexception.h"
#include "stringize.h"
#include <algorithm>
#include <iterator>

//===============================================================================
LLAccountingCostManager::LLAccountingCostManager()
{

}

// Coroutine for sending and processing avatar name cache requests.
// Do not call directly.  See documentation in lleventcoro.h and llcoro.h for
// further explanation.
void LLAccountingCostManager::accountingCostCoro(std::string url,
    eSelectionType selectionType, const LLHandle<LLAccountingCostObserver> observerHandle)
{
    LL_DEBUGS("LLAccountingCostManager") << "Entering coroutine " << LLCoros::getName()
        << " with url '" << url << LL_ENDL;

    LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
    LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t
        httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("AccountingCost", httpPolicy));
    LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest);

    try
    {
        LLAccountingCostManager* self = LLAccountingCostManager::getInstance();
        uuid_set_t diffSet;

        std::set_difference(self->mObjectList.begin(),
                            self->mObjectList.end(),
                            self->mPendingObjectQuota.begin(),
                            self->mPendingObjectQuota.end(),
                            std::inserter(diffSet, diffSet.begin()));

        if (diffSet.empty())
            return;

        self->mObjectList.clear();

        std::string keystr;
        if (selectionType == Roots)
        {
            keystr = "selected_roots";
        }
        else if (selectionType == Prims)
        {
            keystr = "selected_prims";
        }
        else
        {
            LL_INFOS() << "Invalid selection type " << LL_ENDL;
            return;
        }

        LLSD objectList(LLSD::emptyMap());

        for (uuid_set_t::iterator it = diffSet.begin(); it != diffSet.end(); ++it)
        {
            objectList.append(*it);
        }

        self->mPendingObjectQuota.insert(diffSet.begin(), diffSet.end());

        LLSD dataToPost = LLSD::emptyMap();
        dataToPost[keystr.c_str()] = objectList;

        LLSD results = httpAdapter->postAndSuspend(httpRequest, url, dataToPost);

        LLSD httpResults = results["http_result"];
        LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);

        if (LLApp::isQuitting()
            || observerHandle.isDead()
            || !LLAccountingCostManager::instanceExists())
        {
            return;
        }

        LLAccountingCostObserver* observer = NULL;

        // do/while(false) allows error conditions to break out of following
        // block while normal flow goes forward once.
        do
        {
            observer = observerHandle.get();

            if (!status || results.has("error"))
            {
                LL_WARNS() << "Error on fetched data" << LL_ENDL;
                if (!status)
                    observer->setErrorStatus(status.getType(), status.toString());
                else
                    observer->setErrorStatus(499, "Error on fetched data");

                break;
            }

            if (!httpResults["success"].asBoolean())
            {
                LL_WARNS() << "Error result from LLCoreHttpUtil::HttpCoroHandler. Code "
                    << httpResults["status"] << ": '" << httpResults["message"] << "'" << LL_ENDL;
                if (observer)
                {
                    observer->setErrorStatus(httpResults["status"].asInteger(), httpResults["message"].asStringRef());
                }
                break;
            }


            if (results.has("selected"))
            {
                LLSD selected = results["selected"];

                F32 physicsCost = 0.0f;
                F32 networkCost = 0.0f;
                F32 simulationCost = 0.0f;

                physicsCost = (F32)selected["physics"].asReal();
                networkCost = (F32)selected["streaming"].asReal();
                simulationCost = (F32)selected["simulation"].asReal();

                SelectionCost selectionCost( physicsCost, networkCost, simulationCost);

                observer->onWeightsUpdate(selectionCost);
            }

        } while (false);

    }
    catch (...)
    {
        LOG_UNHANDLED_EXCEPTION(STRINGIZE("coroutine " << LLCoros::getName()
                                          << "('" << url << "')"));
        throw;
    }

    // self can be obsolete by this point
    LLAccountingCostManager::getInstance()->mPendingObjectQuota.clear();
}

//===============================================================================
void LLAccountingCostManager::fetchCosts( eSelectionType selectionType,
                                          const std::string& url,
                                          const LLHandle<LLAccountingCostObserver>& observer_handle )
{
    // Invoking system must have already determined capability availability
    if ( !url.empty() )
    {
        std::string coroname =
            LLCoros::instance().launch("LLAccountingCostManager::accountingCostCoro",
            boost::bind(accountingCostCoro, url, selectionType, observer_handle));
        LL_DEBUGS() << coroname << " with  url '" << url << LL_ENDL;

    }
    else
    {
        //url was empty - warn & continue
        LL_WARNS()<<"Supplied url is empty "<<LL_ENDL;
        mObjectList.clear();
        mPendingObjectQuota.clear();
    }
}
//===============================================================================
void LLAccountingCostManager::addObject( const LLUUID& objectID )
{
    mObjectList.insert( objectID );
}
//===============================================================================
void LLAccountingCostManager::removePendingObject( const LLUUID& objectID )
{
    mPendingObjectQuota.erase( objectID );
}
//===============================================================================