


/*---------------------------------------------------------
   Duplicator module, 
   to add new constraints or entity classes use...
   
   duplicator.RegisterConstraint( "name", funct, ... )
   duplicator.RegisterEntityClass( "class", funct, ... )
   
---------------------------------------------------------*/

module( "duplicator", package.seeall )

ConstraintType 	= ConstraintType or {} 
	
/*---------------------------------------------------------
   Register a constraint to be duplicated
---------------------------------------------------------*/
function RegisterConstraint( _name_ , _function_, ... )	

	ConstraintType[ _name_ ] 	= {}
	
	ConstraintType[ _name_ ].Func = _function_;
	ConstraintType[ _name_ ].Args = {...}
	
end


EntityClasses	= EntityClasses or {}

/*---------------------------------------------------------
   Register an entity's class, to allow it to be duplicated
---------------------------------------------------------*/
function RegisterEntityClass( _name_ , _function_, ... )

	EntityClasses[ _name_ ] 		= {}
	
	EntityClasses[ _name_ ].Func 	= _function_
	EntityClasses[ _name_ ].Args 	= {...}
	
end

/*---------------------------------------------------------
   Returns an entity class factory
---------------------------------------------------------*/
function FindEntityClass( _name_ )

	if ( !_name_ ) then return end
	return EntityClasses[ _name_ ]

end

/*---------------------------------------------------------

---------------------------------------------------------*/
BoneModifiers 				= BoneModifiers or {}
EntityModifiers				= EntityModifiers or {}

function RegisterBoneModifier( _name_, _function_ )		BoneModifiers[ _name_ ] 			= _function_ end
function RegisterEntityModifier( _name_, _function_ )	EntityModifiers[ _name_ ] 			= _function_ end

if (!SERVER) then return end

/*---------------------------------------------------------
   Applies generic every-day entity stuff for ent from table data.
---------------------------------------------------------*/
function DoGeneric( ent, data )

	if ( !data ) then return end
	if ( data.Model ) then ent:SetModel( data.Model ) end
	if ( data.Angle ) then ent:SetAngles( data.Angle ) end
	if ( data.Pos ) then ent:SetPos( data.Pos ) end

end


/*---------------------------------------------------------
   Applies bone data, generically. 
---------------------------------------------------------*/
function DoGenericPhysics( Entity, data )

	if (!data) then return end
	if (!data.PhysicsObjects) then return end
	
	for Bone, Args in pairs( data.PhysicsObjects ) do
	
		local Phys = Entity:GetPhysicsObjectNum(Bone)
		
		if ( Phys && Phys:IsValid() ) then	

			Phys:SetPos( Args.Pos )
			Phys:SetAngle( Args.Angle )

			if ( Args.Frozen == true ) then Phys:EnableMotion( false ) end
							
		end
		
	end

end

/*---------------------------------------------------------
   Restore's the face data
---------------------------------------------------------*/
function DoFlex( ent, Flex, Scale )

	if (!Flex) then return end
	if (!ent) then return end

	for k, v in pairs( Flex ) do
		ent:SetFlexWeight( k, v )
	end
	
	if ( Scale ) then
		ent:SetFlexScale( Scale )
	end

end

function GenericDuplicatorFunction( Player, data )

	local Entity = ents.Create( data.Class )
		Entity:SetModel( data.Model )
	Entity:Spawn()

	DoGeneric( Entity, data )
	
	Entity:Activate()
	
	table.Add( Entity:GetTable(), data )
	
	return Entity
	
end

/*---------------------------------------------------------
	Returns a copy of the passed entity's table
---------------------------------------------------------*/
local function CopyEntTable( Ent )

	local Tab = {}
	table.Merge( Tab, Ent:GetTable() )
	
	Tab.Pos = Ent:GetPos()
	Tab.Angle = Ent:GetAngles()
	Tab.Class = Ent:GetClass()
	Tab.Model = Ent:GetModel()
	
	Tab.PhysicsObjects = Tab.PhysicsObjects or {}
	
	// Physics Objects
	local iNumPhysObjects = Ent:GetPhysicsObjectCount()
	for Bone = 0, iNumPhysObjects-1 do 
	
		local PhysObj = Ent:GetPhysicsObjectNum( Bone )
		if ( PhysObj:IsValid() ) then
		
			Tab.PhysicsObjects[ Bone ] = Tab.PhysicsObjects[ Bone ] or {}
			Tab.PhysicsObjects[ Bone ].Pos = PhysObj:GetPos()
			Tab.PhysicsObjects[ Bone ].Angle = PhysObj:GetAngle()
			Tab.PhysicsObjects[ Bone ].Frozen = !PhysObj:IsMoveable()
			
		end
	
	end
	
	// Flexes
	local FlexNum = Ent:GetFlexNum()
	for i = 0, FlexNum do
	
		Tab.Flex = Tab.Flex or {}
		Tab.Flex[ i ] = Ent:GetFlexWeight( i )
	
	end
	
	Tab.FlexScale = Ent:GetFlexScale()

	return Tab

end

/*---------------------------------------------------------
   Copy this entity, and all of its constraints and entities 
   and put them in a table.
---------------------------------------------------------*/
function Copy( Ent )

	local Ents = {}
	local Constraints = {}
	
	GetAllConstrainedEntitiesAndConstraints( Ent, Ents, Constraints )
	
	local EntTables = {}
	for k, v in pairs(Ents) do
		EntTables[ k ] = CopyEntTable( v )
	end
	
	local ConstraintTables = {}
	for k, v in pairs(Constraints) do
		table.insert( ConstraintTables, v )
	end
	
	return EntTables, ConstraintTables

end

/*---------------------------------------------------------
   Create an entity from a table.
---------------------------------------------------------*/
function CreateEntityFromTable( Player, EntTable )

	local EntityClass = FindEntityClass( EntTable.Class )
	
	// This class is unregistered. Instead of failing try using a generic
	// Duplication function to make a new copy..
	if (!EntityClass) then
	
		return GenericDuplicatorFunction( Player, EntTable )
	
	end
		
	// Build the argument list
	local ArgList = {}
		
	for iNumber, Key in pairs( EntityClass.Args ) do

		local Arg = nil
	
		// Translate keys from old system
		if ( Key == "pos" || Key == "position" ) then Key = "Pos" end
		if ( Key == "ang" || Key == "Ang" || Key == "angle" ) then Key = "Angle" end
		if ( Key == "model" ) then Key = "Model" end
		
		Arg = EntTable[ Key ]
		
		// Special keys
		if ( Key == "Data" ) then Arg = EntTable end
		
		// If there's a missing argument then unpack will stop sending at that argument
		if ( Arg == nil ) then Arg = false end
		
		ArgList[ iNumber ] = Arg
		
	end
		
	
	// Create and return the entity
	return EntityClass.Func( Player, unpack(ArgList) )
	
end

function CreateConstraintFromTable( Constraint, EntityList )

	local Factory = ConstraintType[ Constraint.Type ]
	if ( !Factory ) then return end
	
	local Args = {}
	for k, Key in pairs( Factory.Args ) do
	
		local Val = Constraint[ Key ]
		
		for i=1, 6 do 
			if ( Constraint.Entity[ i ] ) then
				if ( Key == "Ent"..i ) then	
					Val = EntityList[ Constraint.Entity[ i ].Index ] 
					if ( Constraint.Entity[ i ].World ) then
						Val = GetWorldEntity()
					end
				end
				if ( Key == "Bone"..i ) then Val = Constraint.Entity[ i ].Bone end
				if ( Key == "LPos"..i ) then Val = Constraint.Entity[ i ].LPos end
				if ( Key == "WPos"..i ) then Val = Constraint.Entity[ i ].WPos end
				if ( Key == "Length"..i ) then Val = Constraint.Entity[ i ].Length end
			end
		end
		
		// If there's a missing argument then unpack will stop sending at that argument
		if ( Val == nil ) then Val = false end
		
		table.insert( Args, Val )
	
	end
	
	local Entity = Factory.Func( unpack(Args) )
	
	return Entity

end

/*---------------------------------------------------------
   Given entity list and constranit list, create all entities
   and return their tables
---------------------------------------------------------*/
function Paste( Player, EntityList, ConstraintList )

	local CreatedEntities = {}
	
	//
	// Create the Entities
	//
	for k, v in pairs( EntityList ) do
		CreatedEntities[ k ] = CreateEntityFromTable( Player, v )
	end
	
	//
	// Apply modifiers to the created entities
	//
	for EntID, Ent in pairs( CreatedEntities ) do	
	
		ApplyEntityModifiers ( Player, Ent )
		ApplyBoneModifiers ( Player, Ent )
	
	end
	
	
	local CreatedConstraints = {}
	
	//
	// Create constraints
	//
	for k, Constraint in pairs( ConstraintList ) do
	
		local Entity = CreateConstraintFromTable( Constraint, CreatedEntities )
		
		if ( Entity && Entity:IsValid() ) then
			table.insert( CreatedConstraints, Entity )
		end
	
	end


	return CreatedEntities, CreatedConstraints
	
end

function ApplyEntityModifiers( Player, Ent )

	for Type, ModFunction in pairs( EntityModifiers ) do
		ModFunction( Player, Ent, Ent:GetTable() )
	end

end


function ApplyBoneModifiers( Player, Ent )

	if ( !Ent.PhysicsObjects ) then return end
	
	// For each modifier
	for Type, ModFunction in pairs( BoneModifiers ) do
		
		// For each of the entity's bones
		for Bone, Args in pairs( Ent.PhysicsObjects ) do
		
			local PhysObject = Ent:GetPhysicsObjectNum( Bone )
		
			if ( Ent.PhysicsObjects[ Bone ] ) then
				ModFunction( Player, Ent, Bone, PhysObject, Ent.PhysicsObjects[ Bone ] )
			end
		
		end
		
	end

end

// Returns all ents & constraints in a system
function GetAllConstrainedEntitiesAndConstraints( ent, EntTable, ConstraintTable )

	if ( !ent:IsValid() ) then return end

	EntTable[ ent:EntIndex() ] = ent
	
	if ( !constraint.HasConstraints( ent ) ) then return end
	
	local ConTable = constraint.GetTable( ent )
	
	for key, constraint in pairs( ConTable ) do

		local index = constraint.Constraint
		
		if ( !ConstraintTable[ index ] ) then

			// Add constraint to the constraints table
			ConstraintTable[ index ] = constraint

			// Run the Function for any ents attached to this constraint
			for key, ConstrainedEnt in pairs( constraint.Entity ) do

				GetAllConstrainedEntitiesAndConstraints( ConstrainedEnt.Entity, EntTable, ConstraintTable )
					
			end
			
		end
	end

	return EntTable, ConstraintTable
	
end

