/** 
 * @file llhoverview.cpp
 * @brief LLHoverView class implementation
 *
 * $LicenseInfo:firstyear=2001&license=viewergpl$
 * 
 * Copyright (c) 2001-2008, Linden Research, Inc.
 * 
 * Second Life Viewer Source Code
 * The source code in this file ("Source Code") is provided by Linden Lab
 * to you under the terms of the GNU General Public License, version 2.0
 * ("GPL"), unless you have obtained a separate licensing agreement
 * ("Other License"), formally executed by you and Linden Lab.  Terms of
 * the GPL can be found in doc/GPL-license.txt in this distribution, or
 * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
 * 
 * There are special exceptions to the terms and conditions of the GPL as
 * it is applied to this Source Code. View the full text of the exception
 * in the file doc/FLOSS-exception.txt in this software distribution, or
 * online at http://secondlifegrid.net/programs/open_source/licensing/flossexception
 * 
 * By copying, modifying or distributing this software, you acknowledge
 * that you have read and understood your obligations described above,
 * and agree to abide by those obligations.
 * 
 * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
 * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
 * COMPLETENESS OR PERFORMANCE.
 * $/LicenseInfo$
 */

#include "llviewerprecompiledheaders.h"

// self include
#include "llhoverview.h"

// Library includes
#include "llfontgl.h"
#include "message.h"
#include "llgl.h"
#include "llrender.h"
#include "llfontgl.h"
#include "llparcel.h"
#include "lldbstrings.h"
#include "llclickaction.h"

// Viewer includes
#include "llagent.h"
#include "llcachename.h"
#include "llviewercontrol.h"
#include "lldrawable.h"
#include "llpermissions.h"
#include "llresmgr.h"
#include "llselectmgr.h"
#include "lltrans.h"
#include "lltoolmgr.h"
#include "lltoolpie.h"
#include "lltoolselectland.h"
#include "llui.h"
#include "llviewercamera.h"
#include "llviewerobject.h"
#include "llviewerobjectlist.h"
#include "llviewerparcelmgr.h"
#include "llviewerregion.h"
#include "llviewerwindow.h"
#include "llglheaders.h"
#include "llviewerimagelist.h"
//#include "lltoolobjpicker.h"
#include "llhudmanager.h"	// HACK for creating flex obj's

#include "llhudmanager.h" // For testing effects
#include "llhudeffect.h"

//
// Constants
//
const char* DEFAULT_DESC = "(No Description)";
const F32 DELAY_BEFORE_SHOW_TIP = 0.35f;

//
// Local globals
//

LLHoverView *gHoverView = NULL;

//
// Static member functions
//
BOOL LLHoverView::sShowHoverTips = TRUE;

//
// Member functions
//

LLHoverView::LLHoverView(const std::string& name, const LLRect& rect)
:	LLView(name, rect, FALSE)
{
	mDoneHoverPick = FALSE;
	mStartHoverPickTimer = FALSE;
	mHoverActive = FALSE;
	mUseHover = TRUE;
	mTyping = FALSE;
	mHoverOffset.clearVec();
}

LLHoverView::~LLHoverView()
{
}

void LLHoverView::updateHover(LLTool* current_tool)
{
	BOOL picking_tool = (	current_tool == LLToolPie::getInstance() 
							|| current_tool == LLToolSelectLand::getInstance() );

	mUseHover = !gAgent.cameraMouselook() 
				&& picking_tool 
				&& !mTyping;
	if (mUseHover)
	{
		if ((gViewerWindow->getMouseVelocityStat()->getPrev(0) < 0.01f)
			&& (LLViewerCamera::getInstance()->getAngularVelocityStat()->getPrev(0) < 0.01f)
			&& (LLViewerCamera::getInstance()->getVelocityStat()->getPrev(0) < 0.01f))
		{
			if (!mStartHoverPickTimer)
			{
				mStartHoverTimer.reset();
				mStartHoverPickTimer = TRUE;
				//  Clear the existing text so that we do not briefly show the wrong data.
				mText.clear();
			}

			if (mDoneHoverPick)
			{
				// Just update the hover data
				updateText();
			}
			else if (mStartHoverTimer.getElapsedTimeF32() > DELAY_BEFORE_SHOW_TIP)
			{
				gViewerWindow->pickAsync(gViewerWindow->getCurrentMouseX(),
													 gViewerWindow->getCurrentMouseY(), 0, pickCallback );
			}
		}
		else
		{
			cancelHover();
		}
	}

}

void LLHoverView::pickCallback(const LLPickInfo& pick_info)
{
	LLViewerObject* hit_obj = pick_info.getObject();

	if (hit_obj)
	{
		gHoverView->setHoverActive(TRUE);
		LLSelectMgr::getInstance()->setHoverObject(hit_obj);
		gHoverView->mLastHoverObject = hit_obj;
		gHoverView->mHoverOffset = pick_info.mObjectOffset;
	}
	else
	{
		gHoverView->mLastHoverObject = NULL;
	}

	// We didn't hit an object, but we did hit land.
	if (!hit_obj && pick_info.mPosGlobal != LLVector3d::zero)
	{
		gHoverView->setHoverActive(TRUE);
		gHoverView->mHoverLandGlobal = pick_info.mPosGlobal;
		LLViewerParcelMgr::getInstance()->requestHoverParcelProperties( gHoverView->mHoverLandGlobal );
	}
	else
	{
		gHoverView->mHoverLandGlobal = LLVector3d::zero;
	}

	gHoverView->mDoneHoverPick = TRUE;
}

void LLHoverView::setTyping(BOOL b)
{
	mTyping = b;
}


void LLHoverView::cancelHover()
{
	mStartHoverTimer.reset();
	mDoneHoverPick = FALSE;
	mStartHoverPickTimer = FALSE;

	LLSelectMgr::getInstance()->setHoverObject(NULL);
	// Can't do this, some code relies on hover object still being
	// set after the hover is cancelled!  Dammit.  JC
	// mLastHoverObject = NULL;

	setHoverActive(FALSE);
}

void LLHoverView::resetLastHoverObject()
{
	mLastHoverObject = NULL;
}

void LLHoverView::updateText()
{
	LLViewerObject* hit_object = getLastHoverObject();
	std::string line;

	mText.clear();
	if ( hit_object )
	{
		if ( hit_object->isHUDAttachment() )
		{
			// no hover tips for HUD elements, since they can obscure
			// what the HUD is displaying
			return;
		}

		if ( hit_object->isAttachment() )
		{
			// get root of attachment then parent, which is avatar
			LLViewerObject* root_edit = hit_object->getRootEdit();
			if (!root_edit)
			{
				// Strange parenting issue, don't show any text
				return;
			}
			hit_object = (LLViewerObject*)root_edit->getParent();
			if (!hit_object)
			{
				// another strange parenting issue, bail out
				return;
			}
		}

		line.clear();
		if (hit_object->isAvatar())
		{
			LLNameValue* title = hit_object->getNVPair("Title");
			LLNameValue* firstname = hit_object->getNVPair("FirstName");
			LLNameValue* lastname =  hit_object->getNVPair("LastName");
			if (firstname && lastname)
			{
				if (title)
				{
					line.append(title->getString());
					line.append(1, ' ');
				}
				line.append(firstname->getString());
				line.append(1, ' ');
				line.append(lastname->getString());
			}
			else
			{
				line.append(LLTrans::getString("TooltipPerson"));
			}
			mText.push_back(line);
		}
		else
		{
			//
			//  We have hit a regular object (not an avatar or attachment)
			// 

			//
			//  Default prefs will suppress display unless the object is interactive
			//
			BOOL suppressObjectHoverDisplay = !gSavedSettings.getBOOL("ShowAllObjectHoverTip");			
			
			LLSelectNode *nodep = LLSelectMgr::getInstance()->getHoverNode();;
			if (nodep)
			{
				line.clear();
				if (nodep->mName.empty())
				{
					line.append(LLTrans::getString("TooltipNoName"));
				}
				else
				{
					line.append( nodep->mName );
				}
				mText.push_back(line);

				if (!nodep->mDescription.empty()
					&& nodep->mDescription != DEFAULT_DESC)
				{
					mText.push_back( nodep->mDescription );
				}

				// Line: "Owner: James Linden"
				line.clear();
				line.append(LLTrans::getString("TooltipOwner") + " ");

				if (nodep->mValid)
				{
					LLUUID owner;
					std::string name;
					if (!nodep->mPermissions->isGroupOwned())
					{
						owner = nodep->mPermissions->getOwner();
						if (LLUUID::null == owner)
						{
							line.append(LLTrans::getString("TooltipPublic"));
						}
						else if(gCacheName->getFullName(owner, name))
						{
							line.append(name);
						}
						else
						{
							line.append(LLTrans::getString("RetrievingData"));
						}
					}
					else
					{
						std::string name;
						owner = nodep->mPermissions->getGroup();
						if (gCacheName->getGroupName(owner, name))
						{
							line.append(name);
							line.append(LLTrans::getString("TooltipIsGroup"));
						}
						else
						{
							line.append(LLTrans::getString("RetrievingData"));
						}
					}
				}
				else
				{
					line.append(LLTrans::getString("RetrievingData"));
				}
				mText.push_back(line);

				// Build a line describing any special properties of this object.
				LLViewerObject *object = hit_object;
				LLViewerObject *parent = (LLViewerObject *)object->getParent();

				if (object &&
					(object->usePhysics() ||
					 object->flagScripted() || 
					 object->flagHandleTouch() || (parent && parent->flagHandleTouch()) ||
					 object->flagTakesMoney() || (parent && parent->flagTakesMoney()) ||
					 object->flagAllowInventoryAdd() ||
					 object->flagTemporary() ||
					 object->flagPhantom()) )
				{
					line.clear();
					if (object->flagScripted())
					{
						
						line.append(LLTrans::getString("TooltipFlagScript") + " ");
					}

					if (object->usePhysics())
					{
						line.append(LLTrans::getString("TooltipFlagPhysics") + " ");
					}

					if (object->flagHandleTouch() || (parent && parent->flagHandleTouch()) )
					{
						line.append(LLTrans::getString("TooltipFlagTouch") + " ");
						suppressObjectHoverDisplay = FALSE;		//  Show tip
					}

					if (object->flagTakesMoney() || (parent && parent->flagTakesMoney()) )
					{
						line.append(LLTrans::getString("TooltipFlagL$") + " ");
						suppressObjectHoverDisplay = FALSE;		//  Show tip
					}

					if (object->flagAllowInventoryAdd())
					{
						line.append(LLTrans::getString("TooltipFlagDropInventory") + " ");
						suppressObjectHoverDisplay = FALSE;		//  Show tip
					}

					if (object->flagPhantom())
					{
						line.append(LLTrans::getString("TooltipFlagPhantom") + " ");
					}

					if (object->flagTemporary())
					{
						line.append(LLTrans::getString("TooltipFlagTemporary") + " ");
					}

					if (object->usePhysics() || 
						object->flagHandleTouch() ||
						(parent && parent->flagHandleTouch()) )
					{
						line.append(LLTrans::getString("TooltipFlagRightClickMenu") + " ");
					}
					mText.push_back(line);
				}

				// Free to copy / For Sale: L$
				line.clear();
				if (nodep->mValid)
				{
					BOOL for_copy = nodep->mPermissions->getMaskEveryone() & PERM_COPY && object->permCopy();
					BOOL for_sale = nodep->mSaleInfo.isForSale() &&
									nodep->mPermissions->getMaskOwner() & PERM_TRANSFER &&
									(nodep->mPermissions->getMaskOwner() & PERM_COPY ||
									 nodep->mSaleInfo.getSaleType() != LLSaleInfo::FS_COPY);
					if (for_copy)
					{
						line.append(LLTrans::getString("TooltipFreeToCopy"));
						suppressObjectHoverDisplay = FALSE;		//  Show tip
					}
					else if (for_sale)
					{
						LLStringUtil::format_map_t args;
						args["[AMOUNT]"] = llformat("%d", nodep->mSaleInfo.getSalePrice());
						line.append(LLTrans::getString("TooltipForSaleL$", args));
						suppressObjectHoverDisplay = FALSE;		//  Show tip
					}
					else
					{
						// Nothing if not for sale
						// line.append("Not for sale");
					}
				}
				else
				{
					LLStringUtil::format_map_t args;
					args["[MESSAGE]"] = LLTrans::getString("RetrievingData");
					line.append(LLTrans::getString("TooltipForSaleMsg", args));
				}
				mText.push_back(line);
			}
			
			//  If the hover tip shouldn't be shown, delete all the object text
			if (suppressObjectHoverDisplay)
			{
				mText.clear();
			}
		}
	}
	else if ( mHoverLandGlobal != LLVector3d::zero )
	{
		// 
		//  Do not show hover for land unless prefs are set to allow it.
		// 
		
		if (!gSavedSettings.getBOOL("ShowLandHoverTip")) return; 

		// Didn't hit an object, but since we have a land point we
		// must be hovering over land.

		LLParcel* hover_parcel = LLViewerParcelMgr::getInstance()->getHoverParcel();
		LLUUID owner;
		S32 width = 0;
		S32 height = 0;

		if ( hover_parcel )
		{
			owner = hover_parcel->getOwnerID();
			width = S32(LLViewerParcelMgr::getInstance()->getHoverParcelWidth());
			height = S32(LLViewerParcelMgr::getInstance()->getHoverParcelHeight());
		}

		// Line: "Land"
		line.clear();
		line.append(LLTrans::getString("TooltipLand"));
		if (hover_parcel)
		{
			line.append(hover_parcel->getName());
		}
		mText.push_back(line);

		// Line: "Owner: James Linden"
		line.clear();
		line.append(LLTrans::getString("TooltipOwner") + " ");

		if ( hover_parcel )
		{
			std::string name;
			if (LLUUID::null == owner)
			{
				line.append(LLTrans::getString("TooltipPublic"));
			}
			else if (hover_parcel->getIsGroupOwned())
			{
				if (gCacheName->getGroupName(owner, name))
				{
					line.append(name);
					line.append(LLTrans::getString("TooltipIsGroup"));
				}
				else
				{
					line.append(LLTrans::getString("RetrievingData"));
				}
			}
			else if(gCacheName->getFullName(owner, name))
			{
				line.append(name);
			}
			else
			{
				line.append(LLTrans::getString("RetrievingData"));
			}
		}
		else
		{
			line.append(LLTrans::getString("RetrievingData"));
		}
		mText.push_back(line);

		// Line: "no fly, not safe, no build"

		// Don't display properties for your land.  This is just
		// confusing, because you can do anything on your own land.
		if ( hover_parcel && owner != gAgent.getID() )
		{
			S32 words = 0;
			
			line.clear();
			// JC - Keep this in the same order as the checkboxes
			// on the land info panel
			if ( !hover_parcel->getAllowModify() )
			{
				if ( hover_parcel->getAllowGroupModify() )
				{
					line.append(LLTrans::getString("TooltipFlagGroupBuild"));
				}
				else
				{
					line.append(LLTrans::getString("TooltipFlagNoBuild"));
				}
				words++;
			}

			if ( !hover_parcel->getAllowTerraform() )
			{
				if (words) line.append(", ");
				line.append(LLTrans::getString("TooltipFlagNoEdit"));
				words++;
			}

			if ( hover_parcel->getAllowDamage() )
			{
				if (words) line.append(", ");
				line.append(LLTrans::getString("TooltipFlagNotSafe"));
				words++;
			}

			// Maybe we should reflect the estate's block fly bit here as well?  DK 12/1/04
			if ( !hover_parcel->getAllowFly() )
			{
				if (words) line.append(", ");
				line.append(LLTrans::getString("TooltipFlagNoFly"));
				words++;
			}

			if ( !hover_parcel->getAllowOtherScripts() )
			{
				if (words) line.append(", ");
				if ( hover_parcel->getAllowGroupScripts() )
				{
					line.append(LLTrans::getString("TooltipFlagGroupScripts"));
				}
				else
				{
					line.append(LLTrans::getString("TooltipFlagNoScripts"));
				}
				
				words++;
			}

			if (words) 
			{
				mText.push_back(line);
			}
		}

		// Line: "Size: 1x4"
		// Only show for non-public land
		/*
		if ( hover_parcel && LLUUID::null != owner)
		{
			line = llformat("Size: %dx%d", width, height );
			mText.push_back(line);
		}
		*/
		if (hover_parcel && hover_parcel->getParcelFlag(PF_FOR_SALE))
		{
			LLStringUtil::format_map_t args;
			args["[AMOUNT]"] = llformat("%d", hover_parcel->getSalePrice());
			line = LLTrans::getString("TooltipForSaleL$", args);
			mText.push_back(line);
		}
	}
}


void LLHoverView::draw()
{
	if ( !isHovering() )
	{
		return;
	}

	// To toggle off hover tips, you have to just suppress the draw.
	// The picking is still needed to do cursor changes over physical
	// and scripted objects.  JC
	if (!sShowHoverTips) 
	{
		return;
	}

	const F32 MAX_HOVER_DISPLAY_SECS = 5.f;
	if (mHoverTimer.getElapsedTimeF32() > MAX_HOVER_DISPLAY_SECS)
	{
		return;
	}

	const F32 MAX_ALPHA = 0.9f;
	//const F32 STEADY_ALPHA = 0.3f;
	F32 alpha;
	if (mHoverActive)
	{
		alpha = 1.f;

		if (isHoveringObject())
		{
			// look at object
			LLViewerObject *hover_object = getLastHoverObject();
			if (hover_object->isAvatar())
			{
				gAgent.setLookAt(LOOKAT_TARGET_HOVER, getLastHoverObject(), LLVector3::zero);
			}
			else
			{
				gAgent.setLookAt(LOOKAT_TARGET_HOVER, getLastHoverObject(), mHoverOffset);
			}
		}
	}
	else
	{
		alpha = llmax(0.f, MAX_ALPHA - mHoverTimer.getElapsedTimeF32()*2.f);
	}

	// Bail out if no text to display
	if (mText.empty())
	{
		return;
	}

	// Don't draw if no alpha
	if (alpha <= 0.f)
	{
		return;
	}

	LLUIImagePtr box_imagep = LLUI::getUIImage("rounded_square.tga");
	LLUIImagePtr shadow_imagep = LLUI::getUIImage("rounded_square_soft.tga");

	const LLFontGL* fontp = LLResMgr::getInstance()->getRes(LLFONT_SANSSERIF_SMALL);

	// Render text.
	LLColor4 text_color = gColors.getColor("ToolTipTextColor");
	// LLColor4 border_color = gColors.getColor("ToolTipBorderColor");
	LLColor4 bg_color = gColors.getColor("ToolTipBgColor");
	LLColor4 shadow_color = gColors.getColor("ColorDropShadow");

	// Could decrease the alpha here. JC
	//text_color.mV[VALPHA] = alpha;
	//border_color.mV[VALPHA] = alpha;
	//bg_color.mV[VALPHA] = alpha;

	S32 max_width = 0;
	S32 num_lines = mText.size();
	for (text_list_t::iterator iter = mText.begin(); iter != mText.end(); ++iter)
	{
		max_width = llmax(max_width, (S32)fontp->getWidth(*iter));
	}

	S32 left	= mHoverPos.mX + 10;
	S32 top		= mHoverPos.mY - 16;
	S32 right	= mHoverPos.mX + max_width + 30;
	S32 bottom	= mHoverPos.mY - 24 - llfloor(num_lines*fontp->getLineHeight());

	// Push down if there's a one-click icon
	if (mHoverActive
		&& isHoveringObject()
		&& mLastHoverObject->getClickAction() != CLICK_ACTION_NONE)
	{
		const S32 CLICK_OFFSET = 10;
		top -= CLICK_OFFSET;
		bottom -= CLICK_OFFSET;
	}

	// Make sure the rect is completely visible
	LLRect old_rect = getRect();
	setRect( LLRect(left, top, right, bottom ) );
	translateIntoRect( gViewerWindow->getVirtualWindowRect(), FALSE );
	left = getRect().mLeft;
	top = getRect().mTop;
	right = getRect().mRight;
	bottom = getRect().mBottom;
	setRect(old_rect);

	LLGLSUIDefault gls_ui;

	shadow_color.mV[VALPHA] = 0.7f * alpha;
	S32 shadow_offset = gSavedSettings.getS32("DropShadowTooltip");
	shadow_imagep->draw(LLRect(left + shadow_offset, top - shadow_offset, right + shadow_offset, bottom - shadow_offset), shadow_color);

	bg_color.mV[VALPHA] = alpha;
	box_imagep->draw(LLRect(left, top, right, bottom), bg_color);

	S32 cur_offset = top - 4;
	for (text_list_t::iterator iter = mText.begin(); iter != mText.end(); ++iter)
	{
		fontp->renderUTF8(*iter, 0, left + 10, cur_offset, text_color, LLFontGL::LEFT, LLFontGL::TOP);
		cur_offset -= llfloor(fontp->getLineHeight());
	}
}

void LLHoverView::setHoverActive(const BOOL active)
{
	if (active != mHoverActive)
	{
		mHoverTimer.reset();
	}

	mHoverActive = active;

	if (active)
	{
		mHoverPos = gViewerWindow->getCurrentMouse();
	}
}


BOOL LLHoverView::isHoveringLand() const
{
	return !mHoverLandGlobal.isExactlyZero();
}


BOOL LLHoverView::isHoveringObject() const
{
	return !mLastHoverObject.isNull() && !mLastHoverObject->isDead();
}


LLViewerObject* LLHoverView::getLastHoverObject() const
{
	if (!mLastHoverObject.isNull() && !mLastHoverObject->isDead())
	{
		return mLastHoverObject;
	}
	else
	{
		return NULL;
	}
}

// EOF