/**
 * @file llfloaterreg.cpp
 * @brief LLFloaterReg Floater Registration Class
 *
 * $LicenseInfo:firstyear=2002&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 "llfloaterreg.h"

//#include "llagent.h"
#include "llfloater.h"
#include "llmultifloater.h"
#include "llfloaterreglistener.h"
#include "lluiusage.h"

//*******************************************************

//static
LLFloaterReg::instance_list_t LLFloaterReg::sNullInstanceList;
LLFloaterReg::instance_map_t LLFloaterReg::sInstanceMap;
LLFloaterReg::build_map_t LLFloaterReg::sBuildMap;
std::map<std::string, std::string, std::less<>> LLFloaterReg::sGroupMap;
bool LLFloaterReg::sBlockShowFloaters = false;
std::set<std::string, std::less<>> LLFloaterReg::sAlwaysShowableList;

static LLFloaterRegListener sFloaterRegListener;

//*******************************************************

//static
void LLFloaterReg::add(const std::string& name, const std::string& filename, const LLFloaterBuildFunc& func, const std::string& groupname)
{
    sBuildMap[name].mFunc = func;
    sBuildMap[name].mFile = filename;
    sGroupMap[name] = groupname.empty() ? name : groupname;
    sGroupMap[groupname] = groupname; // for referencing directly by group name
}

//static
bool LLFloaterReg::isRegistered(std::string_view name)
{
    return sBuildMap.find(name) != sBuildMap.end();
}

//static
LLFloater* LLFloaterReg::getLastFloaterInGroup(std::string_view name)
{
    auto it = sGroupMap.find(name);
    if (it != sGroupMap.end())
    {
        const std::string& groupname = it->second;
        if (!groupname.empty())
        {
            instance_list_t& list = sInstanceMap[groupname];
            if (!list.empty())
            {
                for (instance_list_t::reverse_iterator iter = list.rbegin(), end = list.rend(); iter != end; ++iter)
                {
                    LLFloater* inst = *iter;

                    if (inst->getVisible() && !inst->isMinimized())
                    {
                        return inst;
                    }
                }
            }
        }
    }
    return NULL;
}

LLFloater* LLFloaterReg::getLastFloaterCascading()
{
    LLRect candidate_rect;
    candidate_rect.mTop = 100000;
    LLFloater* candidate_floater = NULL;

    std::map<std::string,std::string>::const_iterator it = sGroupMap.begin(), it_end = sGroupMap.end();
    for( ; it != it_end; ++it)
    {
        const std::string& group_name = it->second;

        instance_list_t& instances = sInstanceMap[group_name];

        for (LLFloater* inst : instances)
        {
            if (inst->getVisible()
                && (inst->isPositioning(LLFloaterEnums::POSITIONING_CASCADING)
                    || inst->isPositioning(LLFloaterEnums::POSITIONING_CASCADE_GROUP)))
            {
                if (candidate_rect.mTop > inst->getRect().mTop)
                {
                    candidate_floater = inst;
                    candidate_rect = inst->getRect();
                }
            }
        }
    }

    return candidate_floater;
}

//static
LLFloater* LLFloaterReg::findInstance(std::string_view name, const LLSD& key)
{
    LLFloater* res = NULL;
    auto it = sGroupMap.find(name);
    if (it != sGroupMap.end())
    {
        const std::string& groupname = it->second;
        if (!groupname.empty())
        {
            instance_list_t& list = sInstanceMap[groupname];
            for (LLFloater* inst : list)
            {
                if (inst->matchesKey(key))
                {
                    res = inst;
                    break;
                }
            }
        }
    }
    return res;
}

//static
LLFloater* LLFloaterReg::getInstance(std::string_view name, const LLSD& key)
{
    LLFloater* res = findInstance(name, key);
    if (!res)
    {
        auto it = sBuildMap.find(name);
        if (it != sBuildMap.end())
        {
            const LLFloaterBuildFunc& build_func = it->second.mFunc;
            const std::string& xui_file = it->second.mFile;
            if (build_func)
            {
                auto it = sGroupMap.find(name);
                if (it != sGroupMap.end())
                {
                    const std::string& groupname = it->second;
                    if (!groupname.empty())
                    {
                        instance_list_t& list = sInstanceMap[groupname];

                        res = build_func(key);
                        if (!res)
                        {
                            LL_WARNS() << "Failed to build floater type: '" << name << "'." << LL_ENDL;
                            return NULL;
                        }
                        bool success = res->buildFromFile(xui_file);
                        if (!success)
                        {
                            LL_WARNS() << "Failed to build floater type: '" << name << "'." << LL_ENDL;
                            return NULL;
                        }

                        // Note: key should eventually be a non optional LLFloater arg; for now, set mKey to be safe
                        if (res->mKey.isUndefined())
                        {
                            res->mKey = key;
                        }
                        res->setInstanceName(std::string(name));

                        LLFloater* last_floater = (list.empty() ? NULL : list.back());

                        res->applyControlsAndPosition(last_floater);

                        gFloaterView->adjustToFitScreen(res, false);

                        list.push_back(res);
                    }
                }
            }
        }
        if (!res)
        {
            LL_WARNS() << "Floater type: '" << name << "' not registered." << LL_ENDL;
        }
    }
    return res;
}

//static
LLFloater* LLFloaterReg::removeInstance(std::string_view name, const LLSD& key)
{
    LLFloater* res = NULL;
    auto it = sGroupMap.find(name);
    if (it != sGroupMap.end())
    {
        const std::string& groupname = it->second;
        if (!groupname.empty())
        {
            instance_list_t& list = sInstanceMap[groupname];
            for (instance_list_t::iterator iter = list.begin(); iter != list.end(); ++iter)
            {
                LLFloater* inst = *iter;
                if (inst->matchesKey(key))
                {
                    res = inst;
                    list.erase(iter);
                    break;
                }
            }
        }
    }
    return res;
}

//static
// returns true if the instance existed
bool LLFloaterReg::destroyInstance(std::string_view name, const LLSD& key)
{
    LLFloater* inst = removeInstance(name, key);
    if (inst)
    {
        delete inst;
        return true;
    }
    else
    {
        return false;
    }
}

// Iterators
//static
LLFloaterReg::const_instance_list_t& LLFloaterReg::getFloaterList(std::string_view name)
{
    instance_map_t::iterator iter = sInstanceMap.find(name);
    if (iter != sInstanceMap.end())
    {
        return iter->second;
    }
    else
    {
        return sNullInstanceList;
    }
}

// Visibility Management

//static
LLFloater* LLFloaterReg::showInstance(std::string_view name, const LLSD& key, bool focus)
{
    if( sBlockShowFloaters
            // see EXT-7090
            && sAlwaysShowableList.find(name) == sAlwaysShowableList.end())
        return 0;//
    LLFloater* instance = getInstance(name, key);
    if (instance)
    {
        instance->openFloater(key);
        if (focus)
            instance->setFocus(true);
    }
    return instance;
}

//static
// returns true if the instance exists
bool LLFloaterReg::hideInstance(std::string_view name, const LLSD& key)
{
    LLFloater* instance = findInstance(name, key);
    if (instance)
    {
        instance->closeHostedFloater();
    }
    return (instance != NULL);
}

//static
// returns true if the instance is visible when completed
bool LLFloaterReg::toggleInstance(std::string_view name, const LLSD& key)
{
    LLFloater* instance = findInstance(name, key);
    if (instance && instance->isShown())
    {
        instance->closeHostedFloater();
        return false;
    }

    instance = showInstance(name, key, true);

    return instance != nullptr;
}

//static
// returns true if the instance exists and is visible (doesnt matter minimized or not)
bool LLFloaterReg::instanceVisible(std::string_view name, const LLSD& key)
{
    LLFloater* instance = findInstance(name, key);
    return LLFloater::isVisible(instance);
}

//static
void LLFloaterReg::showInitialVisibleInstances()
{
    // Iterate through alll registered instance names and show any with a save visible state
    for (build_map_t::iterator iter = sBuildMap.begin(); iter != sBuildMap.end(); ++iter)
    {
        const std::string& name = iter->first;
        std::string controlname = getVisibilityControlName(name);
        if (LLFloater::getControlGroup()->controlExists(controlname))
        {
            bool isvis = LLFloater::getControlGroup()->getBOOL(controlname);
            if (isvis)
            {
                showInstance(name, LLSD()); // keyed floaters shouldn't set save_vis to true
            }
        }
    }
}

//static
void LLFloaterReg::hideVisibleInstances(const std::set<std::string>& exceptions)
{
    // Iterate through alll active instances and hide them
    for (instance_map_t::iterator iter = sInstanceMap.begin(); iter != sInstanceMap.end(); ++iter)
    {
        const std::string& name = iter->first;
        if (exceptions.find(name) != exceptions.end())
            continue;
        instance_list_t& list = iter->second;
        for (instance_list_t::iterator iter = list.begin(); iter != list.end(); ++iter)
        {
            LLFloater* floater = *iter;
            floater->pushVisible(false);
        }
    }
}

//static
void LLFloaterReg::restoreVisibleInstances()
{
    // Iterate through all active instances and restore visibility
    for (instance_map_t::iterator iter = sInstanceMap.begin(); iter != sInstanceMap.end(); ++iter)
    {
        instance_list_t& list = iter->second;
        for (instance_list_t::iterator iter = list.begin(); iter != list.end(); ++iter)
        {
            LLFloater* floater = *iter;
            floater->popVisible();
        }
    }
}

//static
std::string LLFloaterReg::getRectControlName(const std::string& name)
{
    return std::string("floater_rect_") + getBaseControlName(name);
}

//static
std::string LLFloaterReg::declareRectControl(const std::string& name)
{
    std::string controlname = getRectControlName(name);
    LLFloater::getControlGroup()->declareRect(controlname, LLRect(),
                                              llformat("Window Size for %s", name.c_str()),
                                              LLControlVariable::PERSIST_NONDFT);
    return controlname;
}

std::string LLFloaterReg::declarePosXControl(const std::string& name)
{
    std::string controlname = std::string("floater_pos_") + getBaseControlName(name) + "_x";
    LLFloater::getControlGroup()->declareF32(controlname,
                                            10.f,
                                            llformat("Window X Position for %s", name.c_str()),
                                            LLControlVariable::PERSIST_NONDFT);
    return controlname;
}

std::string LLFloaterReg::declarePosYControl(const std::string& name)
{
    std::string controlname = std::string("floater_pos_") + getBaseControlName(name) + "_y";
    LLFloater::getControlGroup()->declareF32(controlname,
                                            10.f,
                                            llformat("Window Y Position for %s", name.c_str()),
                                            LLControlVariable::PERSIST_NONDFT);

    return controlname;
}


//static
std::string LLFloaterReg::getVisibilityControlName(const std::string& name)
{
    return std::string("floater_vis_") + getBaseControlName(name);
}

//static
std::string LLFloaterReg::getBaseControlName(const std::string& name)
{
    std::string res(name);
    LLStringUtil::replaceChar( res, ' ', '_' );
    return res;
}


//static
std::string LLFloaterReg::declareVisibilityControl(const std::string& name)
{
    std::string controlname = getVisibilityControlName(name);
    LLFloater::getControlGroup()->declareBOOL(controlname, false,
                                                 llformat("Window Visibility for %s", name.c_str()),
                                                 LLControlVariable::PERSIST_NONDFT);
    return controlname;
}

//static
std::string LLFloaterReg::declareDockStateControl(const std::string& name)
{
    std::string controlname = getDockStateControlName(name);
    LLFloater::getControlGroup()->declareBOOL(controlname, true,
                                                 llformat("Window Docking state for %s", name.c_str()),
                                                 LLControlVariable::PERSIST_NONDFT);
    return controlname;

}

//static
std::string LLFloaterReg::getDockStateControlName(const std::string& name)
{
    std::string res = std::string("floater_dock_") + name;
    LLStringUtil::replaceChar( res, ' ', '_' );
    return res;
}


//static
void LLFloaterReg::registerControlVariables()
{
    // Iterate through alll registered instance names and register rect and visibility control variables
    for (build_map_t::iterator iter = sBuildMap.begin(); iter != sBuildMap.end(); ++iter)
    {
        const std::string& name = iter->first;
        if (LLFloater::getControlGroup()->controlExists(getRectControlName(name)))
        {
            declareRectControl(name);
        }
        if (LLFloater::getControlGroup()->controlExists(getVisibilityControlName(name)))
        {
            declareVisibilityControl(name);
        }
    }

    const LLSD& exclude_list = LLUI::getInstance()->mSettingGroups["config"]->getLLSD("always_showable_floaters");
    for (LLSD::array_const_iterator iter = exclude_list.beginArray();
        iter != exclude_list.endArray();
        iter++)
    {
        sAlwaysShowableList.insert(iter->asString());
    }
}

//static
void LLFloaterReg::toggleInstanceOrBringToFront(const LLSD& sdname, const LLSD& key)
{
    //
    // Floaters controlled by the toolbar behave a bit differently from others.
    // Namely they have 3-4 states as defined in the design wiki page here:
    //   https://wiki.lindenlab.com/wiki/FUI_Button_states
    //
    // The basic idea is this:
    // * If the target floater is minimized, this button press will un-minimize it.
    // * Else if the target floater is closed open it.
    // * Else if the target floater does not have focus, give it focus.
    //       * Also, if it is not on top, bring it forward when focus is given.
    // * Else the target floater is open, close it.
    //
    std::string name = sdname.asString();
    LLFloater* instance = getInstance(name, key);

    if (!instance)
    {
        LL_DEBUGS() << "Unable to get instance of floater '" << name << "'" << LL_ENDL;
        return;
    }

    // If hosted, we need to take that into account
    LLFloater* host = instance->getHost();

    if (host)
    {
        if (host->isMinimized() || !host->isShown() || !host->isFrontmost())
        {
            host->setMinimized(false);
            instance->openFloater(key);
            instance->setVisibleAndFrontmost(true, key);
        }
        else if (!instance->getVisible())
        {
            instance->openFloater(key);
            instance->setVisibleAndFrontmost(true, key);
            instance->setFocus(true);
        }
        else
        {
            instance->closeHostedFloater();
        }
    }
    else
    {
        if (instance->isMinimized())
        {
            instance->setMinimized(false);
            instance->setVisibleAndFrontmost(true, key);
        }
        else if (!instance->isShown())
        {
            instance->openFloater(key);
            instance->setVisibleAndFrontmost(true, key);
        }
        else if (!instance->isFrontmost())
        {
            instance->setVisibleAndFrontmost(true, key);
        }
        else
        {
            instance->closeHostedFloater();
        }
    }
}

// static
// Same as toggleInstanceOrBringToFront but does not close floater.
// unlike showInstance() does not trigger onOpen() if already open
void LLFloaterReg::showInstanceOrBringToFront(const LLSD& sdname, const LLSD& key)
{
    std::string name = sdname.asString();
    LLFloater* instance = getInstance(name, key);


    if (!instance)
    {
        LL_DEBUGS() << "Unable to get instance of floater '" << name << "'" << LL_ENDL;
        return;
    }

    // If hosted, we need to take that into account
    LLFloater* host = instance->getHost();

    if (host)
    {
        if (host->isMinimized() || !host->isShown() || !host->isFrontmost())
        {
            host->setMinimized(false);
            instance->openFloater(key);
            instance->setVisibleAndFrontmost(true, key);
        }
        else if (!instance->getVisible())
        {
            instance->openFloater(key);
            instance->setVisibleAndFrontmost(true, key);
            instance->setFocus(true);
        }
    }
    else
    {
        if (instance->isMinimized())
        {
            instance->setMinimized(false);
            instance->setVisibleAndFrontmost(true, key);
        }
        else if (!instance->isShown())
        {
            instance->openFloater(key);
            instance->setVisibleAndFrontmost(true, key);
        }
        else if (!instance->isFrontmost())
        {
            instance->setVisibleAndFrontmost(true, key);
        }
    }
}

// static
U32 LLFloaterReg::getVisibleFloaterInstanceCount()
{
    U32 count = 0;

    std::map<std::string,std::string>::const_iterator it = sGroupMap.begin(), it_end = sGroupMap.end();
    for( ; it != it_end; ++it)
    {
        const std::string& group_name = it->second;

        instance_list_t& instances = sInstanceMap[group_name];

        for (instance_list_t::const_iterator iter = instances.begin(); iter != instances.end(); ++iter)
        {
            LLFloater* inst = *iter;

            if (inst->getVisible() && !inst->isMinimized())
            {
                count++;
            }
        }
    }

    return count;
}

LLSD LLFloaterReg::getFloaterNames()
{
    return llsd::toArray(sGroupMap, [](const auto &pair) { return pair.first; });
}