/** 
 * @file llfloateranimpreview.cpp
 * @brief LLFloaterAnimPreview class implementation
 *
 * Copyright (c) 2004-$CurrentYear$, Linden Research, Inc.
 * $License$
 */

#include "llviewerprecompiledheaders.h"

#include "llfloateranimpreview.h"

#include "llbvhloader.h"
#include "lldatapacker.h"
#include "lldir.h"
#include "llvfile.h"
#include "llapr.h"

#include "llagent.h"
#include "llbbox.h"
#include "llbutton.h"
#include "llcheckboxctrl.h"
#include "llcombobox.h"
#include "lldrawable.h"
#include "lldrawpoolavatar.h"
#include "llface.h"
#include "llkeyframemotion.h"
#include "lllineeditor.h"
#include "llsliderctrl.h"
#include "llspinctrl.h"
#include "lltextbox.h"
#include "lltoolmgr.h"
#include "llui.h"
#include "llviewercamera.h"
#include "llviewerobjectlist.h"
#include "llviewerwindow.h"
#include "llviewermenu.h"
#include "llvoavatar.h"
#include "pipeline.h"
#include "viewer.h"
#include "llvieweruictrlfactory.h"

S32 LLFloaterAnimPreview::sUploadAmount = 10;

const S32 PREVIEW_BORDER_WIDTH = 2;
const S32 PREVIEW_RESIZE_HANDLE_SIZE = S32(RESIZE_HANDLE_WIDTH * OO_SQRT2) + PREVIEW_BORDER_WIDTH;
const S32 PREVIEW_HPAD = PREVIEW_RESIZE_HANDLE_SIZE;
const S32 PREF_BUTTON_HEIGHT = 16;
const S32 PREVIEW_TEXTURE_HEIGHT = 300;

const F32 PREVIEW_CAMERA_DISTANCE = 4.f;

const F32 MIN_CAMERA_ZOOM = 0.5f;
const F32 MAX_CAMERA_ZOOM = 10.f;

//-----------------------------------------------------------------------------
// LLFloaterAnimPreview()
//-----------------------------------------------------------------------------
LLFloaterAnimPreview::LLFloaterAnimPreview(const char* filename) : 
	LLFloaterNameDesc(filename)
{
	mLastSliderValue = 0.f;
	mLastMouseX = 0;
	mLastMouseY = 0;

	mIDList["Standing"] = ANIM_AGENT_STAND;
	mIDList["Walking"] = ANIM_AGENT_FEMALE_WALK;
	mIDList["Sitting"] = ANIM_AGENT_SIT_FEMALE;
	mIDList["Flying"] = ANIM_AGENT_HOVER;

	mIDList["[None]"] = LLUUID::null;
	mIDList["Aaaaah"] = ANIM_AGENT_EXPRESS_OPEN_MOUTH;
	mIDList["Afraid"] = ANIM_AGENT_EXPRESS_AFRAID;
	mIDList["Angry"] = ANIM_AGENT_EXPRESS_ANGER;
	mIDList["Big Smile"] = ANIM_AGENT_EXPRESS_TOOTHSMILE;
	mIDList["Bored"] = ANIM_AGENT_EXPRESS_BORED;
	mIDList["Cry"] = ANIM_AGENT_EXPRESS_CRY;
	mIDList["Disdain"] = ANIM_AGENT_EXPRESS_DISDAIN;
	mIDList["Embarrassed"] = ANIM_AGENT_EXPRESS_EMBARRASSED;
	mIDList["Frown"] = ANIM_AGENT_EXPRESS_FROWN;
	mIDList["Kiss"] = ANIM_AGENT_EXPRESS_KISS;
	mIDList["Laugh"] = ANIM_AGENT_EXPRESS_LAUGH;
	mIDList["Plllppt"] = ANIM_AGENT_EXPRESS_TONGUE_OUT;
	mIDList["Repulsed"] = ANIM_AGENT_EXPRESS_REPULSED;
	mIDList["Sad"] = ANIM_AGENT_EXPRESS_SAD;
	mIDList["Shrug"] = ANIM_AGENT_EXPRESS_SHRUG;
	mIDList["Smile"] = ANIM_AGENT_EXPRESS_SMILE;
	mIDList["Surprise"] = ANIM_AGENT_EXPRESS_SURPRISE;
	mIDList["Wink"] = ANIM_AGENT_EXPRESS_WINK;
	mIDList["Worry"] = ANIM_AGENT_EXPRESS_WORRY;
}

//-----------------------------------------------------------------------------
// postBuild()
//-----------------------------------------------------------------------------
BOOL LLFloaterAnimPreview::postBuild()
{
	LLRect r;
	LLKeyframeMotion* motionp = NULL;
	LLBVHLoader* loaderp = NULL;

	if (!LLFloaterNameDesc::postBuild())
	{
		return FALSE;
	}

	childSetCommitCallback("name_form", onCommitName, this);

	childSetLabelArg("ok_btn", "[AMOUNT]", llformat("%d",sUploadAmount));
	childSetAction("ok_btn", onBtnOK, this);
	setDefaultBtn();

	mPreviewRect.set(PREVIEW_HPAD, 
		PREVIEW_TEXTURE_HEIGHT,
		getRect().getWidth() - PREVIEW_HPAD, 
		PREVIEW_HPAD + PREF_BUTTON_HEIGHT + PREVIEW_HPAD);
	mPreviewImageRect.set(0.f, 1.f, 1.f, 0.f);

	S32 y = mPreviewRect.mTop + BTN_HEIGHT;
	S32 btn_left = PREVIEW_HPAD;

	r.set( btn_left, y, btn_left + 32, y - BTN_HEIGHT );
	mPlayButton = LLViewerUICtrlFactory::getButtonByName(this, "play_btn");
	if (!mPlayButton)
	{
		mPlayButton = new LLButton("play_btn", LLRect(0,0,0,0));
	}
	mPlayButton->setClickedCallback(onBtnPlay);
	mPlayButton->setCallbackUserData(this);

	mPlayButton->setImages("button_anim_play.tga",
						   "button_anim_play_selected.tga");
	mPlayButton->setDisabledImages("","");

	mPlayButton->setScaleImage(TRUE);
	mPlayButton->setFixedBorder(0, 0);

	mStopButton = LLViewerUICtrlFactory::getButtonByName(this, "stop_btn");
	if (!mStopButton)
	{
		mStopButton = new LLButton("stop_btn", LLRect(0,0,0,0));
	}
	mStopButton->setClickedCallback(onBtnStop);
	mStopButton->setCallbackUserData(this);

	mStopButton->setImages("button_anim_stop.tga",
						   "button_anim_stop_selected.tga");
	mStopButton->setDisabledImages("","");

	mStopButton->setScaleImage(TRUE);
	mStopButton->setFixedBorder(0, 0);

	r.set(r.mRight + PREVIEW_HPAD, y, getRect().getWidth() - PREVIEW_HPAD, y - BTN_HEIGHT);
	childSetCommitCallback("playback_slider", onSliderMove, this);

	childHide("bad_animation_text");

	childSetCommitCallback("preview_base_anim", onCommitBaseAnim, this);
	childSetValue("preview_base_anim", "Standing");

	childSetCommitCallback("priority", onCommitPriority, this);
	childSetCommitCallback("loop_check", onCommitLoop, this);
	childSetCommitCallback("loop_in_point", onCommitLoopIn, this);
	childSetValidate("loop_in_point", validateLoopIn);
	childSetCommitCallback("loop_out_point", onCommitLoopOut, this);
	childSetValidate("loop_out_point", validateLoopOut);

	childSetCommitCallback("hand_pose_combo", onCommitHandPose, this);
	
	childSetCommitCallback("emote_combo", onCommitEmote, this);
	childSetValue("emote_combo", "[None]");

	childSetCommitCallback("ease_in_time", onCommitEaseIn, this);
	childSetValidate("ease_in_time", validateEaseIn);
	childSetCommitCallback("ease_out_time", onCommitEaseOut, this);
	childSetValidate("ease_out_time", validateEaseOut);

	if (!stricmp(strrchr(mFilename.c_str(), '.'), ".bvh"))
	{
		// loading a bvh file

		// now load bvh file
		S32 file_size;
		apr_file_t* fp = ll_apr_file_open(mFilenameAndPath, LL_APR_RB, &file_size);

		if (!fp)
		{
			llwarns << "Can't open BVH file:" << mFilename << llendl;	
		}
		else
		{
			char*	file_buffer;

			file_buffer = new char[file_size + 1];

			if (file_size == ll_apr_file_read(fp, file_buffer, file_size))
			{
				file_buffer[file_size] = '\0';
				llinfos << "Loading BVH file " << mFilename << llendl;
				loaderp = new LLBVHLoader(file_buffer);
			}

			apr_file_close(fp);
			delete file_buffer;
		}
	}

	if (loaderp && loaderp->isInitialized() && loaderp->getDuration() <= MAX_ANIM_DURATION)
	{
		// generate unique id for this motion
		mTransactionID.generate();
		mMotionID = mTransactionID.makeAssetID(gAgent.getSecureSessionID());

		mAnimPreview = new LLPreviewAnimation(256, 256);

		// motion will be returned, but it will be in a load-pending state, as this is a new motion
		// this motion will not request an asset transfer until next update, so we have a chance to 
		// load the keyframe data locally
		motionp = (LLKeyframeMotion*)mAnimPreview->getDummyAvatar()->createMotion(mMotionID);

		// create data buffer for keyframe initialization
		S32 buffer_size = loaderp->getOutputSize();
		U8* buffer = new U8[buffer_size];

		LLDataPackerBinaryBuffer dp(buffer, buffer_size);

		// pass animation data through memory buffer
		loaderp->serialize(dp);
		dp.reset();
		BOOL success = motionp->deserialize(dp);

		delete []buffer;

		if (motionp && success)
		{
			const LLBBoxLocal &pelvis_bbox = motionp->getPelvisBBox();

			LLVector3 temp = pelvis_bbox.getCenter();
			// only consider XY?
			//temp.mV[VZ] = 0.f;
			F32 pelvis_offset = temp.magVec();

			temp = pelvis_bbox.getExtent();
			//temp.mV[VZ] = 0.f;
			F32 pelvis_max_displacement = pelvis_offset + (temp.magVec() * 0.5f) + 1.f;
			
			F32 camera_zoom = gCamera->getDefaultFOV() / (2.f * atan(pelvis_max_displacement / PREVIEW_CAMERA_DISTANCE));
		
			mAnimPreview->setZoom(camera_zoom);

			motionp->setName(childGetValue("name_form").asString());
			mAnimPreview->getDummyAvatar()->startMotion(mMotionID);
			childSetMinValue("playback_slider", 0.0);
			childSetMaxValue("playback_slider", 1.0);

			childSetValue("loop_check", LLSD(motionp->getLoop()));
			childSetValue("loop_in_point", LLSD(motionp->getLoopIn() / motionp->getDuration() * 100.f));
			childSetValue("loop_out_point", LLSD(motionp->getLoopOut() / motionp->getDuration() * 100.f));
			childSetValue("priority", LLSD((F32)motionp->getPriority()));
			childSetValue("hand_pose_combo", LLHandMotion::getHandPoseName(motionp->getHandPose()));
			childSetValue("ease_in_time", LLSD(motionp->getEaseInDuration()));
			childSetValue("ease_out_time", LLSD(motionp->getEaseOutDuration()));
			mEnabled = TRUE;
			char seconds_string[128];
			sprintf(seconds_string, " - %.2f seconds", motionp->getDuration());

			setTitle(mFilename + LLString(seconds_string));
		}
		else
		{
			delete mAnimPreview;
			mAnimPreview = NULL;
			mMotionID.setNull();
			// XUI:translate
			childSetValue("bad_animation_text", LLSD("Failed to initialize motion."));
			mEnabled = FALSE;
		}
	}
	else
	{
		if ( loaderp )
		{
			if (loaderp->getDuration() > MAX_ANIM_DURATION)
			{
				char output_str[256];

				sprintf(output_str, "Animation file is %.1f seconds in length.\n\nMaximum animation length is %.1f seconds.\n",
					loaderp->getDuration(), MAX_ANIM_DURATION);
				childSetValue("bad_animation_text", LLSD(output_str));
			}
			else
			{
				char* status = loaderp->getStatus();
				LLString error_string("Unable to read animation file.\n\n");
				error_string += LLString(status);
				childSetValue("bad_animation_text", LLSD(error_string));
			}
		}

		mEnabled = FALSE;
		mMotionID.setNull();
		mAnimPreview = NULL;
	}

	refresh();

	delete loaderp;

	return TRUE;
}

//-----------------------------------------------------------------------------
// LLFloaterAnimPreview()
//-----------------------------------------------------------------------------
LLFloaterAnimPreview::~LLFloaterAnimPreview()
{
	delete mAnimPreview;
	mAnimPreview = NULL;

	mEnabled = FALSE;
}

//-----------------------------------------------------------------------------
// draw()
//-----------------------------------------------------------------------------
void LLFloaterAnimPreview::draw()
{
	LLFloater::draw();
	LLRect r = getRect();

	refresh();

	if (mMotionID.notNull() && mAnimPreview)
	{
		glColor3f(1.f, 1.f, 1.f);
		mAnimPreview->bindTexture();

		glBegin( GL_QUADS );
		{
			glTexCoord2f(0.f, 1.f);
			glVertex2i(PREVIEW_HPAD, PREVIEW_TEXTURE_HEIGHT);
			glTexCoord2f(0.f, 0.f);
			glVertex2i(PREVIEW_HPAD, PREVIEW_HPAD + PREF_BUTTON_HEIGHT + PREVIEW_HPAD);
			glTexCoord2f(1.f, 0.f);
			glVertex2i(r.getWidth() - PREVIEW_HPAD, PREVIEW_HPAD + PREF_BUTTON_HEIGHT + PREVIEW_HPAD);
			glTexCoord2f(1.f, 1.f);
			glVertex2i(r.getWidth() - PREVIEW_HPAD, PREVIEW_TEXTURE_HEIGHT);
		}
		glEnd();

		mAnimPreview->unbindTexture();

		LLVOAvatar* avatarp = mAnimPreview->getDummyAvatar();
		if (!avatarp->areAnimationsPaused())
		{
			mAnimPreview->requestUpdate();
		}
	}
}

//-----------------------------------------------------------------------------
// resetMotion()
//-----------------------------------------------------------------------------
void LLFloaterAnimPreview::resetMotion()
{
	LLVOAvatar* avatarp = mAnimPreview->getDummyAvatar();
	BOOL paused = avatarp->areAnimationsPaused();

	mPauseRequest = NULL;
	
	LLUUID anim_id = mIDList[childGetValue("preview_base_anim").asString()];
	avatarp->stopMotion(anim_id, TRUE);
	avatarp->stopMotion(mMotionID, TRUE);
	avatarp->startMotion(anim_id, 5.f);
	avatarp->startMotion(mMotionID);
	childSetValue("playback_slider", 0.0);
	mLastSliderValue = 0.0f;

	if (paused)
	{
		mPauseRequest = avatarp->requestPause();
	}
}

//-----------------------------------------------------------------------------
// handleMouseDown()
//-----------------------------------------------------------------------------
BOOL LLFloaterAnimPreview::handleMouseDown(S32 x, S32 y, MASK mask)
{
	if (mPreviewRect.pointInRect(x, y))
	{
		bringToFront( x, y );
		gViewerWindow->setMouseCapture(this, onMouseCaptureLost);
		gViewerWindow->hideCursor();
		mLastMouseX = x;
		mLastMouseY = y;
		return TRUE;
	}

	return LLFloater::handleMouseDown(x, y, mask);
}

//-----------------------------------------------------------------------------
// handleMouseUp()
//-----------------------------------------------------------------------------
BOOL LLFloaterAnimPreview::handleMouseUp(S32 x, S32 y, MASK mask)
{
	gViewerWindow->setMouseCapture(FALSE, NULL);
	gViewerWindow->showCursor();
	return LLFloater::handleMouseUp(x, y, mask);
}

//-----------------------------------------------------------------------------
// handleHover()
//-----------------------------------------------------------------------------
BOOL LLFloaterAnimPreview::handleHover(S32 x, S32 y, MASK mask)
{
	MASK local_mask = mask & ~MASK_ALT;

	if (mAnimPreview && gViewerWindow->hasMouseCapture(this))
	{
		if (local_mask == MASK_PAN)
		{
			// pan here
			mAnimPreview->pan((F32)(x - mLastMouseX) * -0.005f, (F32)(y - mLastMouseY) * -0.005f);
		}
		else if (local_mask == MASK_ORBIT)
		{
			F32 yaw_radians = (F32)(x - mLastMouseX) * -0.01f;
			F32 pitch_radians = (F32)(y - mLastMouseY) * 0.02f;
			
			mAnimPreview->rotate(yaw_radians, pitch_radians);
		}
		else 
		{
			F32 yaw_radians = (F32)(x - mLastMouseX) * -0.01f;
			F32 zoom_amt = (F32)(y - mLastMouseY) * 0.02f;
			
			mAnimPreview->rotate(yaw_radians, 0.f);
			mAnimPreview->zoom(zoom_amt);
		}

		mAnimPreview->requestUpdate();

		LLUI::setCursorPositionLocal(this, mLastMouseX, mLastMouseY);
	}

	if (!mPreviewRect.pointInRect(x, y) || !mAnimPreview)
	{
		return LLFloater::handleHover(x, y, mask);
	}
	else if (local_mask == MASK_ORBIT)
	{
		gViewerWindow->setCursor(UI_CURSOR_TOOLCAMERA);
	}
	else if (local_mask == MASK_PAN)
	{
		gViewerWindow->setCursor(UI_CURSOR_TOOLPAN);
	}
	else
	{
		gViewerWindow->setCursor(UI_CURSOR_TOOLZOOMIN);
	}

	return TRUE;
}

//-----------------------------------------------------------------------------
// handleScrollWheel()
//-----------------------------------------------------------------------------
BOOL LLFloaterAnimPreview::handleScrollWheel(S32 x, S32 y, S32 clicks)
{
	mAnimPreview->zoom((F32)clicks * -0.2f);
	mAnimPreview->requestUpdate();

	return TRUE;
}

//-----------------------------------------------------------------------------
// onMouseCaptureLost()
//-----------------------------------------------------------------------------
void LLFloaterAnimPreview::onMouseCaptureLost(LLMouseHandler* handler)
{
	gViewerWindow->showCursor();
}

//-----------------------------------------------------------------------------
// onBtnPlay()
//-----------------------------------------------------------------------------
void LLFloaterAnimPreview::onBtnPlay(void* user_data)
{
	LLFloaterAnimPreview* previewp = (LLFloaterAnimPreview*)user_data;
	if (!previewp->mEnabled) return;

	if (previewp->mMotionID.notNull() && previewp->mAnimPreview)
	{
		LLVOAvatar* avatarp = previewp->mAnimPreview->getDummyAvatar();

		if(!avatarp->isMotionActive(previewp->mMotionID))
		{
			previewp->resetMotion();
			previewp->mPauseRequest = NULL;
		}
		else
		{
			if (avatarp->areAnimationsPaused())
			{
				previewp->mPauseRequest = NULL;
			}
			else
			{
				previewp->mPauseRequest = avatarp->requestPause();
			}
		}
	}
}

//-----------------------------------------------------------------------------
// onBtnStop()
//-----------------------------------------------------------------------------
void LLFloaterAnimPreview::onBtnStop(void* user_data)
{
	LLFloaterAnimPreview* previewp = (LLFloaterAnimPreview*)user_data;
	if (!previewp->mEnabled) return;

	if (previewp->mMotionID.notNull() && previewp->mAnimPreview)
	{
		LLVOAvatar* avatarp = previewp->mAnimPreview->getDummyAvatar();

		// is the motion looping and have we passed the loop in point?
		if (previewp->childGetValue("loop_check").asBoolean() && 
			(F32)previewp->childGetValue("loop_in_point").asReal() <= (F32)previewp->childGetValue("playback_slider").asReal() * 100.f)
		{
			avatarp->stopMotion(previewp->mMotionID, FALSE);
		}
		else
		{
			avatarp->stopMotion(previewp->mMotionID, FALSE);
		}
	}
}

//-----------------------------------------------------------------------------
// onSliderMove()
//-----------------------------------------------------------------------------
void LLFloaterAnimPreview::onSliderMove(LLUICtrl* ctrl, void*user_data)
{
	LLFloaterAnimPreview* previewp = (LLFloaterAnimPreview*)user_data;
	if (!previewp->mEnabled) return;

	if (previewp->mAnimPreview)
	{
		LLVOAvatar* avatarp = previewp->mAnimPreview->getDummyAvatar();
		LLMotion* motionp = avatarp->findMotion(previewp->mMotionID);
		LLMotion* base_motionp = 
			avatarp->findMotion(previewp->mIDList[previewp->childGetValue("preview_base_anim").asString()]);

		if (motionp && base_motionp)
		{
			if (!avatarp->isMotionActive(previewp->mMotionID))
			{ 
				previewp->resetMotion();
			}

			previewp->mPauseRequest = avatarp->requestPause();
			F32 original_activation_time = motionp->mActivationTimestamp;
			motionp->mActivationTimestamp -= ((F32)previewp->childGetValue("playback_slider").asReal() - previewp->mLastSliderValue) * 
				motionp->getDuration();
			base_motionp->mActivationTimestamp -= ((F32)previewp->childGetValue("playback_slider").asReal() - previewp->mLastSliderValue) * 
				base_motionp->getDuration();

			if (motionp->mSendStopTimestamp != F32_MIN)
			{
				motionp->mSendStopTimestamp = motionp->mSendStopTimestamp - original_activation_time + motionp->mActivationTimestamp;
			}

			if (motionp->mStopTimestamp != F32_MIN)
			{
				motionp->mStopTimestamp = motionp->mStopTimestamp - original_activation_time + motionp->mActivationTimestamp;
			}
			previewp->refresh();
		}
	}

	previewp->mLastSliderValue = (F32)previewp->childGetValue("playback_slider").asReal();
}

//-----------------------------------------------------------------------------
// onCommitBaseAnim()
//-----------------------------------------------------------------------------
void LLFloaterAnimPreview::onCommitBaseAnim(LLUICtrl* ctrl, void* data)
{
	LLFloaterAnimPreview* previewp = (LLFloaterAnimPreview*)data;

	if (!previewp->mEnabled) return;

	if (previewp->mAnimPreview)
	{
		LLVOAvatar* avatarp = previewp->mAnimPreview->getDummyAvatar();

		BOOL paused = avatarp->areAnimationsPaused();

		// stop all other possible base motions
		avatarp->stopMotion(ANIM_AGENT_STAND, TRUE);
		avatarp->stopMotion(ANIM_AGENT_WALK, TRUE);
		avatarp->stopMotion(ANIM_AGENT_SIT, TRUE);
		avatarp->stopMotion(ANIM_AGENT_HOVER, TRUE);

		previewp->resetMotion();

		if (!paused)
		{
			previewp->mPauseRequest = NULL;
		}
	}
}

//-----------------------------------------------------------------------------
// onCommitLoop()
//-----------------------------------------------------------------------------
void LLFloaterAnimPreview::onCommitLoop(LLUICtrl* ctrl, void* data)
{
	LLFloaterAnimPreview* previewp = (LLFloaterAnimPreview*)data;

	if (!previewp->mEnabled) return;
	LLVOAvatar* avatarp = previewp->mAnimPreview->getDummyAvatar();
	LLKeyframeMotion* motionp = (LLKeyframeMotion*)avatarp->findMotion(previewp->mMotionID);

	if (motionp)
	{
		motionp->setLoop(previewp->childGetValue("loop_check").asBoolean());
		motionp->setLoopIn((F32)previewp->childGetValue("loop_in_point").asReal() * 0.01f * motionp->getDuration());
		motionp->setLoopOut((F32)previewp->childGetValue("loop_out_point").asReal() * 0.01f * motionp->getDuration());
	}
}

//-----------------------------------------------------------------------------
// onCommitLoopIn()
//-----------------------------------------------------------------------------
void LLFloaterAnimPreview::onCommitLoopIn(LLUICtrl* ctrl, void* data)
{
	LLFloaterAnimPreview* previewp = (LLFloaterAnimPreview*)data;
	if (!previewp->mEnabled) return;

	LLVOAvatar* avatarp = previewp->mAnimPreview->getDummyAvatar();
	LLKeyframeMotion* motionp = (LLKeyframeMotion*)avatarp->findMotion(previewp->mMotionID);

	if (motionp)
	{
		motionp->setLoopIn((F32)previewp->childGetValue("loop_in_point").asReal() / 100.f);
		previewp->resetMotion();
		previewp->childSetValue("loop_check", LLSD(TRUE));
		onCommitLoop(ctrl, data);
	}
}

//-----------------------------------------------------------------------------
// onCommitLoopOut()
//-----------------------------------------------------------------------------
void LLFloaterAnimPreview::onCommitLoopOut(LLUICtrl* ctrl, void* data)
{
	LLFloaterAnimPreview* previewp = (LLFloaterAnimPreview*)data;
	if (!previewp->mEnabled) return;

	LLVOAvatar* avatarp = previewp->mAnimPreview->getDummyAvatar();
	LLKeyframeMotion* motionp = (LLKeyframeMotion*)avatarp->findMotion(previewp->mMotionID);

	if (motionp)
	{
		motionp->setLoopOut((F32)previewp->childGetValue("loop_out_point").asReal() * 0.01f * motionp->getDuration());
		previewp->resetMotion();
		previewp->childSetValue("loop_check", LLSD(TRUE));
		onCommitLoop(ctrl, data);
	}
}

//-----------------------------------------------------------------------------
// onCommitName()
//-----------------------------------------------------------------------------
void LLFloaterAnimPreview::onCommitName(LLUICtrl* ctrl, void* data)
{
	LLFloaterAnimPreview* previewp = (LLFloaterAnimPreview*)data;
	if (!previewp->mEnabled) return;

	LLVOAvatar* avatarp = previewp->mAnimPreview->getDummyAvatar();
	LLKeyframeMotion* motionp = (LLKeyframeMotion*)avatarp->findMotion(previewp->mMotionID);

	if (motionp)
	{
		motionp->setName(previewp->childGetValue("name_form").asString());
	}

	LLFloaterNameDesc::doCommit(ctrl, data);
}

//-----------------------------------------------------------------------------
// onCommitHandPose()
//-----------------------------------------------------------------------------
void LLFloaterAnimPreview::onCommitHandPose(LLUICtrl* ctrl, void* data)
{
	LLFloaterAnimPreview* previewp = (LLFloaterAnimPreview*)data;
	if (!previewp->mEnabled) return;

	LLVOAvatar* avatarp = previewp->mAnimPreview->getDummyAvatar();
	LLKeyframeMotion* motionp = (LLKeyframeMotion*)avatarp->findMotion(previewp->mMotionID);

	motionp->setHandPose(LLHandMotion::getHandPose(previewp->childGetValue("hand_pose_combo").asString()));
	previewp->resetMotion();
}

//-----------------------------------------------------------------------------
// onCommitEmote()
//-----------------------------------------------------------------------------
void LLFloaterAnimPreview::onCommitEmote(LLUICtrl* ctrl, void* data)
{
	LLFloaterAnimPreview* previewp = (LLFloaterAnimPreview*)data;
	if (!previewp->mEnabled) return;

	LLVOAvatar* avatarp = previewp->mAnimPreview->getDummyAvatar();
	LLKeyframeMotion* motionp = (LLKeyframeMotion*)avatarp->findMotion(previewp->mMotionID);

	motionp->setEmote(previewp->mIDList[previewp->childGetValue("emote_combo").asString()]);
	previewp->resetMotion();
}

//-----------------------------------------------------------------------------
// onCommitPriority()
//-----------------------------------------------------------------------------
void LLFloaterAnimPreview::onCommitPriority(LLUICtrl* ctrl, void* data)
{
	LLFloaterAnimPreview* previewp = (LLFloaterAnimPreview*)data;
	if (!previewp->mEnabled) return;

	LLVOAvatar* avatarp = previewp->mAnimPreview->getDummyAvatar();
	LLKeyframeMotion* motionp = (LLKeyframeMotion*)avatarp->findMotion(previewp->mMotionID);

	motionp->setPriority(llfloor((F32)previewp->childGetValue("priority").asReal()));
}

//-----------------------------------------------------------------------------
// onCommitEaseIn()
//-----------------------------------------------------------------------------
void LLFloaterAnimPreview::onCommitEaseIn(LLUICtrl* ctrl, void* data)
{
	LLFloaterAnimPreview* previewp = (LLFloaterAnimPreview*)data;
	if (!previewp->mEnabled) return;

	LLVOAvatar* avatarp = previewp->mAnimPreview->getDummyAvatar();
	LLKeyframeMotion* motionp = (LLKeyframeMotion*)avatarp->findMotion(previewp->mMotionID);

	motionp->setEaseIn((F32)previewp->childGetValue("ease_in_time").asReal());
	previewp->resetMotion();
}

//-----------------------------------------------------------------------------
// onCommitEaseOut()
//-----------------------------------------------------------------------------
void LLFloaterAnimPreview::onCommitEaseOut(LLUICtrl* ctrl, void* data)
{
	LLFloaterAnimPreview* previewp = (LLFloaterAnimPreview*)data;
	if (!previewp->mEnabled) return;

	LLVOAvatar* avatarp = previewp->mAnimPreview->getDummyAvatar();
	LLKeyframeMotion* motionp = (LLKeyframeMotion*)avatarp->findMotion(previewp->mMotionID);

	motionp->setEaseOut((F32)previewp->childGetValue("ease_out_time").asReal());
	previewp->resetMotion();
}

//-----------------------------------------------------------------------------
// validateEaseIn()
//-----------------------------------------------------------------------------
BOOL LLFloaterAnimPreview::validateEaseIn(LLUICtrl* spin, void* data)
{
	LLFloaterAnimPreview* previewp = (LLFloaterAnimPreview*)data;
	
	if (!previewp->mEnabled) return FALSE;

	LLVOAvatar* avatarp = previewp->mAnimPreview->getDummyAvatar();
	LLKeyframeMotion* motionp = (LLKeyframeMotion*)avatarp->findMotion(previewp->mMotionID);
	
	if (!motionp->getLoop())
	{
		F32 new_ease_in = llclamp((F32)previewp->childGetValue("ease_in_time").asReal(), 0.f, motionp->getDuration() - motionp->getEaseOutDuration());
		previewp->childSetValue("ease_in_time", LLSD(new_ease_in));
	}
	
	return TRUE;
}

//-----------------------------------------------------------------------------
// validateEaseOut()
//-----------------------------------------------------------------------------
BOOL LLFloaterAnimPreview::validateEaseOut(LLUICtrl* spin, void* data)
{
	LLFloaterAnimPreview* previewp = (LLFloaterAnimPreview*)data;

	if (!previewp->mEnabled) return FALSE;

	LLVOAvatar* avatarp = previewp->mAnimPreview->getDummyAvatar();
	LLKeyframeMotion* motionp = (LLKeyframeMotion*)avatarp->findMotion(previewp->mMotionID);
	
	if (!motionp->getLoop())
	{
		F32 new_ease_out = llclamp((F32)previewp->childGetValue("ease_out_time").asReal(), 0.f, motionp->getDuration() - motionp->getEaseInDuration());
		previewp->childSetValue("ease_out_time", LLSD(new_ease_out));
	}

	return TRUE;
}

//-----------------------------------------------------------------------------
// validateLoopIn()
//-----------------------------------------------------------------------------
BOOL LLFloaterAnimPreview::validateLoopIn(LLUICtrl* ctrl, void* data)
{
	LLFloaterAnimPreview* previewp = (LLFloaterAnimPreview*)data;

	if (!previewp->mEnabled) return FALSE;

	F32 loop_in_value = (F32)previewp->childGetValue("loop_in_point").asReal();
	F32 loop_out_value = (F32)previewp->childGetValue("loop_out_point").asReal();

	if (loop_in_value < 0.f)
	{
		loop_in_value = 0.f;
	}
	else if (loop_in_value > 100.f)
	{
		loop_in_value = 100.f;
	}
	else if (loop_in_value > loop_out_value)
	{
		loop_in_value = loop_out_value;
	}

	previewp->childSetValue("loop_in_point", LLSD(loop_in_value));
	return TRUE;
}

//-----------------------------------------------------------------------------
// validateLoopOut()
//-----------------------------------------------------------------------------
BOOL LLFloaterAnimPreview::validateLoopOut(LLUICtrl* spin, void* data)
{
	LLFloaterAnimPreview* previewp = (LLFloaterAnimPreview*)data;

	if (!previewp->mEnabled) return FALSE;

	F32 loop_out_value = (F32)previewp->childGetValue("loop_out_point").asReal();
	F32 loop_in_value = (F32)previewp->childGetValue("loop_in_point").asReal();

	if (loop_out_value < 0.f)
	{
		loop_out_value = 0.f;
	}
	else if (loop_out_value > 100.f)
	{
		loop_out_value = 100.f;
	}
	else if (loop_out_value < loop_in_value)
	{
		loop_out_value = loop_in_value;
	}

	previewp->childSetValue("loop_out_point", LLSD(loop_out_value));
	return TRUE;
}


//-----------------------------------------------------------------------------
// refresh()
//-----------------------------------------------------------------------------
void LLFloaterAnimPreview::refresh()
{
	if (!mAnimPreview)
	{
		childShow("bad_animation_text");
		mPlayButton->setEnabled(FALSE);
		mStopButton->setEnabled(FALSE);
		childDisable("ok_btn");
	}
	else
	{
		childHide("bad_animation_text");
		mPlayButton->setEnabled(TRUE);
		LLVOAvatar* avatarp = mAnimPreview->getDummyAvatar();
		if (avatarp->isMotionActive(mMotionID))
		{
			mStopButton->setEnabled(TRUE);
			LLKeyframeMotion* motionp = (LLKeyframeMotion*)avatarp->findMotion(mMotionID);
			if (avatarp->areAnimationsPaused())
			{

				mPlayButton->setImages("button_anim_play.tga",
									   "button_anim_play_selected.tga");

			}
			else
			{
				if (motionp)
				{
					F32 fraction_complete;
					fraction_complete = motionp->getLastUpdateTime() / motionp->getDuration();

					childSetValue("playback_slider", fraction_complete);
					mLastSliderValue = fraction_complete;
				}
				mPlayButton->setImages("button_anim_pause.tga",
									   "button_anim_pause_selected.tga");

			}
		}
		else
		{
			mPauseRequest = avatarp->requestPause();
			mPlayButton->setImages("button_anim_play.tga",
								   "button_anim_play_selected.tga");

			mStopButton->setEnabled(FALSE);
		}
		childEnable("ok_btn");
		mAnimPreview->requestUpdate();
	}
}

//-----------------------------------------------------------------------------
// onBtnOK()
//-----------------------------------------------------------------------------
void LLFloaterAnimPreview::onBtnOK(void* userdata)
{
	LLFloaterAnimPreview* floaterp = (LLFloaterAnimPreview*)userdata;
	if (!floaterp->mEnabled) return;

	if (floaterp->mAnimPreview)
	{
		LLKeyframeMotion* motionp = (LLKeyframeMotion*)floaterp->mAnimPreview->getDummyAvatar()->findMotion(floaterp->mMotionID);

		S32 file_size = motionp->getFileSize();
		U8* buffer = new U8[file_size];

		LLDataPackerBinaryBuffer dp(buffer, file_size);
		if (motionp->serialize(dp))
		{
			LLVFile file(gVFS, motionp->getID(), LLAssetType::AT_ANIMATION, LLVFile::APPEND);

			S32 size = dp.getCurrentSize();
			file.setMaxSize(size);
			if (file.write((U8*)buffer, size))
			{
				std::string name = floaterp->childGetValue("name_form").asString();
				std::string desc = floaterp->childGetValue("description_form").asString();
				upload_new_resource(floaterp->mTransactionID, // tid
									LLAssetType::AT_ANIMATION,
									name,
									desc,
									0,
									LLAssetType::AT_NONE,
									LLInventoryType::IT_ANIMATION,
									PERM_NONE,
									name);
			}
			else
			{
				llwarns << "Failure writing animation data." << llendl;
				gViewerWindow->alertXml("WriteAnimationFail");
			}
		}

		delete [] buffer;
		// clear out cache for motion data
		floaterp->mAnimPreview->getDummyAvatar()->removeMotion(floaterp->mMotionID);
		LLKeyframeDataCache::removeKeyframeData(floaterp->mMotionID);
	}

	floaterp->onClose(false);
}

//-----------------------------------------------------------------------------
// LLPreviewAnimation
//-----------------------------------------------------------------------------
LLPreviewAnimation::LLPreviewAnimation(S32 width, S32 height) : LLDynamicTexture(width, height, 3, ORDER_MIDDLE, FALSE)
{
	mNeedsUpdate = TRUE;
	mCameraDistance = PREVIEW_CAMERA_DISTANCE;
	mCameraYaw = 0.f;
	mCameraPitch = 0.f;
	mCameraZoom = 1.f;

	mDummyAvatar = (LLVOAvatar*)gObjectList.createObjectViewer(LL_PCODE_LEGACY_AVATAR, gAgent.getRegion());
	mDummyAvatar->createDrawable(&gPipeline);
	mDummyAvatar->mIsDummy = TRUE;
	mDummyAvatar->mSpecialRenderMode = 1;
	mDummyAvatar->setPositionAgent(LLVector3::zero);
	mDummyAvatar->slamPosition();
	mDummyAvatar->updateJointLODs();
	mDummyAvatar->updateGeometry(mDummyAvatar->mDrawable);
	mDummyAvatar->startMotion(ANIM_AGENT_STAND, 5.f);
	mDummyAvatar->mSkirtLOD.setVisible(FALSE, TRUE);
	gPipeline.markVisible(mDummyAvatar->mDrawable);

	// stop extraneous animations
	mDummyAvatar->stopMotion( ANIM_AGENT_HEAD_ROT, TRUE );
	mDummyAvatar->stopMotion( ANIM_AGENT_EYE, TRUE );
	mDummyAvatar->stopMotion( ANIM_AGENT_BODY_NOISE, TRUE );
	mDummyAvatar->stopMotion( ANIM_AGENT_BREATHE_ROT, TRUE );
}

//-----------------------------------------------------------------------------
// LLPreviewAnimation()
//-----------------------------------------------------------------------------
LLPreviewAnimation::~LLPreviewAnimation()
{
	mDummyAvatar->markDead();
}

//-----------------------------------------------------------------------------
// update()
//-----------------------------------------------------------------------------
BOOL	LLPreviewAnimation::render()
{
	mNeedsUpdate = FALSE;
	LLVOAvatar* avatarp = mDummyAvatar;
	
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity();
	glOrtho(0.0f, mWidth, 0.0f, mHeight, -1.0f, 1.0f);

	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
	glLoadIdentity();

	LLGLSUIDefault def;
	LLGLSNoTexture gls_no_texture;
	glColor4f(0.15f, 0.2f, 0.3f, 1.f);

	gl_rect_2d_simple( mWidth, mHeight );

	glMatrixMode(GL_PROJECTION);
	glPopMatrix();

	glMatrixMode(GL_MODELVIEW);
	glPopMatrix();

	LLVector3 target_pos = avatarp->mRoot.getWorldPosition();

	LLQuaternion camera_rot = LLQuaternion(mCameraPitch, LLVector3::y_axis) * 
		LLQuaternion(mCameraYaw, LLVector3::z_axis);

	LLQuaternion av_rot = avatarp->mRoot.getWorldRotation() * camera_rot;
	gCamera->setOriginAndLookAt(
		target_pos + ((LLVector3(mCameraDistance, 0.f, 0.f) + mCameraOffset) * av_rot),		// camera
		LLVector3::z_axis,																	// up
		target_pos + (mCameraOffset  * av_rot) );											// point of interest

	gCamera->setView(gCamera->getDefaultFOV() / mCameraZoom);
	gCamera->setPerspective(FALSE, mOrigin.mX, mOrigin.mY, mWidth, mHeight, FALSE);

	mCameraRelPos = gCamera->getOrigin() - avatarp->mHeadp->getWorldPosition();

	//avatarp->setAnimationData("LookAtPoint", (void *)&mCameraRelPos);

	//RN: timestep must be zero, because paused animations will never initialize
	// av skeleton otherwise
	avatarp->setTimeStep(0.f);
	if (avatarp->areAnimationsPaused())
	{
		avatarp->updateMotion(TRUE);
	}
	else
	{
		avatarp->updateMotion();
	}
	
	avatarp->mRoot.updateWorldMatrixChildren();

	stop_glerror();

	LLGLDepthTest gls_depth(GL_TRUE);

	if (avatarp->mDrawable.notNull())
	{
		LLDrawPoolAvatar *avatarPoolp = (LLDrawPoolAvatar *)avatarp->mDrawable->getFace(0)->getPool();
		gPipeline.unbindAGP();
		avatarPoolp->syncAGP();
		if (avatarPoolp->canUseAGP() && gPipeline.usingAGP())
		{
			gPipeline.bindAGP();
		}
		avatarPoolp->renderAvatars(avatarp, TRUE);  // renders only one avatar (no shaders)
	}
	
	return TRUE;
}

//-----------------------------------------------------------------------------
// requestUpdate()
//-----------------------------------------------------------------------------
void LLPreviewAnimation::requestUpdate()
{ 
	mNeedsUpdate = TRUE; 
}

//-----------------------------------------------------------------------------
// rotate()
//-----------------------------------------------------------------------------
void LLPreviewAnimation::rotate(F32 yaw_radians, F32 pitch_radians)
{
	mCameraYaw = mCameraYaw + yaw_radians;

	mCameraPitch = llclamp(mCameraPitch + pitch_radians, F_PI_BY_TWO * -0.8f, F_PI_BY_TWO * 0.8f);
}

//-----------------------------------------------------------------------------
// zoom()
//-----------------------------------------------------------------------------
void LLPreviewAnimation::zoom(F32 zoom_delta)
{
	setZoom(mCameraZoom + zoom_delta);
}

//-----------------------------------------------------------------------------
// setZoom()
//-----------------------------------------------------------------------------
void LLPreviewAnimation::setZoom(F32 zoom_amt)
{
	mCameraZoom	= llclamp(zoom_amt, MIN_CAMERA_ZOOM, MAX_CAMERA_ZOOM);
}

//-----------------------------------------------------------------------------
// pan()
//-----------------------------------------------------------------------------
void LLPreviewAnimation::pan(F32 right, F32 up)
{
	mCameraOffset.mV[VY] = llclamp(mCameraOffset.mV[VY] + right * mCameraDistance / mCameraZoom, -1.f, 1.f);
	mCameraOffset.mV[VZ] = llclamp(mCameraOffset.mV[VZ] + up * mCameraDistance / mCameraZoom, -1.f, 1.f);
}

