
IncludeClientFile( "vgui/duplicator_gui.lua" )

AddCSLuaFile( "vgui/duplicator_gui_button.lua" )

TOOL.Category		= "Construction"
TOOL.Name			= "#Duplicator"
TOOL.Command		= nil
TOOL.ConfigName		= ""
TOOL.Stored			= true

TOOL.ClientConVar[ "simple" ] = 0

cleanup.Register( "duplicates" )

//
// Converts to world so that the entities will be spawned in the correct positions
//
local function ConvertEntityPositionsToWorld( EntTable, Offset, HoldAngle )

	for k, Ent in pairs( EntTable ) do

		local NewPos, NewAngle = LocalToWorld( Ent.LocalPos, Ent.LocalAngle, Offset, HoldAngle )
		
		Ent.Pos = NewPos
		Ent.Angle = NewAngle
		
		// And for physics objects
		if ( Ent.PhysicsObjects ) then
			for Num, Object in pairs( Ent.PhysicsObjects ) do
	
				local NewPos, NewAngle = LocalToWorld( Object.LocalPos, Object.LocalAngle, Offset, HoldAngle )
			
				Object.Pos = NewPos
				Object.Angle = NewAngle
	
			end
		end
		
		
	end

end

//
// Move the world positions
//
local function ConvertConstraintPositionsToWorld( Constraints, Offset, HoldAngle )

	for k, Constraint in pairs( Constraints ) do
	
		if ( Constraint.Entity ) then
		
			for k, Entity in pairs( Constraint.Entity ) do
			
				if (Entity.World && Entity.LPos) then
				
					local NewPos, NewAngle = LocalToWorld( Entity.LPos, Angle(0,0,0), Offset, HoldAngle )
				
					Entity.LPosOld = Entity.LPos
					Entity.LPos = NewPos
				
				end
			
			end
		
		end
	
	end

end


//
// Resets the positions of all the entities in the table
//
local function ResetPositions( EntTable, Constraints )

	for k, Ent in pairs( EntTable ) do
	
		Ent.Pos = Ent.LocalPos * 1
		Ent.Angle = Ent.LocalAngle * 1
		
		// And for physics objects
		if ( Ent.PhysicsObjects ) then		
			for Num, Object in pairs( Ent.PhysicsObjects ) do
	
				Object.Pos = Object.LocalPos * 1
				Object.Angle = Object.LocalAngle * 1
	
			end
		end
		
	end
	
	for k, Constraint in pairs( Constraints ) do
	
		if ( Constraint.Entity ) then
		
			for k, Entity in pairs( Constraint.Entity ) do
			
				if (Entity.LPosOld) then
					Entity.LPos = Entity.LPosOld
					Entity.LPosOld = nil
				end
			
			end
		
		end
	
	end

end

//
// Converts the positions from world positions to positions local to Offset
//
local function ConvertPositionsToLocal( EntTable, Constraints, Offset, HoldAngle )

	for k, Ent in pairs( EntTable ) do
	
		Ent.Pos = Ent.Pos - Offset
		Ent.LocalPos = Ent.Pos * 1
		Ent.LocalAngle = Ent.Angle * 1
		
		if ( Ent.PhysicsObjects ) then
			for Num, Object in pairs(Ent.PhysicsObjects) do
			
				Object.Pos = Object.Pos - Offset
				Object.LocalPos = Object.Pos * 1
				Object.LocalAngle = Object.Angle * 1
				
			end
		end

	end
	
	// If the entity is constrained to the world we want to move the points to be
	// relative to where we're clicking
	for k, Constraint in pairs( Constraints ) do
	
		if ( Constraint.Entity ) then
		
			for k, Entity in pairs( Constraint.Entity ) do
			
				if (Entity.World && Entity.LPos) then
					Entity.LPos = Entity.LPos - Offset
				end
			
			end
		
		end
	
	end

end

//
// PASTE
//
function TOOL:LeftClick( trace )

	if ( CLIENT ) then	return true	end
	
	local DupeTable = self:GetOwner():UniqueIDTable( "Duplicator" )
	if (!DupeTable.Entities) then return false end
	
	local angle  = self:GetOwner():GetAngles()
	angle.pitch = 0
	angle.roll = 0	
	
	// Create the entities at the clicked position at the angle we're facing right now	
	ConvertEntityPositionsToWorld( DupeTable.Entities, trace.HitPos, angle - DupeTable.HoldAngle )
	ConvertConstraintPositionsToWorld( DupeTable.Constraints, trace.HitPos, angle - DupeTable.HoldAngle )
	
		local Ents, Constraints = duplicator.Paste( self:GetOwner(), DupeTable.Entities, DupeTable.Constraints )
	
	ResetPositions( DupeTable.Entities, DupeTable.Constraints )
		

	// Add all of the created entities
	//  to the undo system under one undo.
	undo.Create( "Duplicator" )
	
		for k, ent in pairs( Ents ) do
			undo.AddEntity( ent )
		end
		
		for k, ent in pairs( Ents )	do 
			self:GetOwner():AddCleanup( "duplicates", ent ) 
		end
		
		undo.SetPlayer( self:GetOwner() )
		
	undo.Finish()

	return true
	
end

//
// Copy
//
function TOOL:RightClick( trace )

	if (!trace.Entity ||
		!trace.Entity:IsValid() ||
		trace.Entity:IsPlayer() )
	then
	
		self:ReleaseGhostEntity()
	
		local DupeTable = self:GetOwner():UniqueIDTable( "Duplicator" )
		
		DupeTable.HeadEntityIdx	= nil
		DupeTable.HoldAngle 	= nil
		DupeTable.HoldPos 		= nil
		DupeTable.Entities		= nil
		DupeTable.Constraints	= nil
		self.Stored 			= true
		
		return true
	
	end

	local StartPos = trace.HitPos

	self:ReleaseGhostEntity()

	if ( CLIENT ) then return true end
	
	// Get the distance from the floor
	local tr = {}
	tr.start = StartPos
	tr.endpos = StartPos + Vector(0,0,-1024)
	tr.mask = MASK_NPCSOLID_BRUSHONLY
	local tr_floor = util.TraceLine( tr )
	if (tr_floor.Hit) then 

		StartPos = StartPos  + Vector(0,0,-1) * tr_floor.Fraction * 1024
	
	end
	
	// Copy the entities
	local Entities, Constraints = duplicator.Copy( trace.Entity )
	
	local angle  = self:GetOwner():GetAngles()
	angle.pitch = 0
	angle.roll = 0
	
	// Convert the positions to local
	ConvertPositionsToLocal( Entities, Constraints, StartPos, angle )

	
	// Store stuff for pasting/ghosting
	// Save to a UniqueID table so the object will exist after the player dies/leaves the server
	local DupeTable = self:GetOwner():UniqueIDTable( "Duplicator" )
	
	DupeTable.HeadEntityIdx	= trace.Entity:EntIndex()
	DupeTable.HoldAngle 	= angle
	DupeTable.HoldPos 		= trace.Entity:WorldToLocal( StartPos )
	DupeTable.Entities		= Entities
	DupeTable.Constraints	= Constraints
	
	self:StartGhostEntities( DupeTable.Entities, DupeTable.HeadEntityIdx, DupeTable.HoldPos, DupeTable.HoldAngle )
	
	self.Stored = false
	
	return true
	
end

//
// Think
//
function TOOL:Think()

	self:UpdateGhostEntities()
	
end

//
// Make the ghost entities
//
function TOOL:MakeGhostFromTable( EntTable, pParent, HoldPos, HoldAngle )

	local GhostEntity = nil
	
	if ( EntTable.Model:sub( 1, 1 ) == "*" ) then
		GhostEntity = ents.Create( "func_physbox" )
	else
		GhostEntity = ents.Create( "gmod_ghost" )
	end
	
	// If there are too many entities we might not spawn..
	if ( !GhostEntity || GhostEntity == NULL ) then return end
	
	GhostEntity:SetModel( EntTable.Model )
	
	GhostEntity:SetPos( EntTable.Pos )
	GhostEntity:SetAngles( EntTable.Angle )
	GhostEntity:Spawn()
	
	GhostEntity:DrawShadow( false )
	GhostEntity:SetMoveType( MOVETYPE_NONE )
	GhostEntity:SetSolid( SOLID_VPHYSICS );
	GhostEntity:SetNotSolid( true )
	GhostEntity:SetRenderMode( RENDERMODE_TRANSALPHA )
	GhostEntity:SetColor( 255, 255, 255, 150 )
	
	GhostEntity.Pos 	= EntTable.Pos
	GhostEntity.Angle 	= EntTable.Angle - HoldAngle
	
	if ( pParent ) then
		GhostEntity:SetParent( pParent )
	end
	
	// If we're a ragdoll send our bone positions
	if ( EntTable.Class == "prop_ragdoll" ) then

		for k, v in pairs( EntTable.PhysicsObjects ) do
			
			local lPos = v.Pos
			
			// The physics object positions are stored relative to the head entity
			if ( pParent ) then
				lPos = pParent:LocalToWorld( v.LocalPos )
				lPos = GhostEntity:WorldToLocal( v.Pos )
			else
				lPos = lPos + HoldPos
			end
			
			GhostEntity:SetNetworkedBonePosition( k, lPos, v.LocalAngle )
		end	
	
	end
	
	return GhostEntity
	
end


/*---------------------------------------------------------
   Starts up the ghost entities
---------------------------------------------------------*/
function TOOL:Deploy()

	if ( CLIENT ) then return end
	
	local DupeTable = self:GetOwner():UniqueIDTable( "Duplicator" )
	
	if ( DupeTable.Entities ) then
		self:StartGhostEntities( DupeTable.Entities, DupeTable.HeadEntityIdx, DupeTable.HoldPos, DupeTable.HoldAngle )
	end

end


/*---------------------------------------------------------
   Starts up the ghost entities
---------------------------------------------------------*/
function TOOL:StartGhostEntities( EntityTable, Head, HoldPos, HoldAngle )

	self:ReleaseGhostEntity()
	self.GhostEntities = {}
	
	// Make the head entity first
	self.GhostEntities[ Head ] = self:MakeGhostFromTable( EntityTable[ Head ], self.GhostEntities[ Head ], HoldPos, HoldAngle )
	
	// Set NW vars for clientside
	self.Weapon:SetNetworkedEntity( "GhostEntity", self.GhostEntities[ Head ] )
	self.Weapon:SetNetworkedVector( "HeadPos", self.GhostEntities[ Head ].Pos )
	self.Weapon:SetNetworkedAngle( 	"HeadAngle", self.GhostEntities[ Head ].Angle )	
	self.Weapon:SetNetworkedVector( "HoldPos", HoldPos )
	
	if ( !self.GhostEntities[ Head ] || !self.GhostEntities[ Head ]:IsValid() ) then
	
		self.GhostEntities = nil
		return
	
	end
	
	for k, entTable in pairs( EntityTable ) do
		
		if ( !self.GhostEntities[ k ] ) then
			self.GhostEntities[ k ] = self:MakeGhostFromTable( entTable, self.GhostEntities[ Head ], HoldPos, HoldAngle )
		end
	
	end

end

/*---------------------------------------------------------
   Update the ghost entity positions
---------------------------------------------------------*/
function TOOL:UpdateGhostEntities()

	if (SERVER && !self.GhostEntities) then return end
	
	local Owner = self:GetOwner()

	local tr = utilx.GetPlayerTrace( Owner, Owner:GetCursorAimVector() )
	local trace = util.TraceLine( tr )
	if (!trace.Hit) then return end

	local GhostEnt = nil
	local HoldPos = nil
	
	if ( SERVER ) then
		local DupeTable = Owner:UniqueIDTable( "Duplicator" )
		GhostEnt = self.GhostEntities[ DupeTable.HeadEntityIdx ]
		HoldPos = DupeTable.HoldPos
	else
		GhostEnt = self.Weapon:GetNetworkedEntity( "GhostEntity", nil )
		GhostEnt.Pos = self.Weapon:GetNetworkedVector( "HeadPos", Vector(0,0,0) )
		GhostEnt.Angle = self.Weapon:GetNetworkedAngle( "HeadAngle", Angle(0,0,0) )		
		HoldPos = self.Weapon:GetNetworkedVector( "HoldPos", Vector(0,0,0) )
	end
	
	if (!GhostEnt || !GhostEnt:IsValid()) then 
		self.GhostEntities = nil
	return end

	GhostEnt:SetMoveType( MOVETYPE_VPHYSICS )
	GhostEnt:SetNotSolid( true )
	
	local angle  = self:GetOwner():GetAngles()
	angle.pitch = 0
	angle.roll = 0
	
	local TargetPos = GhostEnt:GetPos() - GhostEnt:LocalToWorld( HoldPos )

	local PhysObj = GhostEnt:GetPhysicsObject()
	if ( PhysObj && PhysObj:IsValid() ) then
	
		PhysObj:EnableMotion( false )
		PhysObj:SetPos( TargetPos + trace.HitPos )
		PhysObj:SetAngle( GhostEnt.Angle + angle )
		PhysObj:Wake()
		
	else
	
		// Give the head ghost entity a physics object
		// This way the movement will be predicted on the client
		if ( CLIENT ) then
			GhostEnt:PhysicsInit( SOLID_VPHYSICS )
		end
	
	end
		
end



if (SERVER) then

	local function CC_Store( pl, command, arguments )
	
		local tool = pl:GetTool( "duplicator" )
		if (!tool) then Msg("No Tool!\n") return end
		tool:StoreCurrent( arguments[1] )
	
	end
	concommand.Add( "tool_duplicator_store", CC_Store )
	
	local function CC_Select( pl, command, arguments )
	
		local tool = pl:GetTool( "duplicator" )
		if (!tool) then return end
		
		tool:Select( tonumber(arguments[1]) )
	
	end
	concommand.Add( "tool_duplicator_select", CC_Select )
	
	
	local function CC_Remove( pl, command, arguments )
	
		local tool = pl:GetTool( "duplicator" )
		if (!tool) then return end
		
		tool:Remove( tonumber(arguments[1]) )
	
	end
	concommand.Add( "tool_duplicator_remove", CC_Remove )
	
	
	/*---------------------------------------------------------
	   Store the current duplication under name 
	---------------------------------------------------------*/
	function TOOL:StoreCurrent( name )
	
		// Don't store the same copy more than once
		if ( self.Stored ) then return end
		self.Stored = true
	
		local Owner = self:GetOwner()
		local DupeTable = Owner:UniqueIDTable( "Duplicator" )
		
		if ( name == nil || name == "" ) then
			name = "Rubbish #" .. ( #Owner:UniqueIDTable( "StoredDuplications" ) + 1 )
		end
			
		local SaveTab = {}
		SaveTab.Name 			= name
		SaveTab.Entities 		= DupeTable.Entities
		SaveTab.Constraints 	= DupeTable.Constraints
		SaveTab.HeadEntityIdx 	= DupeTable.HeadEntityIdx
		SaveTab.HoldAngle	 	= DupeTable.HoldAngle * 1
		SaveTab.HoldPos		 	= DupeTable.HoldPos * 1
		
		table.insert( Owner:UniqueIDTable( "StoredDuplications" ), SaveTab )
		
		self:RebuildStoredDuplications()
	
	end
	
	/*---------------------------------------------------------
		Select stored duplication
	---------------------------------------------------------*/
	function TOOL:Select( id )
	
		local Owner = self:GetOwner()
		local DupeTable = Owner:UniqueIDTable( "Duplicator" )
		local Dupe = Owner:UniqueIDTable( "StoredDuplications" )[ id ]
		if (!Dupe) then return end
		
		// Release the old entities
		self:ReleaseGhostEntity()
	
		// Store stuff for pasting/ghosting
		DupeTable.HeadEntityIdx	= Dupe.HeadEntityIdx
		DupeTable.HoldAngle 		= Dupe.HoldAngle * 1
		DupeTable.HoldPos 		= Dupe.HoldPos * 1
		DupeTable.Entities		= Dupe.Entities
		DupeTable.Constraints	= Dupe.Constraints
		
		// Start the entities!
		self:StartGhostEntities( DupeTable.Entities, DupeTable.HeadEntityIdx, DupeTable.HoldPos, DupeTable.HoldAngle )
	
	end
	
	/*---------------------------------------------------------
		Select stored duplication
	---------------------------------------------------------*/
	function TOOL:Remove( id )
	
		local Owner = self:GetOwner()
		if ( Owner:UniqueIDTable( "StoredDuplications" )[ id ] == nil ) then return end
		
		Owner:UniqueIDTable( "StoredDuplications" )[ id ] = nil
		self:RebuildStoredDuplications()
	
	end

	/*---------------------------------------------------------
	   Send the stored duplication to user
	---------------------------------------------------------*/
	function TOOL:RebuildStoredDuplications()
	
		local Owner = self:GetOwner()
		local Saved = Owner:UniqueIDTable( "StoredDuplications" )
		if (!Saved) then return end
		
		// Clear the list
		umsg.Start( "Duplications", Owner )
			umsg.Short( 0 )		
		umsg.End()
		
		// Send each item to the client
		for k, v in pairs( Saved ) do
			umsg.Start( "Duplications", Owner )
				umsg.Short( k )
				umsg.String( v.Name )			
			umsg.End()
		end
		
		umsg.Start( "Duplications", Owner )
			umsg.Short( 1000 )		
		umsg.End()
	
	end



end



if (CLIENT) then

	
	local DuplicatorControl = nil
	
	/*---------------------------------------------------------
		Builds the context menu
	---------------------------------------------------------*/
	function TOOL.BuildCPanel( CPanel )
	
		CPanel:AddControl( "Header", { Text = "#Tool_duplicator_name", Description	= "#Tool_duplicator_desc" }  )
		
		DuplicatorControl = vgui.Create( "DuplicatorControl" )
		CPanel:AddPanel( DuplicatorControl )
	
	end
	

	/*---------------------------------------------------------
		Receives the usermessages
	---------------------------------------------------------*/
	local function RecvFunc( m )
	
		local id = m:ReadShort()
		
		if ( id == 0 ) then
			DuplicatorControl:Clear()
			return
		end
		
		if ( id == 1000 ) then
			DuplicatorControl:Populate()
			return
		end
		
		DuplicatorControl:Add( id, m:ReadString() )
		
	end
	
	usermessage.Hook( "Duplications", RecvFunc )


end
