//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------

#ifndef TORQUE_TGB_ONLY

#include "guiDecalEditorCtrl.h"
#include "platform/platform.h"

#include "console/consoleTypes.h"
#include "scene/sceneManager.h"
#include "collision/collision.h"
#include "math/util/frustum.h"
#include "gfx/gfxPrimitiveBuffer.h"
#include "gfx/gfxTextureHandle.h"
#include "gfx/gfxTransformSaver.h"
#include "gfx/primBuilder.h"
#include "gfx/gfxDrawUtil.h"
#include "gui/core/guiCanvas.h"
#include "gui/buttons/guiButtonCtrl.h"
#include "gui/worldEditor/gizmo.h"
#include "T3D/decal/decalManager.h"
#include "T3D/decal/decalInstance.h"
#include "gui/worldEditor/undoActions.h"

IMPLEMENT_CONOBJECT(GuiDecalEditorCtrl);

ConsoleDocClass( GuiDecalEditorCtrl,
   "@brief The base class for the Decal Editor tool\n\n"
   "Editor use only.\n\n"
   "@internal"
);

bool GuiDecalEditorCtrl::smRenderDecalPixelSize = false;

GuiDecalEditorCtrl::GuiDecalEditorCtrl()
{   
   mSELDecal = NULL;
   mHLDecal = NULL;
   mCurrentDecalData = NULL;
	mMode = "AddDecalMode";
   mPerformedDragCopy = false;
}

GuiDecalEditorCtrl::~GuiDecalEditorCtrl()
{
   // nothing to do
}

bool GuiDecalEditorCtrl::onAdd()
{
   if( !Parent::onAdd() )
      return false;

   return true;
}

void GuiDecalEditorCtrl::initPersistFields()
{
   addField( "currentDecalData", TYPEID< DecalData >(), Offset( mCurrentDecalData, GuiDecalEditorCtrl ) );

   Parent::initPersistFields();
}

void GuiDecalEditorCtrl::consoleInit()
{
   Con::addVariable( "$DecalEditor::renderPixelSize", TypeBool, &smRenderDecalPixelSize, 
      "Set true to render the pixel size as on overlay on the selected decal instance. "
      "This is the value used to fade distant decals and is intended to help the user adjust "
      "the values of DecalData::pixelSizeStartFade and pixelSizeEndFade.\n\n"
	  "@internal" );

   Parent::consoleInit();
}

void GuiDecalEditorCtrl::onEditorDisable()
{
   // Tools are not deleted/recreated between missions, but decals instances
   // ARE. So we must release any references.
   mSELDecal = NULL;
   mHLDecal = NULL;
}

bool GuiDecalEditorCtrl::onWake()
{
   if ( !Parent::onWake() )
      return false;
	
	

   return true;
}

void GuiDecalEditorCtrl::onSleep()
{
   Parent::onSleep();   
}

void GuiDecalEditorCtrl::get3DCursor( GuiCursor *&cursor, 
                                       bool &visible, 
                                       const Gui3DMouseEvent &event_ )
{
   cursor = NULL;
   visible = false;

   GuiCanvas *root = getRoot();
   if ( !root )
      return;

   S32 currCursor = PlatformCursorController::curArrow;

   if ( root->mCursorChanged == currCursor )
      return;

   PlatformWindow *window = root->getPlatformWindow();
   PlatformCursorController *controller = window->getCursorController();
   
   // We've already changed the cursor, 
   // so set it back before we change it again.
   if( root->mCursorChanged != -1)
      controller->popCursor();

   // Now change the cursor shape
   controller->pushCursor(currCursor);
   root->mCursorChanged = currCursor;   
}

void GuiDecalEditorCtrl::on3DMouseDown(const Gui3DMouseEvent & event)
{
   mPerformedDragCopy = false;

   if ( !isFirstResponder() )
      setFirstResponder();
	
	bool dblClick = ( event.mouseClickCount > 1 );

	// Gather selected decal information 
   RayInfo ri;
   bool hit = getRayInfo( event, &ri );

   Point3F start = event.pos;
   Point3F end = start + event.vec * 3000.0f; // use visible distance here??

   DecalInstance *pDecal = gDecalManager->raycast( start, end );
	
	if( mMode.compare("AddDecalMode") != 0 )
	{
		if ( mSELDecal )
		{
			// If our click hit the gizmo we are done.			
			if ( mGizmo->getSelection() != Gizmo::None )
			{
            mGizmo->on3DMouseDown( event );

				char returnBuffer[256];
				dSprintf(returnBuffer, sizeof(returnBuffer), "%f %f %f %f %f %f %f", 
				mSELDecal->mPosition.x, mSELDecal->mPosition.y, mSELDecal->mPosition.z, 
				mSELDecal->mTangent.x, mSELDecal->mTangent.y, mSELDecal->mTangent.z,
				mSELDecal->mSize);

				Con::executef( this, "prepGizmoTransform", Con::getIntArg(mSELDecal->mId), returnBuffer );

				return;
			}
		}

		if ( mHLDecal && pDecal == mHLDecal )
		{
			mHLDecal = NULL;            
			selectDecal( pDecal );   

			if ( isMethod( "onSelectInstance" ) )
			{
				char idBuf[512];
				dSprintf(idBuf, 512, "%i", pDecal->mId);
				Con::executef( this, "onSelectInstance", String(idBuf).c_str(), pDecal->mDataBlock->lookupName.c_str() );
			}

			return;
		}
		else if ( hit && !pDecal)
		{
			if ( dblClick )
				setMode( String("AddDecalMode"), true );

			return;
		}
	}
	else
	{
		// If we accidently hit a decal, then bail(probably an accident). If the use hits the decal twice,
		// then boot them into selection mode and select the decal.
		if ( mHLDecal && pDecal == mHLDecal )
		{
			if ( dblClick )
			{
				mHLDecal = NULL;            
				selectDecal( pDecal );   

				if ( isMethod( "onSelectInstance" ) )
				{
					char idBuf[512];
					dSprintf(idBuf, 512, "%i", pDecal->mId);
					Con::executef( this, "onSelectInstance", String(idBuf).c_str(), pDecal->mDataBlock->lookupName.c_str() );
				}
				setMode( String("SelectDecalMode"), true );
			}
			return;	
		}

		if ( hit && mCurrentDecalData ) // Create a new decal...
		{
			U8 flags = PermanentDecal | SaveDecal;

			DecalInstance *decalInst = gDecalManager->addDecal( ri.point, ri.normal, 0.0f, mCurrentDecalData, 1.0f, -1, flags );      
	      
			if ( decalInst )  
			{
				// Give the decal an id
				decalInst->mId = gDecalManager->mDecalInstanceVec.size();
				gDecalManager->mDecalInstanceVec.push_back(decalInst);

				selectDecal( decalInst );
				
				// Grab the mission editor undo manager.
				UndoManager *undoMan = NULL;
				if ( !Sim::findObject( "EUndoManager", undoMan ) )
				{
					Con::errorf( "GuiMeshRoadEditorCtrl::on3DMouseDown() - EUndoManager not found!" );
					return;           
				}

				// Create the UndoAction.
				DICreateUndoAction *action = new DICreateUndoAction("Create Decal");
				action->addDecal( *decalInst );
				
				action->mEditor = this;
				// Submit it.               
				undoMan->addAction( action );

				if ( isMethod( "onCreateInstance" ) )
				{
					char buffer[512];
					dSprintf(buffer, 512, "%i", decalInst->mId);
					Con::executef( this, "onCreateInstance", buffer, decalInst->mDataBlock->lookupName.c_str());
				}
			}

			return;
		}
	}

   if ( !mSELDecal )
      return;
}

void GuiDecalEditorCtrl::on3DRightMouseDown(const Gui3DMouseEvent & event)
{
   //mIsPanning = true;
   //mGizmo->on3DRightMouseDown( event );
}

void GuiDecalEditorCtrl::on3DRightMouseUp(const Gui3DMouseEvent & event)
{
   //mIsPanning = false;
//   mGizmo->on3DRightMouseUp( event );
}

void GuiDecalEditorCtrl::on3DMouseUp(const Gui3DMouseEvent & event)
{
   if ( mSELDecal )
	{
		if ( mGizmo->isDirty() )
		{
			char returnBuffer[256];
			dSprintf(returnBuffer, sizeof(returnBuffer), "%f %f %f %f %f %f %f", 
			mSELDecal->mPosition.x, mSELDecal->mPosition.y, mSELDecal->mPosition.z, 
			mSELDecal->mTangent.x, mSELDecal->mTangent.y, mSELDecal->mTangent.z,
			mSELDecal->mSize);

			Con::executef( this, "completeGizmoTransform", Con::getIntArg(mSELDecal->mId), returnBuffer );

			mGizmo->markClean();
		}

      mGizmo->on3DMouseUp( event );
	}
}

void GuiDecalEditorCtrl::on3DMouseMove(const Gui3DMouseEvent & event)
{
   if ( mSELDecal )
      mGizmo->on3DMouseMove( event );

   RayInfo ri;
   if ( !getRayInfo( event, &ri ) )
      return; 

   Point3F start = event.pos;
   Point3F end = start + event.vec * 3000.0f; // use visible distance here??

   DecalInstance *pDecal = gDecalManager->raycast( start, end );

   if ( pDecal && pDecal != mSELDecal )
      mHLDecal = pDecal;   
   else if ( !pDecal )
      mHLDecal = NULL;
}

void GuiDecalEditorCtrl::on3DMouseDragged(const Gui3DMouseEvent & event)
{ 
   if ( !mSELDecal )
      return;

   // Doing a drag copy of the decal?
   if ( event.modifier & SI_SHIFT && !mPerformedDragCopy )
   {
      mPerformedDragCopy = true;

		DecalInstance *newDecal = gDecalManager->addDecal(    mSELDecal->mPosition, 
                                                            mSELDecal->mNormal, 
                                                            0.0f, 
                                                            mSELDecal->mDataBlock, 
                                                            1.0f, 
                                                            -1, 
                                                            PermanentDecal | SaveDecal );

      newDecal->mTangent = mSELDecal->mTangent;
      newDecal->mSize = mSELDecal->mSize;
      newDecal->mTextureRectIdx = mSELDecal->mTextureRectIdx;

      // TODO: This is crazy... we should move this sort of tracking
      // inside of the decal manager... IdDecal flag maybe or just a
      // byproduct of PermanentDecal?
      //
		newDecal->mId = gDecalManager->mDecalInstanceVec.size();
		gDecalManager->mDecalInstanceVec.push_back( newDecal );

		selectDecal( newDecal );
			
		// Grab the mission editor undo manager.
		UndoManager *undoMan = NULL;
		if ( Sim::findObject( "EUndoManager", undoMan ) )
		{
			// Create the UndoAction.
			DICreateUndoAction *action = new DICreateUndoAction("Create Decal");
			action->addDecal( *mSELDecal );
			action->mEditor = this;
			undoMan->addAction( action );

			if ( isMethod( "onCreateInstance" ) )
			{
				char buffer[512];
				dSprintf( buffer, 512, "%i", mSELDecal->mId );
				Con::executef( this, "onCreateInstance", buffer, mSELDecal->mDataBlock->lookupName.c_str());
			}
		}
   }

   // Update the Gizmo.
   if (mGizmo->getSelection() != Gizmo::None)
   {
      mGizmo->on3DMouseDragged( event );

      // Pull out the Gizmo transform
      // and position.
      const MatrixF &gizmoMat = mGizmo->getTransform();
      const Point3F &gizmoPos = gizmoMat.getPosition();
      
      // Get the new projection vector.
      VectorF upVec, rightVec;
      gizmoMat.getColumn( 0, &rightVec );
      gizmoMat.getColumn( 2, &upVec );

      const Point3F &scale = mGizmo->getScale();

      // Assign the appropriate changed value back to the decal.
      if ( mGizmo->getMode() == ScaleMode )
      {
         // Save old size.
         const F32 oldSize = mSELDecal->mSize;

         // Set new size.
         mSELDecal->mSize = ( scale.x + scale.y ) * 0.5f;

         // See if the decal properly clips/projects at this size.  If not,
         // stick to the old size.
         mSELEdgeVerts.clear();
         if ( !gDecalManager->clipDecal( mSELDecal, &mSELEdgeVerts ) )
            mSELDecal->mSize = oldSize;
      }
      else if ( mGizmo->getMode() == MoveMode )
         mSELDecal->mPosition = gizmoPos;
      else if ( mGizmo->getMode() == RotateMode )
      {
         mSELDecal->mNormal = upVec;
         mSELDecal->mTangent = rightVec;
      }

      gDecalManager->notifyDecalModified( mSELDecal );

	   Con::executef( this, "syncNodeDetails" );
   }
}

void GuiDecalEditorCtrl::on3DMouseEnter(const Gui3DMouseEvent & event)
{
   // nothing to do
}

void GuiDecalEditorCtrl::on3DMouseLeave(const Gui3DMouseEvent & event)
{
   // nothing to do
}

void GuiDecalEditorCtrl::updateGuiInfo()
{
   // nothing to do
}
      
void GuiDecalEditorCtrl::onRender( Point2I offset, const RectI &updateRect )
{
   Parent::onRender( offset, updateRect );
}

void GuiDecalEditorCtrl::renderGui( Point2I offset, const RectI &updateRect )
{
   Parent::renderGui( offset, updateRect );

   PROFILE_SCOPE( GuiDecalEditorCtrl_renderGui );

   // Show the pixelSize of the selected decal as a text overlay.
   if ( smRenderDecalPixelSize && mSELDecal != NULL )
   {
      const F32 pixelSize = mSELDecal->calcPixelSize( mSaveViewport.extent.y, mLastCameraQuery.cameraMatrix.getPosition(), mSaveWorldToScreenScale.y );
      
      // Find position onscreen to render the text.
      Point3F screenPos;
      bool onScreen = project( mSELDecal->mPosition, &screenPos );

      if ( onScreen )
      {
         // It is extremely annoying to require the GuiProfile to have a font
         // or to create one within the decal editor for only this single use,
         // so we instead rely on the fact that we already have a Gizmo, that
         // all Gizmo's have a GizmoProfile, and that GizmoProfile has a font.
         GFont *font = mGizmo->getProfile()->font;

         // Might as well use some colors defined in GizmoProfile too instead
         // of just hardcoding it here.
         const ColorI bgColor = mGizmo->getProfile()->inActiveColor;
         const ColorI textColor = mGizmo->getProfile()->activeColor;

         // Note: This mostly mirrors the way WorldEditor renders popupText for
         // the gizmo during a drag operation, consider unifying this into a utility method.

         char buf[256];
         dSprintf( buf, 256, "%0.3f", pixelSize );

         const U32 width = font->getStrWidth((const UTF8 *)buf);;
         const Point2I posi( (U32)screenPos.x, (U32)screenPos.y + 12 );   
         const Point2I minPt(posi.x - width / 2 - 2, posi.y - 1);
         const Point2I maxPt(posi.x + width / 2 + 2, posi.y + font->getHeight() + 1);

         GFXDrawUtil *drawer = GFX->getDrawUtil();
         drawer->drawRectFill( minPt, maxPt, bgColor );
	      GFX->getDrawUtil()->setBitmapModulation( textColor );
         GFX->getDrawUtil()->drawText( mProfile->mFont, Point2I( posi.x - width / 2, posi.y ), buf );
      }      
   }
}

void GuiDecalEditorCtrl::renderScene(const RectI & updateRect)
{
   PROFILE_SCOPE( GuiDecalEditorCtrl_renderScene );

   GFXTransformSaver saver;

   RectI bounds = getBounds();
   
   ColorI hlColor(0,255,0,255);
   ColorI regColor(255,0,0,255);
   ColorI selColor(0,0,255,255);
   ColorI color;
   
   GFXDrawUtil *drawUtil = GFX->getDrawUtil();   

   GFXStateBlockDesc desc;
   desc.setBlend( true );
   desc.setZReadWrite( true, false );

   // Draw 3D stuff here.   
   if ( mSELDecal )
   {
      mGizmo->renderGizmo( mLastCameraQuery.cameraMatrix, mLastCameraQuery.fov );

      mSELEdgeVerts.clear();
      if ( gDecalManager->clipDecal( mSELDecal, &mSELEdgeVerts ) )
         _renderDecalEdge( mSELEdgeVerts, ColorI( 255, 255, 255, 255 ) );

      const F32 &decalSize = mSELDecal->mSize;
      Point3F boxSize( decalSize, decalSize, decalSize );

      MatrixF worldMat( true );
      mSELDecal->getWorldMatrix( &worldMat, true );   

      drawUtil->drawObjectBox( desc, boxSize, mSELDecal->mPosition, worldMat, ColorI( 255, 255, 255, 255 ) );
   }

   if ( mHLDecal )
   {
      mHLEdgeVerts.clear();
      if ( gDecalManager->clipDecal( mHLDecal, &mHLEdgeVerts ) )
         _renderDecalEdge( mHLEdgeVerts, ColorI( 255, 255, 255, 255 ) );

      const F32 &decalSize = mHLDecal->mSize;
      Point3F boxSize( decalSize, decalSize, decalSize );

      MatrixF worldMat( true );
      mHLDecal->getWorldMatrix( &worldMat, true );  

      drawUtil->drawObjectBox( desc, boxSize, mHLDecal->mPosition, worldMat, ColorI( 255, 255, 255, 255 ) );
   }
} 

void GuiDecalEditorCtrl::forceRedraw( DecalInstance * decalInstance )
{ 
   // This should be redundant because the decal is already reclipped
   // on each frame. Also it is not possible execute rendering code like
   // this in response to UI events from script.
   /*
	if ( !decalInstance )
		return;

	GFXDrawUtil *drawUtil = GFX->getDrawUtil();  
	GFXStateBlockDesc desc;
   desc.setBlend( true );
   desc.setZReadWrite( true, false );

   Vector<Point3F> verts;
   verts.clear();
   if ( gDecalManager->clipDecal( decalInstance, &verts ) )
   if ( gDecalManager->clipDecal( decalInstance, &verts ) )
      _renderDecalEdge( verts, ColorI( 255, 255, 255, 255 ) );

   const F32 &decalSize = decalInstance->mSize;
   Point3F boxSize( decalSize, decalSize, decalSize );

   MatrixF worldMat( true );
   decalInstance->getWorldMatrix( &worldMat, true );   

   drawUtil->drawObjectBox( desc, boxSize, decalInstance->mPosition, worldMat, ColorI( 255, 255, 255, 255 ) );
   */
}

void GuiDecalEditorCtrl::_renderDecalEdge( const Vector<Point3F> &verts, const ColorI &color )
{
   U32 vertCount = verts.size();

   GFXTransformSaver saver;

   PrimBuild::color( color );

   Point3F endPt( 0, 0, 0 );
   for ( U32 i = 0; i < vertCount; i++ )
   {
      const Point3F &vert = verts[i];
      if ( i + 1 < vertCount )
         endPt = verts[i + 1];
      else
         break;

      PrimBuild::begin( GFXLineList, 2 );

      PrimBuild::vertex3f( vert.x, vert.y, vert.z );
      PrimBuild::vertex3f( endPt.x, endPt.y, endPt.z );

      PrimBuild::end();
   }
}

bool GuiDecalEditorCtrl::getRayInfo( const Gui3DMouseEvent & event, RayInfo *rInfo )
{       
   Point3F startPnt = event.pos;
   Point3F endPnt = event.pos + event.vec * 3000.0f;

   bool hit;         
         
   hit = gServerContainer.castRayRendered( startPnt, endPnt, STATIC_COLLISION_TYPEMASK, rInfo );    
   
   return hit;
}

void GuiDecalEditorCtrl::selectDecal( DecalInstance *decalInst )
{
   // If id is zero or invalid we set the selected decal to null
   // which is correct.
   mSELDecal = decalInst;

   if ( decalInst )
      setGizmoFocus( decalInst );
}

void GuiDecalEditorCtrl::deleteSelectedDecal()
{
   if ( !mSELDecal )
      return;
	
	// Grab the mission editor undo manager.
	UndoManager *undoMan = NULL;
	if ( !Sim::findObject( "EUndoManager", undoMan ) )
	{
		Con::errorf( "GuiMeshRoadEditorCtrl::on3DMouseDown() - EUndoManager not found!" );
		return;           
	}

	// Create the UndoAction.
	DIDeleteUndoAction *action = new DIDeleteUndoAction("Delete Decal");
	action->deleteDecal( *mSELDecal );
	
	action->mEditor = this;
	// Submit it.               
	undoMan->addAction( action );
	
	if ( isMethod( "onDeleteInstance" ) )
	{
		char buffer[512];
		dSprintf(buffer, 512, "%i", mSELDecal->mId);
		Con::executef( this, "onDeleteInstance", String(buffer).c_str(), mSELDecal->mDataBlock->lookupName.c_str() );
	}

   gDecalManager->removeDecal( mSELDecal );
   mSELDecal = NULL;
}

void GuiDecalEditorCtrl::deleteDecalDatablock( String lookupName )
{
	DecalData * datablock = dynamic_cast<DecalData*> ( Sim::findObject(lookupName.c_str()) );
	if( !datablock )
		return;

	// Grab the mission editor undo manager.
	UndoManager *undoMan = NULL;
	if ( !Sim::findObject( "EUndoManager", undoMan ) )
	{
		Con::errorf( "GuiMeshRoadEditorCtrl::on3DMouseDown() - EUndoManager not found!" );
		return;           
	}

	// Create the UndoAction.
	DBDeleteUndoAction *action = new DBDeleteUndoAction("Delete Decal Datablock");
	action->mEditor = this;
	action->mDatablockId = datablock->getId();
	
	Vector<DecalInstance*> mDecalQueue;
	Vector<DecalInstance *>::iterator iter;
	mDecalQueue.clear();
	const Vector<DecalSphere*> &grid = gDecalManager->getDecalDataFile()->getSphereList();

	for ( U32 i = 0; i < grid.size(); i++ )
	{
		const DecalSphere *decalSphere = grid[i];
		mDecalQueue.merge( decalSphere->mItems );
	}

	for ( iter = mDecalQueue.begin();iter != mDecalQueue.end();iter++ )
	{	
		if( !(*iter) )
			continue;

		if( (*iter)->mDataBlock->lookupName.compare( lookupName ) == 0 )
		{
			if( (*iter)->mId != -1 )
			{
				//make sure to call onDeleteInstance as well
				if ( isMethod( "onDeleteInstance" ) )
				{
					char buffer[512];
					dSprintf(buffer, 512, "%i", (*iter)->mId);
					Con::executef( this, "onDeleteInstance", String(buffer).c_str(), (*iter)->mDataBlock->lookupName.c_str() );
				}
				
				action->deleteDecal( *(*iter) );
				
				if( mSELDecal == (*iter) )
					mSELDecal = NULL;

				if( mHLDecal == (*iter) )
					mHLDecal = NULL;
			}
			gDecalManager->removeDecal( (*iter) );
		}
	}
	
	undoMan->addAction( action );

	mCurrentDecalData = NULL;
}

void GuiDecalEditorCtrl::retargetDecalDatablock( String dbFrom, String dbTo )
{
	DecalData * ptrFrom = dynamic_cast<DecalData*> ( Sim::findObject(dbFrom.c_str()) );
	DecalData * ptrTo = dynamic_cast<DecalData*> ( Sim::findObject(dbTo.c_str()) );
	
	if( !ptrFrom || !ptrTo )
		return;
	
	// Grab the mission editor undo manager.
	UndoManager *undoMan = NULL;
	if ( !Sim::findObject( "EUndoManager", undoMan ) )
	{
		Con::errorf( "GuiMeshRoadEditorCtrl::on3DMouseDown() - EUndoManager not found!" );
		return;           
	}

	// Create the UndoAction.
	DBRetargetUndoAction *action = new DBRetargetUndoAction("Retarget Decal Datablock");
	action->mEditor = this;
	action->mDBFromId = ptrFrom->getId();
	action->mDBToId = ptrTo->getId();

	Vector<DecalInstance*> mDecalQueue;
	Vector<DecalInstance *>::iterator iter;
	mDecalQueue.clear();
	const Vector<DecalSphere*> &grid = gDecalManager->getDecalDataFile()->getSphereList();
	for ( U32 i = 0; i < grid.size(); i++ )
	{
		const DecalSphere *decalSphere = grid[i];
		mDecalQueue.merge( decalSphere->mItems );
	}

	for ( iter = mDecalQueue.begin();iter != mDecalQueue.end();iter++ )
	{	
		if( !(*iter) )
			continue;

		if( (*iter)->mDataBlock->lookupName.compare( dbFrom ) == 0 )
		{
			if( (*iter)->mId != -1 )
			{
				action->retargetDecal((*iter));	
				(*iter)->mDataBlock = ptrTo;
				forceRedraw((*iter));
			}
		}
	}

	undoMan->addAction( action );
}

void GuiDecalEditorCtrl::setMode( String mode, bool sourceShortcut = false )
{
	if( mode.compare("SelectDecalMode") == 0)
		mGizmo->getProfile()->mode = NoneMode;
	else if( mode.compare("AddDecalMode") == 0)
		mGizmo->getProfile()->mode = NoneMode;
	else if( mode.compare("MoveDecalMode") == 0)
		mGizmo->getProfile()->mode = MoveMode;
	else if( mode.compare("RotateDecalMode") == 0)
		mGizmo->getProfile()->mode = RotateMode;
	else if( mode.compare("ScaleDecalMode") == 0)
		mGizmo->getProfile()->mode = ScaleMode;
	
	mMode = mode;

	if( sourceShortcut )
		Con::executef( this, "paletteSync", mMode );
}

ConsoleMethod( GuiDecalEditorCtrl, deleteSelectedDecal, void, 2, 2, "deleteSelectedDecal()" )
{
   object->deleteSelectedDecal();
}

ConsoleMethod( GuiDecalEditorCtrl, deleteDecalDatablock, void, 3, 3, "deleteSelectedDecalDatablock( String datablock )" )
{
	String lookupName( argv[2] );
	if( lookupName == String::EmptyString )
		return;
	
	object->deleteDecalDatablock( lookupName );
}

ConsoleMethod( GuiDecalEditorCtrl, setMode, void, 3, 3, "setMode( String mode )()" )
{
	String newMode = ( argv[2] );
	object->setMode( newMode );
}

ConsoleMethod( GuiDecalEditorCtrl, getMode, const char*, 2, 2, "getMode()" )
{
	return object->mMode;
}

ConsoleMethod( GuiDecalEditorCtrl, getDecalCount, S32, 2, 2, "getDecalCount()" )
{
	return gDecalManager->mDecalInstanceVec.size();
}

ConsoleMethod( GuiDecalEditorCtrl, getDecalTransform, const char*, 3, 3, "getDecalTransform()" )
{
	DecalInstance *decalInstance = gDecalManager->mDecalInstanceVec[dAtoi(argv[2])];
	if( decalInstance == NULL )
		return "";

	char* returnBuffer = Con::getReturnBuffer(256);
   returnBuffer[0] = 0;

   if ( decalInstance )
   {
	   dSprintf(returnBuffer, 256, "%f %f %f %f %f %f %f",
         decalInstance->mPosition.x, decalInstance->mPosition.y, decalInstance->mPosition.z, 
		   decalInstance->mTangent.x, decalInstance->mTangent.y, decalInstance->mTangent.z,
		   decalInstance->mSize);
   }

	return returnBuffer;
}

ConsoleMethod( GuiDecalEditorCtrl, getDecalLookupName, const char*, 3, 3, "getDecalLookupName( S32 )()" )
{
	DecalInstance *decalInstance = gDecalManager->mDecalInstanceVec[dAtoi(argv[2])];
	if( decalInstance == NULL )
		return "invalid";

	return decalInstance->mDataBlock->lookupName;
}

ConsoleMethod( GuiDecalEditorCtrl, selectDecal, void, 3, 3, "selectDecal( S32 )()" )
{
	DecalInstance *decalInstance = gDecalManager->mDecalInstanceVec[dAtoi(argv[2])];
	if( decalInstance == NULL )
		return;

	object->selectDecal( decalInstance );
}

ConsoleMethod( GuiDecalEditorCtrl, editDecalDetails, void, 4, 4, "editDecalDetails( S32 )()" )
{
	DecalInstance *decalInstance = gDecalManager->mDecalInstanceVec[ dAtoi(argv[2]) ];
	if( decalInstance == NULL )
		return;

	Point3F pos;
	Point3F tan;
	F32 size;

	S32 count = dSscanf( argv[3], "%f %f %f %f %f %f %f", 
		&pos.x, &pos.y, &pos.z, &tan.x, &tan.y, &tan.z, &size);
	
	if ( (count != 7) )
   {
		Con::printf("Failed to parse decal information \"px py pz tx ty tz s\" from '%s'", argv[3]);
      return;
   }

   decalInstance->mPosition = pos;
	decalInstance->mTangent = tan;
	decalInstance->mSize = size;
	
	if ( decalInstance == object->mSELDecal )
		object->setGizmoFocus( decalInstance );

	object->forceRedraw( decalInstance );

	gDecalManager->notifyDecalModified( decalInstance );
}

ConsoleMethod( GuiDecalEditorCtrl, getSelectionCount, S32, 2, 2, "" )
{
   if ( object->mSELDecal != NULL )
      return 1;
   return 0;
}

ConsoleMethod( GuiDecalEditorCtrl, retargetDecalDatablock, void, 4, 4, "" )
{
   if( dStrcmp( argv[2], "" ) != 0 && dStrcmp( argv[3], "" ) != 0 )
		object->retargetDecalDatablock( argv[2], argv[3] );
}

void GuiDecalEditorCtrl::setGizmoFocus( DecalInstance * decalInstance )
{
	const F32 &size = decalInstance->mSize;
   MatrixF worldMat( true );
   decalInstance->getWorldMatrix( &worldMat, true );
   worldMat.setPosition( Point3F( 0, 0, 0 ) );
   mGizmo->set( worldMat, decalInstance->mPosition, Point3F( size, size, size ) );
}

//Decal Instance Create Undo Actions
IMPLEMENT_CONOBJECT( DICreateUndoAction );

ConsoleDocClass( DICreateUndoAction,
				"@brief Decal Instance Create Undo Actions\n\n"
				"Not intended for game development, for editors or internal use only.\n\n "
				"@internal");

DICreateUndoAction::DICreateUndoAction( const UTF8* actionName )
   :  UndoAction( actionName )
{
}

DICreateUndoAction::~DICreateUndoAction()
{
}

void DICreateUndoAction::initPersistFields()
{
   Parent::initPersistFields();
}

void DICreateUndoAction::addDecal( DecalInstance decal )
{
	mDecalInstance = decal;
	mDatablockId = decal.mDataBlock->getId();
}

void DICreateUndoAction::undo()
{
	Vector<DecalInstance *>::iterator iter;
   for(iter = gDecalManager->mDecalInstanceVec.begin();iter != gDecalManager->mDecalInstanceVec.end();iter++)
	{
		if( !(*iter) )
			continue;
		
		if( (*iter)->mId != mDecalInstance.mId )
			continue;

		if ( mEditor->isMethod( "onDeleteInstance" ) )
		{
			char buffer[512];
			dSprintf(buffer, 512, "%i", (*iter)->mId);
			Con::executef( mEditor, "onDeleteInstance", String(buffer).c_str(), (*iter)->mDataBlock->lookupName.c_str() );
		}
		
		// Decal manager handles clearing the vector if the decal contains a valid id
		if( mEditor->mSELDecal == (*iter) )
			mEditor->mSELDecal = NULL;

		if( mEditor->mHLDecal == (*iter) )
			mEditor->mHLDecal = NULL;

		gDecalManager->removeDecal( (*iter) );
		break;
	}
}

void DICreateUndoAction::redo()
{
	//Reinstate the valid datablock pointer	
	mDecalInstance.mDataBlock = dynamic_cast<DecalData *>( Sim::findObject( mDatablockId ) );

	DecalInstance * decal = gDecalManager->addDecal( mDecalInstance.mPosition, 
		mDecalInstance.mNormal, 
		mDecalInstance.mTangent, 
		mDecalInstance.mDataBlock,
		( mDecalInstance.mSize / mDecalInstance.mDataBlock->size ), 
		mDecalInstance.mTextureRectIdx, 
		mDecalInstance.mFlags );
	
	decal->mId = mDecalInstance.mId;

	// Override the rectIdx regardless of random decision in addDecal
	decal->mTextureRectIdx = mDecalInstance.mTextureRectIdx;
	
	// We take care of filling in the vector space that was once there
	gDecalManager->mDecalInstanceVec[decal->mId] = decal;

	if ( mEditor->isMethod( "onCreateInstance" ) )
	{
		char buffer[512];
		dSprintf(buffer, 512, "%i", decal->mId);
		Con::executef( mEditor, "onCreateInstance", buffer, decal->mDataBlock->lookupName.c_str());
	}
	mEditor->selectDecal( decal );
}

//Decal Instance Delete Undo Actions
IMPLEMENT_CONOBJECT( DIDeleteUndoAction );

ConsoleDocClass( DIDeleteUndoAction,
				"@brief Decal Instance Delete Undo Actions\n\n"
				"Not intended for game development, for editors or internal use only.\n\n "
				"@internal");

DIDeleteUndoAction::DIDeleteUndoAction( const UTF8 *actionName )
   :  UndoAction( actionName )
{
}

DIDeleteUndoAction::~DIDeleteUndoAction()
{
}

void DIDeleteUndoAction::initPersistFields()
{
   Parent::initPersistFields();
}

void DIDeleteUndoAction::deleteDecal( DecalInstance decal )
{
	mDecalInstance = decal;
	mDatablockId = decal.mDataBlock->getId();
}

void DIDeleteUndoAction::undo()
{
	//Reinstate the valid datablock pointer	
	mDecalInstance.mDataBlock = dynamic_cast<DecalData *>( Sim::findObject( mDatablockId ) );

	DecalInstance * decal = gDecalManager->addDecal( mDecalInstance.mPosition, 
		mDecalInstance.mNormal, 
		mDecalInstance.mTangent, 
		mDecalInstance.mDataBlock,
		( mDecalInstance.mSize / mDecalInstance.mDataBlock->size ), 
		mDecalInstance.mTextureRectIdx, 
		mDecalInstance.mFlags );
	
	decal->mId = mDecalInstance.mId;

	// Override the rectIdx regardless of random decision in addDecal
	decal->mTextureRectIdx = mDecalInstance.mTextureRectIdx;
	
	// We take care of filling in the vector space that was once there
	gDecalManager->mDecalInstanceVec[decal->mId] = decal;

	if ( mEditor->isMethod( "onCreateInstance" ) )
	{
		char buffer[512];
		dSprintf(buffer, 512, "%i", decal->mId);
		Con::executef( mEditor, "onCreateInstance", buffer, decal->mDataBlock->lookupName.c_str());
	}
	mEditor->selectDecal( decal );
}

void DIDeleteUndoAction::redo()
{
	Vector<DecalInstance *>::iterator iter;
   for(iter = gDecalManager->mDecalInstanceVec.begin();iter != gDecalManager->mDecalInstanceVec.end();iter++)
	{
		if( !(*iter) )
			continue;
		
		if( (*iter)->mId != mDecalInstance.mId )
			continue;

		if ( mEditor->isMethod( "onDeleteInstance" ) )
		{
			char buffer[512];
			dSprintf(buffer, 512, "%i", (*iter)->mId);
			Con::executef( mEditor, "onDeleteInstance", String(buffer).c_str(), (*iter)->mDataBlock->lookupName.c_str() );
		}
		
		// Decal manager handles clearing the vector if the decal contains a valid id
		if( mEditor->mSELDecal == (*iter) )
			mEditor->mSELDecal = NULL;

		if( mEditor->mHLDecal == (*iter) )
			mEditor->mHLDecal = NULL;

		gDecalManager->removeDecal( (*iter) );
		break;
	}
}

//Decal Datablock Delete Undo Actions
IMPLEMENT_CONOBJECT( DBDeleteUndoAction );

ConsoleDocClass( DBDeleteUndoAction,
				"@brief Decal Datablock Delete Undo Actions\n\n"
				"Not intended for game development, for editors or internal use only.\n\n "
				"@internal");

DBDeleteUndoAction::DBDeleteUndoAction( const UTF8 *actionName )
   :  UndoAction( actionName )
{
}

DBDeleteUndoAction::~DBDeleteUndoAction()
{
}

void DBDeleteUndoAction::initPersistFields()
{
   Parent::initPersistFields();
}

void DBDeleteUndoAction::deleteDecal( DecalInstance decal )
{
	mDecalInstanceVec.increment();
   mDecalInstanceVec.last() = decal;
}

void DBDeleteUndoAction::undo()
{
	DecalData * datablock = dynamic_cast<DecalData *>( Sim::findObject( mDatablockId ) );
	if ( mEditor->isMethod( "undoDeleteDecalDatablock" ) )
			Con::executef( mEditor, "undoDeleteDecalDatablock", datablock->lookupName.c_str());

	// Create and restore the decal instances
	for ( S32 i= mDecalInstanceVec.size()-1; i >= 0; i-- )
   {
		DecalInstance vecInstance = mDecalInstanceVec[i];

		//Reinstate the valid datablock pointer		
		vecInstance.mDataBlock = datablock;

		DecalInstance * decalInstance = gDecalManager->addDecal( vecInstance.mPosition, 
		vecInstance.mNormal, 
		vecInstance.mTangent, 
		vecInstance.mDataBlock,
		( vecInstance.mSize / vecInstance.mDataBlock->size ), 
		vecInstance.mTextureRectIdx, 
		vecInstance.mFlags );
	
		decalInstance->mId = vecInstance.mId;

		// Override the rectIdx regardless of random decision in addDecal
		decalInstance->mTextureRectIdx = vecInstance.mTextureRectIdx;
	
		// We take care of filling in the vector space that was once there
		gDecalManager->mDecalInstanceVec[decalInstance->mId] = decalInstance;

		if ( mEditor->isMethod( "onCreateInstance" ) )
		{
			char buffer[512];
			dSprintf(buffer, 512, "%i", decalInstance->mId);
			Con::executef( mEditor, "onCreateInstance", buffer, decalInstance->mDataBlock->lookupName.c_str());
		}
	}
	
}

void DBDeleteUndoAction::redo()
{
	for ( S32 i=0; i < mDecalInstanceVec.size(); i++ )
   {
		DecalInstance vecInstance = mDecalInstanceVec[i];

		Vector<DecalInstance *>::iterator iter;
		for(iter = gDecalManager->mDecalInstanceVec.begin();iter != gDecalManager->mDecalInstanceVec.end();iter++)
		{
			DecalInstance * decalInstance = (*iter);
			if( !decalInstance )
				continue;
			
			if( decalInstance->mId != vecInstance.mId )
				continue;

			if ( mEditor->isMethod( "onDeleteInstance" ) )
			{
				char buffer[512];
				dSprintf(buffer, 512, "%i", decalInstance->mId);
				Con::executef( mEditor, "onDeleteInstance", String(buffer).c_str(), decalInstance->mDataBlock->lookupName.c_str() );
			}
			
			// Decal manager handles clearing the vector if the decal contains a valid id
			if( mEditor->mSELDecal == decalInstance )
				mEditor->mSELDecal = NULL;

			if( mEditor->mHLDecal == decalInstance )
				mEditor->mHLDecal = NULL;

			gDecalManager->removeDecal( decalInstance );
			break;
		}
	}
	
	DecalData * datablock = dynamic_cast<DecalData *>( Sim::findObject( mDatablockId ) );
	if ( mEditor->isMethod( "redoDeleteDecalDatablock" ) )
		Con::executef( mEditor, "redoDeleteDecalDatablock", datablock->lookupName.c_str());
}

//------------------------------
//Decal Datablock Retarget Undo Actions
IMPLEMENT_CONOBJECT( DBRetargetUndoAction );

ConsoleDocClass( DBRetargetUndoAction,
				"@brief Decal Datablock Retarget Undo Actions\n\n"
				"Not intended for game development, for editors or internal use only.\n\n "
				"@internal");

DBRetargetUndoAction::DBRetargetUndoAction( const UTF8 *actionName )
   :  UndoAction( actionName )
{
}

DBRetargetUndoAction::~DBRetargetUndoAction()
{
}

void DBRetargetUndoAction::initPersistFields()
{
   Parent::initPersistFields();
}

void DBRetargetUndoAction::retargetDecal( DecalInstance* decal )
{
	mDecalInstanceVec.increment();
   mDecalInstanceVec.last() = decal;
}

void DBRetargetUndoAction::undo()
{
	DecalData * ptrFrom = dynamic_cast<DecalData*> ( Sim::findObject(mDBFromId) );
	
	if( !ptrFrom )
		return;

	Vector<DecalInstance *>::iterator iter;
	for(iter = mDecalInstanceVec.begin();iter != mDecalInstanceVec.end();iter++)
   {
		(*iter)->mDataBlock = ptrFrom;
		mEditor->forceRedraw((*iter));
	}
	if ( mEditor->isMethod( "rebuildInstanceTree" ) )
			Con::executef( mEditor, "rebuildInstanceTree" );
}

void DBRetargetUndoAction::redo()
{
	DecalData * ptrTo = dynamic_cast<DecalData*> ( Sim::findObject(mDBToId) );
	
	if( !ptrTo )
		return;

	Vector<DecalInstance *>::iterator iter;
	for(iter = mDecalInstanceVec.begin();iter != mDecalInstanceVec.end();iter++)
   {
		(*iter)->mDataBlock = ptrTo;
		mEditor->forceRedraw((*iter));
	}
	
	if ( mEditor->isMethod( "rebuildInstanceTree" ) )
		Con::executef( mEditor, "rebuildInstanceTree" );
}
#endif