//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------

#include "platform/platform.h"
#include "gui/containers/guiSplitContainer.h"

#include "gui/core/guiCanvas.h"
#include "console/consoleTypes.h"
#include "gfx/gfxDrawUtil.h"


IMPLEMENT_CONOBJECT( GuiSplitContainer );

ConsoleDocClass( GuiSplitContainer,
   "@brief A container that splits its area between two child controls.\n\n"
   
   "A GuiSplitContainer can be used to dynamically subdivide an area between two child controls.  "
   "A splitter bar is placed between the two controls and allows to dynamically adjust the sizing "
   "ratio between the two sides.  Splitting can be either horizontal (subdividing top and bottom) "
   "or vertical (subdividing left and right) depending on #orientation.\n\n"
   
   "By using #fixedPanel, one of the panels can be chosen to remain at a fixed size (#fixedSize)."
   
   "@tsexample\n"
   "// Create a vertical splitter with a fixed-size left panel.\n"
   "%splitter = new GuiSplitContainer()\n"
   "{\n"
   "   orientation = \"Vertical\";\n"
   "   fixedPanel = \"FirstPanel\";\n"
   "   fixedSize = 100;\n"
   "\n"
   "   new GuiScrollCtrl()\n"
   "   {\n"
   "      new GuiMLTextCtrl()\n"
   "      {\n"
   "         text = %longText;\n"
   "      };\n"
   "   };\n"
   "\n"
   "   new GuiScrollCtrl()\n"
   "   {\n"
   "      new GuiMLTextCtrl()\n"
   "      {\n"
   "         text = %moreLongText;\n"
   "      };\n"
   "   };\n"
   "};\n"
   "@endtsexample\n\n"
   
   "@note The children placed inside GuiSplitContainers must be GuiContainers.\n\n"
   
   "@ingroup GuiContainers"
);


ImplementEnumType( GuiSplitOrientation,
   "Axis along which to divide the container's space.\n\n"
   "@ingroup GuiContainers" )
   { GuiSplitContainer::Vertical, "Vertical", "Divide vertically placing one child left and one child right." },
   { GuiSplitContainer::Horizontal, "Horizontal", "Divide horizontally placing one child on top and one child below." }   
EndImplementEnumType;

ImplementEnumType( GuiSplitFixedPanel,
   "Which side of the splitter to keep at a fixed size (if any).\n\n"
   "@ingroup GuiContainers" )
   { GuiSplitContainer::None, "None", "Allow both childs to resize (default)." },
   { GuiSplitContainer::FirstPanel, "FirstPanel", "Keep " },
   { GuiSplitContainer::SecondPanel, "SecondPanel" }
EndImplementEnumType;


//-----------------------------------------------------------------------------

GuiSplitContainer::GuiSplitContainer()
 : mFixedPanel( None ),
   mFixedPanelSize( 100 ),
   mOrientation( Vertical ),
   mSplitterSize( 2 ),
   mSplitPoint( 0, 0 ),
   mSplitRect( 0, 0, mSplitterSize, mSplitterSize ),
   mDragging( false )
{
   setMinExtent( Point2I(64,64) );
   setExtent(200,200);
   setDocking( Docking::dockNone );

   mSplitPoint.set( 300, 100 );

   // We only support client docked items in a split container
   mValidDockingMask = Docking::dockClient;
}

//-----------------------------------------------------------------------------

void GuiSplitContainer::initPersistFields()
{
   addGroup( "Splitter", "Options to configure split panels contained by this control" );
   
      addField( "orientation",   TYPEID< Orientation >(),   Offset( mOrientation, GuiSplitContainer),
         "Whether to split between top and bottom (horizontal) or between left and right (vertical)." );
      addField( "splitterSize",  TypeS32,    Offset( mSplitterSize, GuiSplitContainer),
         "Width of the splitter bar between the two sides.  Default is 2." );
      addField( "splitPoint",    TypePoint2I, Offset( mSplitPoint, GuiSplitContainer),
         "Point on control through which the splitter goes.\n\n"
         "Changed relatively if size of control changes." );
      addField( "fixedPanel",    TYPEID< FixedPanel >(),   Offset( mFixedPanel, GuiSplitContainer),
         "Which (if any) side of the splitter to keep at a fixed size." );
      addField( "fixedSize",     TypeS32,    Offset( mFixedPanelSize, GuiSplitContainer),
         "Width of the fixed panel specified by #fixedPanel (if any)." );
      
   endGroup( "Splitter" );

   Parent::initPersistFields();
}

//-----------------------------------------------------------------------------

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

   return true;
}

//-----------------------------------------------------------------------------

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

   // Create Panel 1
   if ( empty() )
   {
      GuiPanel *newPanel = new GuiPanel();
      AssertFatal( newPanel, "GuiSplitContainer::onAdd - Cannot create subordinate panel #1!" );
      newPanel->registerObject();
      newPanel->setInternalName( "Panel1" );
      newPanel->setDocking( Docking::dockClient );
      addObject( (SimObject*)newPanel );
   }
   else
   {
      GuiContainer *containerCtrl = dynamic_cast<GuiContainer*>( at(0) );
      if ( containerCtrl )
      {
         containerCtrl->setInternalName( "Panel1" );
         containerCtrl->setDocking( Docking::dockClient );
      }
   }

   if ( size() == 1 )
   {
      // Create Panel 2
      GuiPanel *newPanel = new GuiPanel();
      AssertFatal( newPanel, "GuiSplitContainer::onAdd - Cannot create subordinate panel #2!" );
      newPanel->registerObject();
      newPanel->setInternalName( "Panel2" );
      newPanel->setDocking( Docking::dockClient );
      addObject( (SimObject*)newPanel );
   }
   else
   {
      GuiContainer *containerCtrl = dynamic_cast<GuiContainer*>( at(1) );
      if ( containerCtrl )
      {
         containerCtrl->setInternalName( "Panel2" );
         containerCtrl->setDocking( Docking::dockClient );
      }
   }

   // Has FixedWidth been specified?
   if ( mFixedPanelSize == 0 )
   {
      // Nope, so try to guess as best we can
      GuiContainer *firstPanel = dynamic_cast<GuiContainer*>( at(0) );
      GuiContainer *secondPanel = dynamic_cast<GuiContainer*>( at(1) );
      if ( mFixedPanel == FirstPanel )
      {
         if ( mOrientation == Horizontal )
            mFixedPanelSize = firstPanel->getExtent().y;
         else
            mFixedPanelSize = firstPanel->getExtent().x;

         mSplitPoint = Point2I( mFixedPanelSize, mFixedPanelSize );
      }
      else if ( mFixedPanel == SecondPanel )
      {
         if ( mOrientation == Horizontal )
            mFixedPanelSize = getExtent().y - secondPanel->getExtent().y;
         else
            mFixedPanelSize = getExtent().x - secondPanel->getExtent().x;

         mSplitPoint = getExtent() - Point2I( mFixedPanelSize, mFixedPanelSize );
      }

   }

   setUpdateLayout();

   return true;
}

//-----------------------------------------------------------------------------

void GuiSplitContainer::onRender( Point2I offset, const RectI &updateRect )
{
   Parent::onRender( offset, updateRect );

   // Only render if we're dragging the splitter
   if ( mDragging && mSplitRect.isValidRect() )
   {
      // Splitter Rectangle (will adjust positioning only)
      RectI splitterRect = mSplitRect;

      // Currently being dragged to Rect 
      Point2I splitterPoint = localToGlobalCoord( mSplitRect.point );
      splitterRect.point = localToGlobalCoord( mSplitPoint );

      RectI clientRect = getClientRect();
      clientRect.point = localToGlobalCoord( clientRect.point );

      if ( mOrientation == Horizontal ) 
      {
         splitterRect.point.y -= mSplitterSize;
         splitterRect.point.x = splitterPoint.x;
      }
      else
      {
         splitterRect.point.x -= mSplitterSize;
         splitterRect.point.y = splitterPoint.y;
      }

      RectI oldClip = GFX->getClipRect();
      GFX->setClipRect( clientRect );
      GFX->getDrawUtil()->drawRectFill( splitterRect, mProfile->mFillColorHL );
      GFX->setClipRect( oldClip );

   }
   else
   {
      RectI splitterRect = mSplitRect;
      splitterRect.point += offset;
      GFX->getDrawUtil()->drawRectFill( splitterRect, mProfile->mFillColor );
   }
}

//-----------------------------------------------------------------------------

Point2I GuiSplitContainer::getMinExtent() const
{
   GuiContainer *panelOne = dynamic_cast<GuiContainer*>( at(0) );
   GuiContainer *panelTwo = dynamic_cast<GuiContainer*>( at(1) );

   if ( !panelOne || !panelTwo )
      return Parent::getMinExtent();

   Point2I minExtent = Point2I(0,0);
   Point2I panelOneMinExtent = panelOne->getMinExtent();
   Point2I panelTwoMinExtent = panelTwo->getMinExtent();

   if ( mOrientation == Horizontal )
   {
      minExtent.y = 2 * mSplitterSize + panelOneMinExtent.y + panelTwoMinExtent.y;
      minExtent.x = getMax( panelOneMinExtent.x, panelTwoMinExtent.x );
   }
   else
   {
      minExtent.x = 2 * mSplitterSize + panelOneMinExtent.x + panelTwoMinExtent.x;
      minExtent.y = getMax( panelOneMinExtent.y, panelTwoMinExtent.y );
   }

   return minExtent;
}

//-----------------------------------------------------------------------------

void GuiSplitContainer::parentResized( const RectI &oldParentRect, const RectI &newParentRect )
{
   Parent::parentResized( oldParentRect, newParentRect );
   return;

   // TODO: Is this right James?  This isn't needed anymore?
   /*

   // GuiSplitContainer overrides parentResized to make sure that the proper fixed frame's width/height
   // is not compromised in the call
   
   if ( size() < 2 )
      return;

   GuiContainer *panelOne = dynamic_cast<GuiContainer*>( at(0) );
   GuiContainer *panelTwo = dynamic_cast<GuiContainer*>( at(1) );

   AssertFatal( panelOne && panelTwo, "GuiSplitContainer::parentResized - Missing/Invalid Subordinate Controls! Split contained controls must derive from GuiContainer!" );

   Point2I newDragPos;
   if ( mFixedPanel == FirstPanel )
   {
      newDragPos = panelOne->getExtent();
      newDragPos += Point2I( mSplitterSize, mSplitterSize );
   }
   else if ( mFixedPanel == SecondPanel )
   {
      newDragPos = getExtent() - panelTwo->getExtent();
      newDragPos -= Point2I( mSplitterSize, mSplitterSize );
   }
   else // None
      newDragPos.set( 1, 1);   
  
   RectI clientRect = getClientRect();
   solvePanelConstraints( newDragPos, panelOne, panelTwo, clientRect );

   setUpdateLayout(); 
   */
}

//-----------------------------------------------------------------------------

bool GuiSplitContainer::resize( const Point2I &newPosition, const Point2I &newExtent )
{
   // Save previous extent.
   Point2I oldExtent = getExtent();

   // Resize ourselves.
   if ( !Parent::resize( newPosition, newExtent ) || size() < 2 )
      return false;

   GuiContainer *panelOne = dynamic_cast<GuiContainer*>( at(0) );
   GuiContainer *panelTwo = dynamic_cast<GuiContainer*>( at(1) );
	
   // 
   AssertFatal( panelOne && panelTwo, "GuiSplitContainer::resize - Missing/Invalid Subordinate Controls! Split contained controls must derive from GuiContainer!" );

   // We only need to update the split point if our second panel is fixed.  
   // If the first is fixed, then we can leave the split point alone because
   // the remainder of the size will be added to or taken from the second panel
   Point2I newDragPos;
   if ( mFixedPanel == SecondPanel )
   {      
      S32 deltaX = newExtent.x - oldExtent.x;
      S32 deltaY = newExtent.y - oldExtent.y;

      if( mOrientation == Horizontal )
         mSplitPoint.y += deltaY;
      else
         mSplitPoint.x += deltaX;
   }

   // If we got here, parent returned true
   return true;
}

//-----------------------------------------------------------------------------

bool GuiSplitContainer::layoutControls( RectI &clientRect )
{
   if ( size() < 2 )
      return false;

   GuiContainer *panelOne = dynamic_cast<GuiContainer*>( at(0) );
   GuiContainer *panelTwo = dynamic_cast<GuiContainer*>( at(1) );

   // 
   AssertFatal( panelOne && panelTwo, "GuiSplitContainer::layoutControl - Missing/Invalid Subordinate Controls! Split contained controls must derive from GuiContainer!" );

   RectI panelOneRect = RectI( clientRect.point, Point2I( 0, 0 ) );
   RectI panelTwoRect;
   RectI splitRect;

   solvePanelConstraints( getSplitPoint(), panelOne, panelTwo, clientRect );

   switch( mOrientation )
   {
   case Horizontal:
      panelOneRect.extent = Point2I( clientRect.extent.x, getSplitPoint().y );
      panelTwoRect = panelOneRect;
      panelTwoRect.intersect( clientRect );
      panelTwoRect.point.y = panelOneRect.extent.y;
      panelTwoRect.extent.y = clientRect.extent.y - panelOneRect.extent.y;

      // Generate new Splitter Rectangle
      splitRect = panelTwoRect;
      splitRect.extent.y = 0;
      splitRect.inset( 0, -mSplitterSize );

      panelOneRect.extent.y -= mSplitterSize;
      panelTwoRect.point.y += mSplitterSize;
      panelTwoRect.extent.y -= mSplitterSize;

      break;

   case Vertical:
      panelOneRect.extent = Point2I( getSplitPoint().x, clientRect.extent.y );
      panelTwoRect = panelOneRect;
      panelTwoRect.intersect( clientRect );
      panelTwoRect.point.x = panelOneRect.extent.x;
      panelTwoRect.extent.x = clientRect.extent.x - panelOneRect.extent.x;

      // Generate new Splitter Rectangle
      splitRect = panelTwoRect;
      splitRect.extent.x = 0;
      splitRect.inset( -mSplitterSize, 0 );

      panelOneRect.extent.x -= mSplitterSize;
      panelTwoRect.point.x += mSplitterSize;
      panelTwoRect.extent.x -= mSplitterSize;

      break;
   }

   // Update Split Rect
   mSplitRect = splitRect;

   // Dock Appropriately
   if( !( mFixedPanel == FirstPanel && !panelOne->isVisible() ) )
      dockControl( panelOne, panelOne->getDocking(), panelOneRect );
   if( !( mFixedPanel == FirstPanel && !panelTwo->isVisible() ) )
      dockControl( panelTwo, panelOne->getDocking(), panelTwoRect );   

   // 
   return false;
}

//-----------------------------------------------------------------------------

void GuiSplitContainer::solvePanelConstraints( Point2I newDragPos, GuiContainer * firstPanel, GuiContainer * secondPanel, RectI clientRect )
{
   if( !firstPanel || !secondPanel )
      return;

   if ( mOrientation == Horizontal )
   {
      // Constrain based on Y axis (Horizontal Splitter)

      // This accounts for the splitter width 
      S32 splitterSize = (S32)(mSplitRect.extent.y * 0.5);

      // Collapsed fixed panel
      if ( mFixedPanel == SecondPanel && !secondPanel->isVisible() )
      {
         newDragPos = Point2I(mSplitPoint.x, getExtent().y - splitterSize );
      }
      else if( mFixedPanel == SecondPanel && !firstPanel->isVisible() )
      {
         newDragPos = Point2I(mSplitPoint.x, splitterSize );
      }
      else // Normal constraints
      {
         //newDragPos.y -= splitterSize;
         S32 newPosition = mClamp( newDragPos.y, 
                                   firstPanel->getMinExtent().y + splitterSize,
                                   getExtent().y - secondPanel->getMinExtent().y - splitterSize );
         newDragPos = Point2I( mSplitPoint.x, newPosition );
      }
   }
   else
   {
      // Constrain based on X axis (Vertical Splitter)

      // This accounts for the splitter width 
      S32 splitterSize = (S32)(mSplitRect.extent.x * 0.5);

      // Collapsed fixed panel
      if ( mFixedPanel == SecondPanel && !secondPanel->isVisible() )
      {
         newDragPos = Point2I(getExtent().x - splitterSize, mSplitPoint.y  );
      }
      else if ( mFixedPanel == FirstPanel && !firstPanel->isVisible() )
      {
         newDragPos = Point2I( splitterSize, mSplitPoint.x );
      }
      else // Normal constraints
      {
         S32 newPosition = mClamp( newDragPos.x, firstPanel->getMinExtent().x + splitterSize,
            getExtent().x - secondPanel->getMinExtent().x - splitterSize );
         newDragPos = Point2I( newPosition, mSplitPoint.y );
      }
   }

   // Just in case, clamp to bounds of controls
   newDragPos.x = mClamp( newDragPos.x, clientRect.point.x, clientRect.point.x + clientRect.extent.x );
   newDragPos.y = mClamp( newDragPos.y, clientRect.point.y, clientRect.point.y + clientRect.extent.y );

   mSplitPoint = newDragPos;
}

//-----------------------------------------------------------------------------

void GuiSplitContainer::getCursor( GuiCursor *&cursor, bool &showCursor, const GuiEvent &lastGuiEvent )
{
   GuiCanvas *rootCtrl = getRoot();
   if ( !rootCtrl )
      return;

   S32 desiredCursor = 0;
   RectI splitRect = getSplitRect();

   // Figure out which cursor we want if we need one
   if ( mOrientation == Horizontal )
      desiredCursor = PlatformCursorController::curResizeHorz;
   else if ( mOrientation == Vertical )
      desiredCursor = PlatformCursorController::curResizeVert;

   PlatformWindow *platformWindow = static_cast<GuiCanvas*>(getRoot())->getPlatformWindow();
   AssertFatal( platformWindow != NULL,"GuiControl without owning platform window!  This should not be possible." );

   PlatformCursorController *cusrorController = platformWindow->getCursorController();
   AssertFatal( cusrorController != NULL,"PlatformWindow without an owned CursorController!" );

   // Check to see if we need one or just the default...

   Point2I localPoint = Point2I( globalToLocalCoord( lastGuiEvent.mousePoint ) );
   if ( splitRect.pointInRect( localPoint ) || mDragging  )
   {
      // Do we need to change it or is it already set?
      if ( rootCtrl->mCursorChanged != desiredCursor )
      {
         // We've already changed the cursor, so set it back
         if ( rootCtrl->mCursorChanged != -1 )
            cusrorController->popCursor();

         // Now change the cursor shape
         cusrorController->pushCursor( desiredCursor );
         rootCtrl->mCursorChanged = desiredCursor;
      }
   }
   else if ( rootCtrl->mCursorChanged != -1 )
   {
      // Just the default
      cusrorController->popCursor();
      rootCtrl->mCursorChanged = -1;
   }
}

//-----------------------------------------------------------------------------

void GuiSplitContainer::onMouseDown( const GuiEvent &event )
{
   GuiContainer *firstPanel = dynamic_cast<GuiContainer*>(at(0));
   GuiContainer *secondPanel = dynamic_cast<GuiContainer*>(at(1));

   // This function will constrain the panels to their minExtents and update the mSplitPoint
   if ( firstPanel && secondPanel )
   {
      mouseLock();
      mDragging = true;

      RectI clientRect = getClientRect();
      Point2I newDragPos = globalToLocalCoord( event.mousePoint );

      solvePanelConstraints(newDragPos, firstPanel, secondPanel, clientRect);
   }
}

//-----------------------------------------------------------------------------

void GuiSplitContainer::onMouseUp( const GuiEvent &event )
{
   // If we've been dragging, we need to update the fixed panel extent.  
   // NOTE : This should ONLY ever happen in this function.  the Fixed panel
   // is to REMAIN FIXED unless the user changes it.
   if ( mDragging )
   {
      Point2I newSplitPoint = getSplitPoint();

      // Update Fixed Panel Extent
      if ( mFixedPanel == FirstPanel )
         mFixedPanelSize = ( mOrientation == Horizontal ) ? newSplitPoint.y : newSplitPoint.x;
      else
         mFixedPanelSize = ( mOrientation == Horizontal ) ? getExtent().y - newSplitPoint.y : getExtent().x - newSplitPoint.x;

      setUpdateLayout();
   }

   mDragging = false;
   mouseUnlock();
}

//-----------------------------------------------------------------------------

void GuiSplitContainer::onMouseDragged( const GuiEvent &event )
{
   GuiContainer *firstPanel = dynamic_cast<GuiContainer*>(at(0));
   GuiContainer *secondPanel = dynamic_cast<GuiContainer*>(at(1));

   // This function will constrain the panels to their minExtents and update the mSplitPoint
   if ( mDragging && firstPanel && secondPanel )
   {
      RectI clientRect = getClientRect();
      Point2I newDragPos = globalToLocalCoord( event.mousePoint );

      solvePanelConstraints(newDragPos, firstPanel, secondPanel, clientRect);
   }
}
