/**
 * @file llhints.cpp
 * @brief Hint popups for displaying context sensitive help in a UI overlay
 *
 * $LicenseInfo:firstyear=2000&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" // must be first include

#include "llhints.h"

#include "llbutton.h"
#include "lltextbox.h"
#include "llviewerwindow.h"
#include "llviewercontrol.h"
#include "lliconctrl.h"
#include "llsdparam.h"

class LLHintPopup : public LLPanel
{
public:

	typedef enum e_popup_direction
	{
		LEFT,
		TOP,
		RIGHT,
		BOTTOM,
		TOP_RIGHT
	} EPopupDirection;

	struct PopupDirections : public LLInitParam::TypeValuesHelper<LLHintPopup::EPopupDirection, PopupDirections>
	{
		static void declareValues()
		{
			declare("left", LLHintPopup::LEFT);
			declare("right", LLHintPopup::RIGHT);
			declare("top", LLHintPopup::TOP);
			declare("bottom", LLHintPopup::BOTTOM);
			declare("top_right", LLHintPopup::TOP_RIGHT);
		}
	};

	struct TargetParams : public LLInitParam::Block<TargetParams>
	{
		Mandatory<std::string>	target;
		Mandatory<EPopupDirection, PopupDirections> direction;

		TargetParams()
		:	target("target"),
			direction("direction")
		{}
	};

	struct Params : public LLInitParam::Block<Params, LLPanel::Params>
	{
		Mandatory<LLNotificationPtr>	notification;
		Optional<TargetParams>			target_params;
		Optional<S32>					distance;
		Optional<LLUIImage*>			left_arrow,
										up_arrow,
										right_arrow,
										down_arrow,
										lower_left_arrow,
										hint_image;
				
		Optional<S32>					left_arrow_offset,
										up_arrow_offset,
										right_arrow_offset,
										down_arrow_offset;
		Optional<F32>					fade_in_time,
										fade_out_time;

		Params()
		:	distance("distance"),
			left_arrow("left_arrow"),
			up_arrow("up_arrow"),
			right_arrow("right_arrow"),
			down_arrow("down_arrow"),
			lower_left_arrow("lower_left_arrow"),
			hint_image("hint_image"),
			left_arrow_offset("left_arrow_offset"),
			up_arrow_offset("up_arrow_offset"),
			right_arrow_offset("right_arrow_offset"),
			down_arrow_offset("down_arrow_offset"),
			fade_in_time("fade_in_time"),
			fade_out_time("fade_out_time")
		{}
	};

	LLHintPopup(const Params&);

	/*virtual*/ BOOL postBuild();

	void onClickClose() 
	{ 
		if (!mHidden) 
		{
			hide(); 
			LLNotifications::instance().cancel(mNotification);
		}
	}
	void draw();
	void hide() { if(!mHidden) {mHidden = true; mFadeTimer.reset();} }

private:
	LLNotificationPtr	mNotification;
	std::string			mTarget;
	EPopupDirection		mDirection;
	S32					mDistance;
	LLUIImagePtr		mArrowLeft,
						mArrowUp,
						mArrowRight,
						mArrowDown,
						mArrowDownAndLeft;
	S32					mArrowLeftOffset,
						mArrowUpOffset,
						mArrowRightOffset,
						mArrowDownOffset;
	LLFrameTimer		mFadeTimer;
	F32					mFadeInTime,
						mFadeOutTime;
	bool				mHidden;
};

static LLDefaultChildRegistry::Register<LLHintPopup> r("hint_popup");


LLHintPopup::LLHintPopup(const LLHintPopup::Params& p)
:	mNotification(p.notification),
	mDirection(TOP),
	mDistance(p.distance),
	mArrowLeft(p.left_arrow),
	mArrowUp(p.up_arrow),
	mArrowRight(p.right_arrow),
	mArrowDown(p.down_arrow),
	mArrowDownAndLeft(p.lower_left_arrow),
	mArrowLeftOffset(p.left_arrow_offset),
	mArrowUpOffset(p.up_arrow_offset),
	mArrowRightOffset(p.right_arrow_offset),
	mArrowDownOffset(p.down_arrow_offset),
	mHidden(false),
	mFadeInTime(p.fade_in_time),
	mFadeOutTime(p.fade_out_time),
	LLPanel(p)
{
	if (p.target_params.isProvided())
	{
		mDirection = p.target_params.direction;
		mTarget = p.target_params.target;
	}
	if (p.hint_image.isProvided())
	{
		buildFromFile("panel_hint_image.xml", p);
		getChild<LLIconCtrl>("hint_image")->setImage(p.hint_image());
	}
	else
	{
		buildFromFile( "panel_hint.xml", p);
	}
}

BOOL LLHintPopup::postBuild()
{
	LLTextBox& hint_text = getChildRef<LLTextBox>("hint_text");
	hint_text.setText(mNotification->getMessage());
	
	getChild<LLButton>("close")->setClickedCallback(boost::bind(&LLHintPopup::onClickClose, this));
	getChild<LLTextBox>("hint_title")->setText(mNotification->getLabel());

	LLRect text_bounds = hint_text.getTextBoundingRect();
	S32 delta_height = text_bounds.getHeight() - hint_text.getRect().getHeight();
	reshape(getRect().getWidth(), getRect().getHeight() + delta_height);
	hint_text.reshape(hint_text.getRect().getWidth(), hint_text.getRect().getHeight() + delta_height);
//	hint_text.translate(0, -delta_height);
	return TRUE;
}

void LLHintPopup::draw()
{
	F32 alpha = 1.f;
	if (mHidden)
	{
		alpha = clamp_rescale(mFadeTimer.getElapsedTimeF32(), 0.f, mFadeOutTime, 1.f, 0.f);
		if (alpha == 0.f)
		{
			die();
			return;
		}
	}
	else
	{
		alpha = clamp_rescale(mFadeTimer.getElapsedTimeF32(), 0.f, mFadeInTime, 0.f, 1.f);
	}
	
	LLIconCtrl* hint_icon = findChild<LLIconCtrl>("hint_image");

	if (hint_icon)
	{
		LLUIImagePtr hint_image = hint_icon->getImage();
		S32 image_height = hint_image.isNull() ? 0 : hint_image->getHeight();
		S32 image_width = hint_image.isNull() ? 0 : hint_image->getWidth();

		LLView* layout_stack = hint_icon->getParent()->getParent();
		S32 delta_height = image_height - layout_stack->getRect().getHeight();
		hint_icon->getParent()->reshape(image_width, hint_icon->getParent()->getRect().getHeight());
		layout_stack->reshape(layout_stack->getRect().getWidth(), image_height);
		layout_stack->translate(0, -delta_height);

		LLRect hint_rect = getLocalRect();
		reshape(hint_rect.getWidth(), hint_rect.getHeight() + delta_height);
	}

	{	LLViewDrawContext context(alpha); 

		if (mTarget.empty())
		{
			// just draw contents, no arrow, in default position
			LLPanel::draw();
		}
		else 
		{
			LLView* targetp = LLHints::getInstance()->getHintTarget(mTarget).get();
			if (!targetp)
			{
				// target widget is no longer valid, go away
				die();
			}
			else if (!targetp->isInVisibleChain()) 
			{
				// if target is invisible, don't draw, but keep alive in case widget comes back
				// but do make it so that it allows mouse events to pass through
				setEnabled(false);
				setMouseOpaque(false);
			}
			else
			{
				// revert back enabled and mouse opaque state in case we disabled it before
				setEnabled(true);
				setMouseOpaque(true);

				LLRect target_rect;
				targetp->localRectToOtherView(targetp->getLocalRect(), &target_rect, getParent());

				LLRect my_local_rect = getLocalRect();
				LLRect my_rect;
				LLRect arrow_rect;
				LLUIImagePtr arrow_imagep;

				switch(mDirection)
				{
				case LEFT:
					my_rect.setCenterAndSize(	target_rect.mLeft - (my_local_rect.getWidth() / 2 + mDistance), 
												target_rect.getCenterY(), 
												my_local_rect.getWidth(), 
												my_local_rect.getHeight());
					if (mArrowRight)
					{
						arrow_rect.setCenterAndSize(my_local_rect.mRight + mArrowRight->getWidth() / 2 + mArrowRightOffset,
													my_local_rect.getCenterY(),
													mArrowRight->getWidth(), 
													mArrowRight->getHeight());
						arrow_imagep = mArrowRight;
					}
					break;
				case TOP:
					my_rect.setCenterAndSize(	target_rect.getCenterX(), 
												target_rect.mTop + (my_local_rect.getHeight() / 2 + mDistance), 
												my_local_rect.getWidth(), 
												my_local_rect.getHeight());
					if (mArrowDown)
					{
						arrow_rect.setCenterAndSize(my_local_rect.getCenterX(),
													my_local_rect.mBottom - mArrowDown->getHeight() / 2 + mArrowDownOffset,
													mArrowDown->getWidth(), 
													mArrowDown->getHeight());
						arrow_imagep = mArrowDown;
					}
					break;
				case RIGHT:
					my_rect.setCenterAndSize(	target_rect.mRight + (my_local_rect.getWidth() / 2 + mDistance), 
												target_rect.getCenterY(),
												my_local_rect.getWidth(), 
												my_local_rect.getHeight());
					if (mArrowLeft)
					{
						arrow_rect.setCenterAndSize(my_local_rect.mLeft - mArrowLeft->getWidth() / 2 + mArrowLeftOffset,
													my_local_rect.getCenterY(),
													mArrowLeft->getWidth(), 
													mArrowLeft->getHeight());
						arrow_imagep = mArrowLeft;
					}
					break;
				case BOTTOM:
					my_rect.setCenterAndSize(	target_rect.getCenterX(), 
												target_rect.mBottom - (my_local_rect.getHeight() / 2 + mDistance),
												my_local_rect.getWidth(), 
												my_local_rect.getHeight());
					if (mArrowUp)
					{
						arrow_rect.setCenterAndSize(my_local_rect.getCenterX(),
													my_local_rect.mTop + mArrowUp->getHeight() / 2 + mArrowUpOffset,
													mArrowUp->getWidth(), 
													mArrowUp->getHeight());
						arrow_imagep = mArrowUp;
					}
					break;
				case TOP_RIGHT:
					my_rect.setCenterAndSize(	target_rect.mRight + (my_local_rect.getWidth() / 2),
												target_rect.mTop + (my_local_rect.getHeight() / 2 + mDistance),
												my_local_rect.getWidth(), 
												my_local_rect.getHeight());
					if (mArrowDownAndLeft)
					{
						arrow_rect.setCenterAndSize(my_local_rect.mLeft + mArrowDownAndLeft->getWidth() / 2 + mArrowLeftOffset,
													my_local_rect.mBottom - mArrowDownAndLeft->getHeight() / 2 + mArrowDownOffset,
													mArrowDownAndLeft->getWidth(), 
													mArrowDownAndLeft->getHeight());
						arrow_imagep = mArrowDownAndLeft;
					}
				}
				setShape(my_rect);
				LLPanel::draw();

				if (arrow_imagep) arrow_imagep->draw(arrow_rect, LLColor4(1.f, 1.f, 1.f, alpha));
			}
		}
	}
}


/// LLHints

LLHints::LLHints()
{
    LLControlVariablePtr control = gSavedSettings.getControl("EnableUIHints");
    mControlConnection = control->getSignal()->connect(boost::bind(&LLHints::showHints, this, _2));
    gViewerWindow->getHintHolder()->setVisible(control->getValue().asBoolean());
}

LLHints::~LLHints()
{
    mControlConnection.disconnect();
}

void LLHints::show(LLNotificationPtr hint)
{
	LLHintPopup::Params p(LLUICtrlFactory::getDefaultParams<LLHintPopup>());

	LLParamSDParser parser;
	parser.readSD(hint->getPayload(), p, true);
	p.notification = hint;

	if (p.validateBlock())
	{
		LLHintPopup* popup = new LLHintPopup(p);

		mHints[hint] = popup;

		LLView* hint_holder = gViewerWindow->getHintHolder();
		if (hint_holder)
		{
			hint_holder->addChild(popup);
			popup->centerWithin(hint_holder->getLocalRect());
		}
	}
}

void LLHints::hide(LLNotificationPtr hint)
{
	hint_map_t::iterator found_it = mHints.find(hint);
	if (found_it != mHints.end())
	{
		found_it->second->hide();
		mHints.erase(found_it);
	}
}

void LLHints::registerHintTarget(const std::string& name, LLHandle<LLView> target)
{
	mTargetRegistry.defaultRegistrar().replace(name, target);
}

LLHandle<LLView> LLHints::getHintTarget(const std::string& name)
{
	LLHandle<LLView>* handlep = mTargetRegistry.getValue(name);
	if (handlep) 
	{
		return *handlep;
	}
	else
	{
		return LLHandle<LLView>();
	}
}

void LLHints::showHints(const LLSD& show)
{
	bool visible = show.asBoolean();
	gViewerWindow->getHintHolder()->setVisible(visible);
}