if SERVER then return end

--[[

 -- hairy cocks

--]]

dragndrop = {}

function dragndrop.Clear()

	dragndrop.m_Receiver		= nil
	dragndrop.m_ReceiverSlot	= nil
	dragndrop.m_HoverStart		= nil
	dragndrop.m_MouseCode		= 0;
	dragndrop.m_DragWatch		= nil;
	dragndrop.m_MouseX			= 0;
	dragndrop.m_MouseY			= 0;
	dragndrop.m_DraggingMain	= nil
	dragndrop.m_Dragging		= nil;
	dragndrop.m_DropMenu		= nil;
	
end

function dragndrop.IsDragging()

	if ( dragndrop.m_Dragging != nil ) then return true end

	return false;

end

function dragndrop.HandleDroppedInGame()

	local panel = vgui.GetHoveredPanel()
	if ( !IsValid( panel ) ) then return end
	if ( panel:GetClassName() != "CGModBase" ) then return end
	
end

function dragndrop.Drop()

	if ( dragndrop.HandleDroppedInGame() ) then
		dragndrop.StopDragging()
		return 
	end
	
	-- Show the menu
	if ( dragndrop.m_MouseCode == MOUSE_RIGHT && dragndrop.m_ReceiverSlot && dragndrop.m_ReceiverSlot.Menu ) then

		local x, y = dragndrop.m_Receiver:LocalCursorPos();
		
		local menu = DermaMenu()
		
		for k, v in pairs( dragndrop.m_ReceiverSlot.Menu ) do
		
			menu:AddOption( v, function()
			
					dragndrop.CallReceiverFunction( true, k, x, y )
					dragndrop.StopDragging()
			
				end );
		
		end
				
		menu:Open()
		dragndrop.m_DropMenu = menu
		return
	
	end
	
	dragndrop.CallReceiverFunction( true, nil, nil, nil )
	dragndrop.StopDragging()

end

function dragndrop.StartDragging()
	
	if ( !dragndrop.m_DragWatch:IsSelected() ) then
		
		dragndrop.m_Dragging = { dragndrop.m_DragWatch } 
		
	else
			
		local canvas = dragndrop.m_DragWatch:GetSelectionCanvas()
		dragndrop.m_Dragging = {}
		
		for k, v in pairs( canvas:GetSelectedChildren() ) do
		
			if ( !v.m_DragSlot ) then continue end
						
			table.insert( dragndrop.m_Dragging, v )
		
		end
				
	end

	for k, v in pairs( dragndrop.m_Dragging ) do

		if ( !IsValid( v ) ) then continue end 
		
		v:OnStartDragging()

	end
	
	dragndrop.m_DraggingMain = dragndrop.m_DragWatch;
	dragndrop.m_DraggingMain:MouseCapture( true )
	dragndrop.m_DragWatch = nil	

end

function dragndrop.StopDragging()

	if ( IsValid(dragndrop.m_Receiver) ) then
		dragndrop.m_Receiver:DragHoverEnd()
		dragndrop.m_Receiver = nil
	end
	
	dragndrop.Clear()
	
end

function dragndrop.UpdateReceiver()

	local hovered = vgui.GetHoveredPanel()
	local receiver = nil
	local receiverslot = nil
	
	if ( IsValid( hovered ) ) then
		receiver, receiverslot = hovered:GetValidReceiverSlot()
	end

	if ( IsValid( dragndrop.m_Receiver ) ) then
	
		if ( receiver == dragndrop.m_Receiver ) then return end
		
		dragndrop.m_Receiver:DragHoverEnd()
		
	end
	
	if ( !IsValid( receiver ) ) then 
		dragndrop.m_Receiver		= nil
		dragndrop.m_ReceiverSlot	= nil
	end
	
	dragndrop.m_Receiver		= receiver
	dragndrop.m_ReceiverSlot	= receiverslot

end

--
-- Return all the dragged panels that match this name
--
function dragndrop.GetDroppable( name )

	-- TODO - filter by receiver name
	
	return dragndrop.m_Dragging

end

function dragndrop.CallReceiverFunction( bDoDrop, command, mx, my )

	if ( !dragndrop.m_ReceiverSlot ) then return end
	if ( !IsValid( dragndrop.m_Receiver ) ) then return end
	
	local x, y = dragndrop.m_Receiver:LocalCursorPos();
	if ( mx ) then x = mx end
	if ( my ) then y = my end

	if ( dragndrop.m_ReceiverSlot.Func ) then
	
		local droppable = dragndrop.GetDroppable( dragndrop.m_ReceiverSlot.Name );
		
		dragndrop.m_ReceiverSlot.Func( dragndrop.m_Receiver, droppable, bDoDrop, command, x, y )
		
	end

end

function dragndrop.Think()

	if ( IsValid( dragndrop.m_DropMenu ) ) then return end
	
	--
	-- We're dragging but no mouse buttons are down.. 
	-- So force the drop whereever it is!
	--
	if ( dragndrop.m_Dragging != nil && !input.IsMouseDown( MOUSE_LEFT ) && !input.IsMouseDown( MOUSE_RIGHT ) ) then
		 --dragndrop.m_Dragging:DragMouseRelease( dragndrop.m_MouseCode );
		-- return
	end

	--
	-- We're holding down a panel, watch for start of dragging
	--
	if ( IsValid( dragndrop.m_DragWatch ) ) then
	
		local dist = math.abs( dragndrop.m_MouseX - gui.MouseX() ) + math.abs( dragndrop.m_MouseY - gui.MouseY() );
		if ( dist > 20 ) then
			dragndrop.StartDragging()
			return
		end
	
	end

	if ( dragndrop.m_Dragging != nil ) then
	
		dragndrop.HoverThink()
		dragndrop.UpdateReceiver()

		if ( IsValid( dragndrop.m_Receiver ) ) then
			dragndrop.CallReceiverFunction( false )		
		end
		
	end	

end

hook.Add( "DrawOverlay", "DragNDropPaint", function()
	
	if ( dragndrop.m_Dragging == nil ) then return end
	if ( dragndrop.m_DraggingMain == nil ) then return end
	if ( IsValid( dragndrop.m_DropMenu ) ) then return end

	local hold_offset_x = 2048;
	local hold_offset_y = 2048;

	-- Find the top, left most panel
	for k, v in pairs( dragndrop.m_Dragging ) do

		if ( !IsValid( v ) ) then continue end 

		hold_offset_x = math.min( hold_offset_x, v.x )
		hold_offset_y = math.min( hold_offset_y, v.y )
			
	end

	DisableClipping( true )
	
		local Alpha = 0.7;
		if ( IsValid( dragndrop.m_Hovered ) ) then Alpha = 0.8 end
		surface.SetAlphaMultiplier( Alpha );
				
			local ox = gui.MouseX() - hold_offset_x + 8
			local oy = gui.MouseY() - hold_offset_y + 8
		
			for k, v in pairs( dragndrop.m_Dragging ) do
					
				if ( !IsValid( v ) ) then continue end 

				local dist = 512 - v:Distance( dragndrop.m_DraggingMain )
				
				if ( dist < 0 ) then continue end
				
				dist = dist / 512
				surface.SetAlphaMultiplier( Alpha * dist );
				
				v:PaintAt( ox + v.x - v:GetWide() / 2, oy + v.y - v:GetTall() / 2 ); // fill the gap between the top left corner and the mouse position
			
			end
			
		surface.SetAlphaMultiplier( 1.0 );

	DisableClipping( false )

end )

hook.Add( "Think", "DragNDropThink", dragndrop.Think )





--
--
--
-- Panel Drag n Drop Extensions
--
--
--


local meta = FindMetaTable( "Panel" )

--
-- Make this panel droppable
--
function meta:Droppable( name )

	self.m_DragSlot = self.m_DragSlot or {}
	
	self.m_DragSlot[ name ] = {}
	
	return self.m_DragSlot[ name ];

end

--
-- Make this pannel a drop target
--
function meta:Receiver( name, func, menu )

	self.m_ReceiverSlot = self.m_ReceiverSlot or {}
	
	self.m_ReceiverSlot[ name ] = {}
	self.m_ReceiverSlot[ name ].Name = name
	self.m_ReceiverSlot[ name ].Func = func
	self.m_ReceiverSlot[ name ].Menu = menu

end

--
-- Drag parent means that when we start to
-- drag this panel, we'll really start 
-- dragging the defined parent
--
function meta:SetDragParent( parent )
	self.m_pDragParent = parent;
end



function meta:GetValidReceiverSlot()

	if ( self.m_ReceiverSlot ) then
	
		-- Find matching slot..
		for k, v in pairs( self.m_ReceiverSlot ) do
		
			if ( !dragndrop.m_DraggingMain.m_DragSlot ) then continue end

			local slot = dragndrop.m_DraggingMain.m_DragSlot[ k ]
			if ( !slot ) then continue end
			
			return self, v
		
		end
		
	end
	
	if ( !IsValid( self:GetParent() ) ) then
		return false;
	end
	
	return self:GetParent():GetValidReceiverSlot()

end

function meta:IsDraggable()

	return self.m_DragSlot != nil;

end

function meta:IsDragging()

	if ( !self.m_DragSlot ) then return false end

	return self.m_Dragging

end

function meta:OnStartDragging()

	self.Dragging = true	
	self:InvalidateLayout()
	
	if ( self:IsSelectable() ) then
	
		local canvas = self:GetSelectionCanvas()
	
		if ( !self:IsSelected() ) then		
			canvas:UnselectAll()
		end
	
	end
	
end

function meta:OnStopDragging()
	self.Dragging = false	
end

function meta:DragMousePress( mcode )

	if ( IsValid( dragndrop.m_DropMenu ) ) then return end
	if ( dragndrop.IsDragging() ) then return end
	
	if ( IsValid( self.m_pDragParent ) ) then
		return self.m_pDragParent:DragMousePress( mcode );
	end

	if ( !self.m_DragSlot ) then return end

	dragndrop.Clear()
	dragndrop.m_MouseCode = mcode;
	dragndrop.m_DragWatch = self;
	dragndrop.m_MouseX = gui.MouseX();
	dragndrop.m_MouseY = gui.MouseY();

end

function meta:DragClick( mcode )

	self:MouseCapture( true )
	-- Clicking one mouse button while dragging with another!
	-- Return true to stop us clicking and selecting stuff below..
	return true

end

function meta:DragMouseRelease( mcode )

	if ( IsValid( dragndrop.m_DropMenu ) ) then return end
	
	-- This wasn't the button we clicked with - so don't release drag
	if ( dragndrop.IsDragging() && dragndrop.m_MouseCode != mcode ) then
	
		return self:DragClick( mcode )
		
	end
	
	if ( !dragndrop.IsDragging() ) then
		dragndrop.Clear()
		return false 
	end

	for k, v in pairs( dragndrop.m_Dragging ) do

		if ( !IsValid( v ) ) then continue end 
		v:OnStopDragging()

	end	

	dragndrop.Drop();
	
	-- Todo.. we should only do this if we enabled it!
	if ( gui.EnableScreenClicker ) then 
		gui.EnableScreenClicker( false ) 
	end
	
	self:MouseCapture( false )	
	return true;

end

function meta:DrawDragHover( x, y, w, h )

	DisableClipping( true )

	surface.SetDrawColor( 255, 0, 255, 100 )
	surface.DrawRect( x, y, w, h )
	
	surface.SetDrawColor( 255, 220, 255, 230 )
	surface.DrawOutlinedRect( x, y, w, h )

	surface.SetDrawColor( 255, 100, 255, 50 )
	surface.DrawOutlinedRect( x-1, y-1, w+2, h+2 )
	
	DisableClipping( false )
	
end

function meta:SetDropTarget( x, y, w, h )
		
	if ( !self.m_bDrawingPaintOver ) then
		self.m_OldPaintOver = self.PaintOver
		self.m_bDrawingPaintOver = true;
	end
	
	self.PaintOver = function()
	
		if ( self.m_OldPaintOver ) then
			self:m_OldPaintOver()
		end

		self:DrawDragHover( x, y, w, h )
	
	end
	
end

function meta:DragHoverEnd()

	if ( !self.m_bDrawingPaintOver ) then return end

	self.PaintOver = self.m_OldPaintOver
	self.m_bDrawingPaintOver = false;
	
end




--
-- Drag Hover
--
-- These functions are used for things like trees
-- So that when you hover over the tree while dragging something
-- it will open up the tree. This works regardless of whether the 
-- is droppable or not.
--
-- Implement DragHoverClick in your panel class to get this functionality
--
function meta:DragHover( HoverTime )

	//
	// Call DragHoverClick if we've been hovering for 0.5 seconds..
	//
	if ( HoverTime < 0.1 ) then dragndrop.m_bHoverClick = false end
	if ( HoverTime > 0.1 && !dragndrop.m_bHoverClick ) then
	
		self:DragHoverClick( HoverTime );
		dragndrop.m_bHoverClick = true;
	
	end	

end

function meta:DragHoverClick( HoverTime )

end

function meta:DroppedOn( pnl )
	-- Fpr override.
end

function meta:OnDrop()
	
	-- We're being dropped on something
	-- we can create a new panel here and return it, so that instead of
	-- dropping us - it drops the new panel instead! We remain where we are!
	
	-- By default we return ourself
	
	return self
	
	
end


--
--
--
-- This is called to open stuff when you're hovering over it.
--
--

local LastHoverThink = nil
local LastHoverChangeTime = 0
local LastX = 0;
local LastY = 0;

function dragndrop.HoverThink()

	local hovered = vgui.GetHoveredPanel()
	local x = gui.MouseX();
	local y = gui.MouseY();

	-- Hovering a different panel
	if ( LastHoverThink != hovered || x != LastX || y != LastY ) then
		
		LastHoverChangeTime = SysTime();
		LastHoverThink		= hovered
		
	end
	
	-- Hovered panel might do stuff when we're hovering it
	-- so give it a chance to do that now.
	if ( IsValid( LastHoverThink ) ) then

		LastX = x;
		LastY = y;
		LastHoverThink:DragHover( SysTime() - LastHoverChangeTime )
	
	end

end
