
require ( "usermessage" )
require ( "timer" )

local table 			= table
local pairs 			= pairs
local ipairs 			= ipairs
local concommand 		= concommand
local SERVER 			= SERVER
local CLIENT 			= CLIENT
local saverestore 		= saverestore
local tostring			= tostring
local umsg				= umsg
local Msg				= Msg
local usermessage		= usermessage
local tonumber			= tonumber
local CurTime			= CurTime
local PrintTable		= PrintTable
local timer				= timer


//Client
local GetControlPanel 	= GetControlPanel

module("undo")



// undo.Create("Wheel")
// undo.AddEntity( axis )
// undo.AddEntity( constraint )
// undo.SetPlayer( self.Owner )
// undo.Finish()

if ( CLIENT ) then

	local ClientUndos = {}
	local bIsDirty = true
	
	/*---------------------------------------------------------
		UpdateUI
		Actually updates the UI. Removes old controls and 
		re-creates them using the new data.
	---------------------------------------------------------*/
	local function UpdateUI()

		local Panel = GetControlPanel( "Undo" )
		
		Panel:ClearControls()
		Panel:AddHeader()
		
		local NUM = 18
		local Count = 0
		for k, v in ipairs( ClientUndos ) do
		
			local params = {}
				params.Text = tostring( v.Name )
				params.Command = "gmod_undonum " .. tostring( v.Key )
			Panel:AddControl( "Button", params )
			
			Count = Count + 1
			if ( Count > NUM ) then break end

		end

	end
	
	/*---------------------------------------------------------
		AddUndo
		Called from server. Adds a new undo to our UI
	---------------------------------------------------------*/
	local function AddUndo( message )

		local k 	= message:ReadLong();
		local v 	= message:ReadString();

		// Move all the undos up one to make room
		local NewUndo = {}
		for k, v in ipairs( ClientUndos ) do
			NewUndo [ k + 1 ] = v
		end

		NewUndo[ 1 ] = { Key = k, Name = v }
		
		ClientUndos = NewUndo
		NewUndo = nil
		
		MakeUIDirty()

	end
	
	usermessage.Hook( "AddUndo", AddUndo )
	
	
	/*---------------------------------------------------------
		Undone
		Called from server. Notifies us that one of our undos
		has been undone or made redundant. We act by updating 
		out data (We wait until the UI is viewed until updating)
	---------------------------------------------------------*/
	local function Undone( message )

		local key = message:ReadLong();
		
		local NewUndo = {}
		local i = 1
		for k, v in ipairs( ClientUndos ) do
		
			if ( v.Key != key ) then
				NewUndo [ i ] = v
				i = i + 1
			end
		end
		
		ClientUndos = NewUndo
		NewUndo = nil
		
		MakeUIDirty()

	end
	
	usermessage.Hook( "Undone", Undone )
	
	/*---------------------------------------------------------
		MakeUIDirty
		Makes the UI dirty - it will re-create the controls
		the next time it is viewed. We also take this opportun
	---------------------------------------------------------*/
	function MakeUIDirty()
	
		bIsDirty = true
	
	end
	
	/*---------------------------------------------------------
		CPanelPaint
		Called when the inner panel of the undo CPanel is painted
		We hook onto this to determine when the panel is viewed.
		When it's viewed we update the UI if it's marked as dirty
	---------------------------------------------------------*/
	local function CPanelPaint( panel )
	
		// This is kind of a shitty way of doing it - but we only want
		// to update the UI when it's visible.
		if ( bIsDirty ) then
		
			// Doing this in a timer so it calls it in a sensible place
			// Calling in the paint function could cause all kinds of problems
			// It's a hack but hey welcome to GMod!
			timer.Simple( 0, UpdateUI )
			bIsDirty = false
		
		end
	
		return false
	
	end
	
	/*---------------------------------------------------------
		SetupUI
		Adds a hook (CPanelPaint) to the control panel paint
		function so we can determine when it is being drawn.
	---------------------------------------------------------*/
	function SetupUI()
	
		local UndoPanel = GetControlPanel( "Undo" )
		local InnerPanel = UndoPanel:GetEmbeddedPanel()
		InnerPanel:SetPaintFunction( CPanelPaint )
	
	end

end


if (!SERVER) then return end

local PlayerUndo = {}
// PlayerUndo
//	- Player UniqueID
//		- Undo Table
//			- Name
//			- Entities (table of ents)
//			- Owner (player)

local Current_Undo = nil


local function Save( save )

	saverestore.WriteTable( PlayerUndo, save )

end

local function Restore( restore )

	PlayerUndo = saverestore.ReadTable( restore )

end

saverestore.AddSaveHook( "UndoTable", Save )
saverestore.AddRestoreHook( "UndoTable", Restore )

// Starts an undo
function Create( text )

	Current_Undo = {}
	Current_Undo.Name 			= text
	Current_Undo.Entities 		= {}
	Current_Undo.Owner 			= nil
	
end

// Add entity to undo
function AddEntity( ent )

	if ( Current_Undo == nil ) then return end
	if ( !ent ) then return end
	if ( !ent:IsValid() ) then return end
	
	table.insert( Current_Undo.Entities, ent )
	
end

// Set the player owner od the undo
function SetPlayer( player )

	if (Current_Undo == nil) then return end	
	if ( !player || !player:IsValid() ) then return end
	
	Current_Undo.Owner = player
	
end

/*---------------------------------------------------------
   SendUndoneMessage
   Sends a message to notify the client that one of their 
   undos has been removed. They can then update their GUI.
---------------------------------------------------------*/
local function SendUndoneMessage( ent, id, ply )

	if (!ply || !ply:IsValid()) then return end
	
	// For further optimization we could queue up the ids and send them
	// in one batch ever 0.5 seconds or something along those lines.
	
	umsg.Start( "Undone", ply )
		umsg.Long( id )
	umsg.End()

end

/*---------------------------------------------------------
   Finish
---------------------------------------------------------*/
function Finish( NiceText )

	if (Current_Undo == nil) then return end
	if (Current_Undo.Owner == nil) then return end
	if (!Current_Undo.Owner:IsValid()) then return end
	if ( #Current_Undo.Entities == 0 ) then return end
	
	local index = Current_Undo.Owner:UniqueID()
	PlayerUndo[ index ] = PlayerUndo[ index ] or {}
	
	local id = table.insert( PlayerUndo[ index ], Current_Undo )
	
	NiceText = NiceText or Current_Undo.Name
	
	umsg.Start( "AddUndo", Current_Undo.Owner )
		umsg.Long( id )
		umsg.String( NiceText )
	umsg.End()
	
	// Have one of the entities in the undo tell us when it gets undone.
	if ( Current_Undo.Entities[1] ) then
	
		local ent = Current_Undo.Entities[ 1 ]
		ent:CallOnRemove( "undo"..id, SendUndoneMessage, id, Current_Undo.Owner )
	
	end
	
			
	Current_Undo = nil
	
end


local function Do_Undo( undo )

	if ( !undo ) then return false end
	
	local count = 0
	
	// Remove each entity in this undo
	for index, entity in pairs( undo.Entities ) do
	
		if ( entity:IsValid() ) then
			entity:Remove()
			count = count + 1
		end
		
	end
	
	if (count > 0) then
		// Sends tooltip to client
		undo.Owner:SendLua( "GAMEMODE:OnUndo( '"..undo.Name.."' )" )
	end
	
	return count;
	
end

local function CC_UndoLast( pl, command, args )

	local index = pl:UniqueID()
	PlayerUndo[ index ] = PlayerUndo[ index ] or {}
	
	local last = nil
	local lastk = nil
	
	for k, v in pairs( PlayerUndo[ index ] ) do
	
		lastk = k
		last = v
	
	end
	
	// No undos
	if (!last) then	return end
	
	local count = Do_Undo( last )
	
	umsg.Start( "Undone", pl )
		umsg.Long( lastk )
	umsg.End()
	
	PlayerUndo[ index ][ lastk ] = nil
	
	// If our last undo object is already deleted then compact
	// the undos until we hit one that does something
	if ( count == 0 ) then
		CC_UndoLast( pl )
	end

end

local function CC_UndoNum( pl, command, args )

	if (!args[1]) then return end	
	local index = pl:UniqueID()
	PlayerUndo[ index ] = PlayerUndo[ index ] or {}
	
	local UndoNum = tonumber( args[1] )
	
	// Delete the undo whatever happens
	//umsg.Start( "Undone", pl )
	//	umsg.Long( UndoNum )
	//umsg.End()
	
	if ( !PlayerUndo[ index ][ UndoNum ] ) then return end
	
	// Undo!
	Do_Undo( PlayerUndo[ index ][ UndoNum ] )
	PlayerUndo[ index ][ UndoNum ] = nil

end

concommand.Add("undo",					CC_UndoLast		)
concommand.Add("gmod_undo",				CC_UndoLast		)
concommand.Add("gmod_undonum",			CC_UndoNum		)

	
