/**
 * @file llpaneltopinfobar.cpp
 * @brief Coordinates and Parcel Settings information panel definition
 *
 * $LicenseInfo:firstyear=2010&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 "llviewerprecompiledheaders.h"

#include "llpaneltopinfobar.h"

#include "llagent.h"
#include "llagentui.h"
#include "llclipboard.h"
#include "llfloaterreg.h"
#include "llfloatersidepanelcontainer.h"
#include "lllandmarkactions.h"
#include "lllocationinputctrl.h"
#include "llnotificationsutil.h"
#include "llparcel.h"
#include "llslurl.h"
#include "llstatusbar.h"
#include "lltrans.h"
#include "llviewercontrol.h"
#include "llviewerinventory.h"
#include "llviewermenu.h"
#include "llviewerparcelmgr.h"
#include "llviewerregion.h"

class LLPanelTopInfoBar::LLParcelChangeObserver : public LLParcelObserver
{
public:
    LLParcelChangeObserver(LLPanelTopInfoBar* topInfoBar) : mTopInfoBar(topInfoBar) {}

private:
    /*virtual*/ void changed()
    {
        if (mTopInfoBar)
        {
            mTopInfoBar->updateParcelIcons();
        }
    }

    LLPanelTopInfoBar* mTopInfoBar;
};

LLPanelTopInfoBar::LLPanelTopInfoBar(): mParcelChangedObserver(0)
{
    buildFromFile( "panel_topinfo_bar.xml");
}

LLPanelTopInfoBar::~LLPanelTopInfoBar()
{
    if (mParcelChangedObserver)
    {
        LLViewerParcelMgr::getInstance()->removeObserver(mParcelChangedObserver);
        delete mParcelChangedObserver;
    }

    if (mParcelPropsCtrlConnection.connected())
    {
        mParcelPropsCtrlConnection.disconnect();
    }

    if (mParcelMgrConnection.connected())
    {
        mParcelMgrConnection.disconnect();
    }

    if (mShowCoordsCtrlConnection.connected())
    {
        mShowCoordsCtrlConnection.disconnect();
    }
}

void LLPanelTopInfoBar::initParcelIcons()
{
    mParcelIcon[VOICE_ICON] = getChild<LLIconCtrl>("voice_icon");
    mParcelIcon[FLY_ICON] = getChild<LLIconCtrl>("fly_icon");
    mParcelIcon[PUSH_ICON] = getChild<LLIconCtrl>("push_icon");
    mParcelIcon[BUILD_ICON] = getChild<LLIconCtrl>("build_icon");
    mParcelIcon[SCRIPTS_ICON] = getChild<LLIconCtrl>("scripts_icon");
    mParcelIcon[DAMAGE_ICON] = getChild<LLIconCtrl>("damage_icon");
    mParcelIcon[SEE_AVATARS_ICON] = getChild<LLIconCtrl>("see_avatars_icon");

    mParcelIcon[VOICE_ICON]->setToolTip(LLTrans::getString("LocationCtrlVoiceTooltip"));
    mParcelIcon[FLY_ICON]->setToolTip(LLTrans::getString("LocationCtrlFlyTooltip"));
    mParcelIcon[PUSH_ICON]->setToolTip(LLTrans::getString("LocationCtrlPushTooltip"));
    mParcelIcon[BUILD_ICON]->setToolTip(LLTrans::getString("LocationCtrlBuildTooltip"));
    mParcelIcon[SCRIPTS_ICON]->setToolTip(LLTrans::getString("LocationCtrlScriptsTooltip"));
    mParcelIcon[DAMAGE_ICON]->setToolTip(LLTrans::getString("LocationCtrlDamageTooltip"));
    mParcelIcon[SEE_AVATARS_ICON]->setToolTip(LLTrans::getString("LocationCtrlSeeAVsTooltip"));

    mParcelIcon[VOICE_ICON]->setMouseDownCallback(boost::bind(&LLPanelTopInfoBar::onParcelIconClick, this, VOICE_ICON));
    mParcelIcon[FLY_ICON]->setMouseDownCallback(boost::bind(&LLPanelTopInfoBar::onParcelIconClick, this, FLY_ICON));
    mParcelIcon[PUSH_ICON]->setMouseDownCallback(boost::bind(&LLPanelTopInfoBar::onParcelIconClick, this, PUSH_ICON));
    mParcelIcon[BUILD_ICON]->setMouseDownCallback(boost::bind(&LLPanelTopInfoBar::onParcelIconClick, this, BUILD_ICON));
    mParcelIcon[SCRIPTS_ICON]->setMouseDownCallback(boost::bind(&LLPanelTopInfoBar::onParcelIconClick, this, SCRIPTS_ICON));
    mParcelIcon[DAMAGE_ICON]->setMouseDownCallback(boost::bind(&LLPanelTopInfoBar::onParcelIconClick, this, DAMAGE_ICON));
    mParcelIcon[SEE_AVATARS_ICON]->setMouseDownCallback(boost::bind(&LLPanelTopInfoBar::onParcelIconClick, this, SEE_AVATARS_ICON));

    mDamageText->setText(LLStringExplicit("100%"));
}

void LLPanelTopInfoBar::handleLoginComplete()
{
    // An agent parcel update hasn't occurred yet, so
    // we have to manually set location and the icons.
    update();
}

bool LLPanelTopInfoBar::handleRightMouseDown(S32 x, S32 y, MASK mask)
{
    if(!LLUICtrl::CommitCallbackRegistry::getValue("TopInfoBar.Action"))
    {
        LLUICtrl::CommitCallbackRegistry::currentRegistrar()
                .add("TopInfoBar.Action", boost::bind(&LLPanelTopInfoBar::onContextMenuItemClicked, this, _2));
    }
    show_topinfobar_context_menu(this, x, y);
    return true;
}

bool LLPanelTopInfoBar::postBuild()
{
    mInfoBtn = getChild<LLButton>("place_info_btn");
    mInfoBtn->setClickedCallback(boost::bind(&LLPanelTopInfoBar::onInfoButtonClicked, this));
    mInfoBtn->setToolTip(LLTrans::getString("LocationCtrlInfoBtnTooltip"));

    mParcelInfoText = getChild<LLTextBox>("parcel_info_text");
    mDamageText = getChild<LLTextBox>("damage_text");

    initParcelIcons();

    mParcelChangedObserver = new LLParcelChangeObserver(this);
    LLViewerParcelMgr::getInstance()->addObserver(mParcelChangedObserver);

    // Connecting signal for updating parcel icons on "Show Parcel Properties" setting change.
    LLControlVariable* ctrl = gSavedSettings.getControl("NavBarShowParcelProperties").get();
    if (ctrl)
    {
        mParcelPropsCtrlConnection = ctrl->getSignal()->connect(boost::bind(&LLPanelTopInfoBar::updateParcelIcons, this));
    }

    // Connecting signal for updating parcel text on "Show Coordinates" setting change.
    ctrl = gSavedSettings.getControl("NavBarShowCoordinates").get();
    if (ctrl)
    {
        mShowCoordsCtrlConnection = ctrl->getSignal()->connect(boost::bind(&LLPanelTopInfoBar::onNavBarShowParcelPropertiesCtrlChanged, this));
    }

    mParcelMgrConnection = gAgent.addParcelChangedCallback(
            boost::bind(&LLPanelTopInfoBar::onAgentParcelChange, this));

    setVisibleCallback(boost::bind(&LLPanelTopInfoBar::onVisibilityChanged, this, _2));

    return true;
}

void LLPanelTopInfoBar::onNavBarShowParcelPropertiesCtrlChanged()
{
    std::string new_text;

    // don't need to have separate show_coords variable; if user requested the coords to be shown
    // they will be added during the next call to the draw() method.
    buildLocationString(new_text, false);
    setParcelInfoText(new_text);
}

// when panel is shown, all minimized floaters should be shifted downwards to prevent overlapping of
// PanelTopInfoBar. See EXT-7951.
void LLPanelTopInfoBar::onVisibilityChanged(const LLSD& show)
{
    // this height is used as a vertical offset for ALREADY MINIMIZED floaters
    // when PanelTopInfoBar visibility changes
    S32 height = getRect().getHeight();

    // this vertical offset is used for a start minimize position of floaters that
    // are NOT MIMIMIZED YET
    S32 minimize_pos_offset = 0;

    if (show.asBoolean())
    {
        height = minimize_pos_offset = -height;
    }

    gFloaterView->shiftFloaters(0, height);
    gFloaterView->setMinimizePositionVerticalOffset(minimize_pos_offset);
}

boost::signals2::connection LLPanelTopInfoBar::setResizeCallback( const resize_signal_t::slot_type& cb )
{
    return mResizeSignal.connect(cb);
}

void LLPanelTopInfoBar::draw()
{
    updateParcelInfoText();
    updateHealth();

    LLPanel::draw();
}

void LLPanelTopInfoBar::buildLocationString(std::string& loc_str, bool show_coords)
{
    LLAgentUI::ELocationFormat format =
        (show_coords ? LLAgentUI::LOCATION_FORMAT_FULL : LLAgentUI::LOCATION_FORMAT_NO_COORDS);

    if (!LLAgentUI::buildLocationString(loc_str, format))
    {
        loc_str = "???";
    }
}

void LLPanelTopInfoBar::setParcelInfoText(const std::string& new_text)
{
    LLRect old_rect = getRect();
    const LLFontGL* font = mParcelInfoText->getFont();
    S32 new_text_width = font->getWidth(new_text);

    mParcelInfoText->setText(new_text);

    LLRect rect = mParcelInfoText->getRect();
    rect.setOriginAndSize(rect.mLeft, rect.mBottom, new_text_width, rect.getHeight());

    mParcelInfoText->reshape(rect.getWidth(), rect.getHeight(), true);
    mParcelInfoText->setRect(rect);
    layoutParcelIcons();

    if (old_rect != getRect())
    {
        mResizeSignal();
    }
}

void LLPanelTopInfoBar::update()
{
    std::string new_text;

    // don't need to have separate show_coords variable; if user requested the coords to be shown
    // they will be added during the next call to the draw() method.
    buildLocationString(new_text, false);
    setParcelInfoText(new_text);

    updateParcelIcons();
}

void LLPanelTopInfoBar::updateParcelInfoText()
{
    static LLUICachedControl<bool> show_coords("NavBarShowCoordinates", false);

    if (show_coords)
    {
        std::string new_text;

        buildLocationString(new_text, show_coords);
        setParcelInfoText(new_text);
    }
}

void LLPanelTopInfoBar::updateParcelIcons()
{
    LLViewerParcelMgr* vpm = LLViewerParcelMgr::getInstance();

    LLViewerRegion* agent_region = gAgent.getRegion();
    LLParcel* agent_parcel = vpm->getAgentParcel();
    if (!agent_region || !agent_parcel)
        return;

    if (gSavedSettings.getBOOL("NavBarShowParcelProperties"))
    {
        LLParcel* current_parcel;
        LLViewerRegion* selection_region = vpm->getSelectionRegion();
        LLParcel* selected_parcel = vpm->getParcelSelection()->getParcel();

        // If agent is in selected parcel we use its properties because
        // they are updated more often by LLViewerParcelMgr than agent parcel properties.
        // See LLViewerParcelMgr::processParcelProperties().
        // This is needed to reflect parcel restrictions changes without having to leave
        // the parcel and then enter it again. See EXT-2987
        if (selected_parcel && selected_parcel->getLocalID() == agent_parcel->getLocalID()
                && selection_region == agent_region)
        {
            current_parcel = selected_parcel;
        }
        else
        {
            current_parcel = agent_parcel;
        }

        bool allow_voice    = vpm->allowAgentVoice(agent_region, current_parcel);
        bool allow_fly      = vpm->allowAgentFly(agent_region, current_parcel);
        bool allow_push     = vpm->allowAgentPush(agent_region, current_parcel);
        bool allow_build    = vpm->allowAgentBuild(current_parcel); // true when anyone is allowed to build. See EXT-4610.
        bool allow_scripts  = vpm->allowAgentScripts(agent_region, current_parcel);
        bool allow_damage   = vpm->allowAgentDamage(agent_region, current_parcel);
        bool see_avs        = current_parcel->getSeeAVs();

        // Most icons are "block this ability"
        mParcelIcon[VOICE_ICON]->setVisible(   !allow_voice );
        mParcelIcon[FLY_ICON]->setVisible(     !allow_fly );
        mParcelIcon[PUSH_ICON]->setVisible(    !allow_push );
        mParcelIcon[BUILD_ICON]->setVisible(   !allow_build );
        mParcelIcon[SCRIPTS_ICON]->setVisible( !allow_scripts );
        mParcelIcon[DAMAGE_ICON]->setVisible(  allow_damage );
        mDamageText->setVisible(allow_damage);
        mParcelIcon[SEE_AVATARS_ICON]->setVisible( !see_avs );

        layoutParcelIcons();
    }
    else
    {
        for (S32 i = 0; i < ICON_COUNT; ++i)
        {
            mParcelIcon[i]->setVisible(false);
        }
        mDamageText->setVisible(false);
    }
}

void LLPanelTopInfoBar::updateHealth()
{
    static LLUICachedControl<bool> show_icons("NavBarShowParcelProperties", false);

    // *FIXME: Status bar owns health information, should be in agent
    if (show_icons && gStatusBar)
    {
        static S32 last_health = -1;
        S32 health = gStatusBar->getHealth();
        if (health != last_health)
        {
            std::string text = llformat("%d%%", health);
            mDamageText->setText(text);
            last_health = health;
        }
    }
}

void LLPanelTopInfoBar::layoutParcelIcons()
{
    LLRect old_rect = getRect();

    // TODO: remove hard-coded values and read them as xml parameters
    static const int FIRST_ICON_HPAD = 32;
    static const int LAST_ICON_HPAD = 11;

    S32 left = mParcelInfoText->getRect().mRight + FIRST_ICON_HPAD;

    left = layoutWidget(mDamageText, left);

    for (int i = ICON_COUNT - 1; i >= 0; --i)
    {
        left = layoutWidget(mParcelIcon[i], left);
    }

    LLRect rect = getRect();
    rect.set(rect.mLeft, rect.mTop, left + LAST_ICON_HPAD, rect.mBottom);
    setRect(rect);

    if (old_rect != getRect())
    {
        mResizeSignal();
    }
}

S32 LLPanelTopInfoBar::layoutWidget(LLUICtrl* ctrl, S32 left)
{
    // TODO: remove hard-coded values and read them as xml parameters
    static const int ICON_HPAD = 2;

    if (ctrl->getVisible())
    {
        LLRect rect = ctrl->getRect();
        rect.mRight = left + rect.getWidth();
        rect.mLeft = left;

        ctrl->setRect(rect);
        left += rect.getWidth() + ICON_HPAD;
    }

    return left;
}

void LLPanelTopInfoBar::onParcelIconClick(EParcelIcon icon)
{
    switch (icon)
    {
    case VOICE_ICON:
        LLNotificationsUtil::add("NoVoice");
        break;
    case FLY_ICON:
        LLNotificationsUtil::add("NoFly");
        break;
    case PUSH_ICON:
        LLNotificationsUtil::add("PushRestricted");
        break;
    case BUILD_ICON:
        LLNotificationsUtil::add("NoBuild");
        break;
    case SCRIPTS_ICON:
    {
        LLViewerRegion* region = gAgent.getRegion();
        if(region && region->getRegionFlag(REGION_FLAGS_ESTATE_SKIP_SCRIPTS))
        {
            LLNotificationsUtil::add("ScriptsStopped");
        }
        else if(region && region->getRegionFlag(REGION_FLAGS_SKIP_SCRIPTS))
        {
            LLNotificationsUtil::add("ScriptsNotRunning");
        }
        else
        {
            LLNotificationsUtil::add("NoOutsideScripts");
        }
        break;
    }
    case DAMAGE_ICON:
        LLNotificationsUtil::add("NotSafe");
        break;
    case SEE_AVATARS_ICON:
        LLNotificationsUtil::add("SeeAvatars");
        break;
    case ICON_COUNT:
        break;
    // no default to get compiler warning when a new icon gets added
    }
}

void LLPanelTopInfoBar::onAgentParcelChange()
{
    update();
}

void LLPanelTopInfoBar::onContextMenuItemClicked(const LLSD::String& item)
{
    if (item == "landmark")
    {
        LLViewerInventoryItem* landmark = LLLandmarkActions::findLandmarkForAgentPos();

        if(landmark == NULL)
        {
            LLFloaterReg::showInstance("add_landmark");
        }
        else
        {
            LLFloaterSidePanelContainer::showPanel("places", LLSD().with("type", "landmark").with("id",landmark->getUUID()));
        }
    }
    else if (item == "copy")
    {
        LLSLURL slurl;
        LLAgentUI::buildSLURL(slurl, false);
        LLUIString location_str(slurl.getSLURLString());

        LLClipboard::instance().copyToClipboard(location_str,0,location_str.length());
    }
}

void LLPanelTopInfoBar::onInfoButtonClicked()
{
    LLFloaterSidePanelContainer::showPanel("places", LLSD().with("type", "agent"));
}