AddCSLuaFile( "cl_init.lua" )
AddCSLuaFile( "shared.lua" )

include('shared.lua')

ENT.WireDebugName = "Dupe Teleporter"
ENT.OverlayDelay = 0

function ENT:Initialize()
	self.Entity:PhysicsInit( SOLID_VPHYSICS )
	self.Entity:SetMoveType( MOVETYPE_VPHYSICS )
	self.Entity:SetSolid( SOLID_VPHYSICS )
	self.Entity:SetUseType( SIMPLE_USE )
	
	self.Outputs = WireLib.CreateSpecialOutputs(self.Entity,{"Entity Scaned","Entity Serialised","Spawn Available","Deserialisable Input Available","Data Output","Sending","Blocks Sended","Receiving","Blocks Received","Serialised Output Block Count","Serialised Input Block Count"},{"NORMAL","NORMAL","NORMAL","NORMAL","STRING","NORMAL","NORMAL","NORMAL","NORMAL","NORMAL","NORMAL"})
	self.Inputs = WireLib.CreateSpecialInputs(self.Entity,{"Read Entity","Serialise Entity","Spawn Entity","Deserialise Input","Data Input","Start Data Sending","Piece Size","Clear Input","Spawn Player SteamID"},{"NORMAL","NORMAL","NORMAL","NORMAL","STRING","NORMAL","NORMAL","NORMAL","STRING"})
	
	self.OutHeadEntityIdx	= nil
	self.OutHoldAngle 		= nil
	self.OutHoldPos 		= nil
	self.OutStartPos		= nil
	self.OutEntities		= nil
	self.OutConstraints		= nil
	self.OutNumOfEnts		= 0
	self.OutNumOfConst		= 0
	
	self.OutSerialised 		= ""
	self.OutBlockCount		= 0
	self.OutBlocks			= nil
	
	self.OutBlockSendNum 	= -1
	
	self.InHeadEntityIdx	= nil
	self.InHoldAngle 		= nil
	self.InHoldPos 			= nil
	self.InStartPos			= nil
	self.InEntities			= nil
	self.InConstraints		= nil
	self.InNumOfEnts		= 0
	self.InNumOfConst		= 0
	
	self.InSerialised 		= ""
	self.InBlocks			= nil
	self.InBlockCount 		= 0
	
	self.Copied			= false
	self.SpawnData 		= false
	
	self.PieceSize = 32
	
	self.SendingData = false
	self.ReceivingData = false
	
	self.OwnerSteamID = ""
	self.SpawnSteamID = ""
	
	self:ClearClipBoard()
end

function ENT:Think()
	self.BaseClass.Think(self)
	
	if (self.SendingData) then
		if (self.OutBlockSendNum == -1) then
			self.OutBlockSendNum = 0
			
			Wire_TriggerOutput(self.Entity,"Data Output","Data:Start:"..self.OutBlockCount)
			
			Wire_TriggerOutput(self.Entity,"Sending",1)
		else
			if (self.OutBlockSendNum == self.OutBlockCount) then
				self.SendingData = false
				self.OutBlockSendNum = -1
				Wire_TriggerOutput(self.Entity,"Sending",0)
				Wire_TriggerOutput(self.Entity,"Blocks Sended",0)
				Wire_TriggerOutput(self.Entity,"Data Output","")
			else
				Wire_TriggerOutput(self.Entity,"Data Output","Data:"..self.OutBlockSendNum..":"..self.OutBlocks[self.OutBlockSendNum])
				Wire_TriggerOutput(self.Entity,"Blocks Sended",self.OutBlockSendNum+1)
				self.OutBlockSendNum = self.OutBlockSendNum + 1
			end
		end
	end
	
	self.Entity:NextThink( CurTime() + 0.1 )
end

function ENT:TriggerInput(iname, value)
	if (iname == "Read Entity") then
		if (value > 0 && !self.SendingData) then
			self:ClearClipBoard()
						
			if ( CLIENT ) then return true end
			
			local StartPos = self.Entity:GetPos()
			
			local tr = {}
			tr.start = StartPos
			tr.endpos = StartPos + self.Entity:GetUp() * 100
			tr.filter = { self.Entity }
			local trace = util.TraceLine( tr )
			
			if (trace.Entity && trace.Entity:IsValid()) then
				self.OutEntities = {}
				self.OutConstraints = {}
				
				AdvDupe.Copy( trace.Entity, self.OutEntities, self.OutConstraints, StartPos )
				
				local angle  = self.Entity:GetAngles()
					
				self.OutHeadEntityIdx	= trace.Entity:EntIndex()
				self.OutHoldAngle 		= angle
				self.OutHoldPos 		= trace.Entity:WorldToLocal( StartPos )
				self.OutStartPos		= StartPos

				self.OrgStartPos	= StartPos
				
				self.OutNumOfEnts		= table.Count(self.OutEntities)	or 0
				self.OutNumOfConst		= table.Count(self.OutConstraints)	or 0
				
				self.Copied	= true
				Wire_TriggerOutput(self.Entity, "Entity Scaned", 1)
			end
		end
	elseif (iname == "Spawn Entity") then
		if (value > 0 && !self.SendingData) then
			if ( CLIENT ) then	return true	end
			if ( self.SpawnData ) then
				local ply = self:GetUserEntity(self.SpawnSteamID)
				local angle  = self.Entity:GetAngles()
				if (ply) then
					AdvDupe.StartPaste( ply, self.InEntities, self.InConstraints, self.InHeadEntityIdx, self.Entity:GetPos(), self.Entity:GetAngles()-self.InHoldAngle, self.InNumOfEnts, self.InNumOfConst, false, false, nil, true, self.Entity, true )
				end
			end
		end
	elseif (iname == "Serialise Entity") then
		if (value > 0 && self.Copied && !self.SendingData) then
			local Header = {}
			Header[1] = "Type:"			.."AdvDupe File"
			Header[2] =	"Creator:"		..string.format('%q', self:GetUserEntity(self.OwnerSteamID):GetName())
			Header[3] =	"Date:"			..os.date("%m/%d/%y")
			Header[4] =	"Description:"	..string.format('%q', "none")
			Header[5] =	"Entities:"		..self.OutNumOfEnts
			Header[6] =	"Constraints:"	..self.OutNumOfConst
			
			local ExtraHeader = {}
			ExtraHeader[1] = "FileVersion:"				..AdvDupe.FileVersion
			ExtraHeader[2] = "AdvDupeVersion:"			..AdvDupe.Version
			ExtraHeader[3] = "AdvDupeToolVersion:"		..AdvDupe.ToolVersion
			ExtraHeader[4] = "AdvDupeSharedVersion:"	..dupeshare.Version
			ExtraHeader[5] = "SerialiserVersion:"		..Serialiser.Version
			ExtraHeader[6] = "WireVersion:"				..(WireVersion or "Not Installed")
			ExtraHeader[7] = "Time:"					..os.date("%I:%M %p")
			ExtraHeader[8] = "Head:"					..self.OutHeadEntityIdx
			ExtraHeader[9] = "HoldAngle:"				..string.format( "%g,%g,%g", self.OutHoldAngle.pitch, self.OutHoldAngle.yaw, self.OutHoldAngle.roll )
			ExtraHeader[10] = "HoldPos:"				..string.format( "%g,%g,%g", self.OutHoldPos.x, self.OutHoldPos.y, self.OutHoldPos.z )
			ExtraHeader[11] = "StartPos:"				..string.format( "%g,%g,%g", self.OutStartPos.x, self.OutStartPos.y, self.OutStartPos.z )
			
			ConstsTable				= {}
			for k, v in pairs(self.OutConstraints) do
				table.insert( ConstsTable, v )
			end
			
			local StrTbl = {}
			StrTbl.Strings = {} --keyed with string indexes
			StrTbl.StringIndx = {} --keyed with strings
			StrTbl.LastIndx = 0 --the index last used
			StrTbl.Saved = 0 --number of strings we didn't have to save
			
			local EntsStr = Serialiser.SingleTable( self.OutEntities, StrTbl, false )
			local ConstsStr = Serialiser.SingleTable( ConstsTable, StrTbl, false )
				
			local Dict = {}
			for idx,cstr in pairs(StrTbl.Strings) do
				table.insert(Dict, table.concat( {idx, ":", cstr} ))
			end
			local DictStr = table.concat( Dict, "\n" ) .. "\nSaved:" .. StrTbl.Saved
			
			self.OutSerialised = table.concat(
				{
				"[Info]",
				table.concat( Header, "\n" ),
				"[More Information]",
				table.concat( ExtraHeader, "\n" ),
				"[Save]",
				"Entities:"..EntsStr,
				"Constraints:"..ConstsStr,
				"[Dict]",
				DictStr
				}, "\n")
				
			Wire_TriggerOutput(self.Entity, "Entity Serialised", 1)
			
			local length = string.len(self.OutSerialised)
			self.OutBlocks = {}
			self.OutBlockCount = math.ceil(length/self.PieceSize)
			for i = 1,length,self.PieceSize do
				if ((length - i) >= (self.PieceSize - 1)) then
					self.OutBlocks[(i-1)/self.PieceSize] = string.sub(self.OutSerialised,i,i + self.PieceSize - 1)
				else
					self.OutBlocks[(i-1)/self.PieceSize] = string.sub(self.OutSerialised,i,length)
				end
			end
			Wire_TriggerOutput(self.Entity,"Serialised Output Block Count",self.OutBlockCount)
		end
	elseif (iname == "Deserialise Input") then
		if (value > 0 && self.InSerialised != "" && !self.SendingData) then
			if ( string.Left(self.InSerialised, 5) != "\"Out\"") then
				local function DupePortLoad(ply, filepath, ent, HeaderTbl, ExtraHeaderTbl, Data)
					if ( HeaderTbl.Type ) and ( HeaderTbl.Type == "AdvDupe File" ) then
						ExtraHeaderTbl.FileVersion = tonumber(ExtraHeaderTbl.FileVersion)
						
						if (ExtraHeaderTbl.FileVersion > AdvDupe.FileVersion) then
							Msg("AdvDupeINFO:File is newer than installed version, failure may occure, you should update.")
						end
						
						if ( ExtraHeaderTbl.FileVersion >= 0.82 ) and ( ExtraHeaderTbl.FileVersion < 0.9 )then
							local a,b,c = ExtraHeaderTbl.HoldAngle:match("(.-),(.-),(.+)")
							local HoldAngle = Angle( tonumber(a), tonumber(b), tonumber(c) )
							
							ent:DupeLoadCallBack(Data.Entities,Data.Constraints,tonumber(ExtraHeaderTbl.Head),tonumber(HeaderTbl.Entities),tonumber(HeaderTbl.Constraints),HoldAngle)
						elseif ( ExtraHeaderTbl.FileVersion <= 0.81 ) then
							ent:DupeLoadCallBack(Data.Entities,Data.Constraints,Data.HeadEntityIdx,ata.HoldAngle,tonumber(HeaderTbl.Entities),tonumber(HeaderTbl.Constraints),Data.HoldAngle)
						end
					elseif ( HeaderTbl.Type ) and ( HeaderTbl.Type == "Contraption Saver File" ) then
						ent:DupeLoadCallBack(Data.Entities,Data.Constraints,Data.Head,tonumber(HeaderTbl.Entities),tonumber(HeaderTbl.Constraints),Angle(0,0,0))
					elseif (Data.Information) then
						local head,low
						for k,v in pairs(Data.Entities) do
							if (!head) or (v.Pos.z < low) then
								head = k
								low = v.Pos.z
							end
						end
						
						AdvDupe.ConvertPositionsToLocal( Data.Entities, Data.Constraints, Data.Entities[head].Pos + Vector(0,0,-15), Angle(0,0,0) )
						
						ent:DupeLoadCallBack( Data.Entities, Data.Constraints,Data.head,Data.Information.Entities,Data.Information.Constraints,Angle(0,0,0))
					else
						Msg("AdvDupeERROR: Unknown File Type or Bad File\n")
						return false
					end
					return true
				end
			
				Serialiser.DeserialiseWithHeaders( self.InSerialised, DupePortLoad, nil, "", self )
			end
		end
	elseif (iname == "Piece Size") then
		if (value >= 8 && !self.SendingData) then
			self.PieceSize = value
		elseif (value < 8 && self.SendingData) then
			self.PieceSize = 8
		end
	elseif (iname == "Start Data Sending") then
		if (value > 0 && !self.SendingData && (self.OutSerialised != "")) then
			self.SendingData = true
		end
	elseif (iname == "Data Input") then
		if (value != "") then
			if (string.sub(value,1,5) == "Data:") then
				if (string.sub(value,6,10) == "Start") then
					self:ClearInputClipBoard()
					
					self.ReceivingData = true
					
					self.InBlocks = {}
					self.InBlockCount = tonumber(string.sub(value,12,string.len(value)))
					Wire_TriggerOutput(self.Entity,"Serialised Input Block Count",self.InBlockCount)
					Wire_TriggerOutput(self.Entity,"Receiving",1)
				elseif (self.ReceivingData) then
					pos = string.instr(value,":",6)
					BlockNum = tonumber(string.sub(value,6,pos-1))
					
					self.InBlocks[BlockNum] = string.sub(value,pos+1,string.len(value))
					
					if (BlockNum == (self.InBlockCount - 1)) then
						self.ReceivingData = false
						
						self.InSerialised = ""
						for i = 0,(self.InBlockCount-1) do
							self.InSerialised = self.InSerialised .. self.InBlocks[i]
						end
						
						self.ReceivingData = false
						Wire_TriggerOutput(self.Entity,"Blocks Received",self.InBlockCount)
						Wire_TriggerOutput(self.Entity,"Deserialisable Input Available",1)
						Wire_TriggerOutput(self.Entity,"Receiving",0)
					else
						Wire_TriggerOutput(self.Entity,"Blocks Received",BlockNum+1)
					end
				end
			end
		end
	elseif (iname == "Clear Input") then
		if (value > 0 && !self.ReceivingData) then
			self:ClearInputClipBoard()
		end
	elseif (iname == "Spawn Player SteamID") then
		if (SinglePlayer()) then
			self.SpawnSteamID = self.OwnerSteamID
		else
			if (value != "") then
				self.SpawnSteamID = value
			else
				self.SpawnSteamID = self.OwnerSteamID
			end
		end
	end
end

function ENT:OnRemove()
	self.BaseClass.OnRemove(self)
end

function ENT:ClearClipBoard()
	self.OutHeadEntityIdx	= nil
	self.OutHoldAngle 		= nil
	self.OutHoldPos 		= nil
	self.OutStartPos		= nil
	self.OutEntities		= nil
	self.OutConstraints		= nil
	self.OutNumOfEnts		= 0
	self.OutNumOfConst		= 0
	self.OutSerialised 		= ""
	self.OutBlockCount		= 0
	self.OutBlocks			= nil
	self.Copied				= false
	
	Wire_TriggerOutput(self.Entity, "Entity Scaned", 0)
	Wire_TriggerOutput(self.Entity, "Entity Serialised", 0)
	Wire_TriggerOutput(self.Entity, "Serialised Output Block Count",0)
end

function ENT:ClearInputClipBoard()
	self.InHeadEntityIdx	= nil
	self.InHoldAngle 		= nil
	self.InHoldPos 			= nil
	self.InStartPos			= nil
	self.InEntities			= nil
	self.InConstraints		= nil
	self.InNumOfEnts		= 0
	self.InNumOfConst		= 0
	self.InSerialised		= ""
	
	self.InBlocks 			= nil
	self.InBlockCount 		= 0
	self.ReceivingData		= false
	self.SpawnData			= false
	
	Wire_TriggerOutput(self.Entity,"Spawn Available",0)
	Wire_TriggerOutput(self.Entity,"Deserialisable Input Available",0)
	Wire_TriggerOutput(self.Entity,"Serialised Input Block Count",0)
	Wire_TriggerOutput(self.Entity,"Blocks Received",0)
end

function ENT:DupeLoadCallBack( Entities, Constraints, HeadEntityIdx, NumOfEnts, NumOfConst, HoldAngle )
	if ( CLIENT ) then return end
	if (Entities) then
		
		self.InHeadEntityIdx	= HeadEntityIdx

		self.InEntities			= Entities
		self.InConstraints		= Constraints or {}
		
		self.InHoldAngle 		= HoldAngle
		
		self.InNumOfEnts		= NumOfEnts
		self.InNumOfConst		= NumOfConst
		
		self.SpawnData			= true
		Wire_TriggerOutput(self.Entity, "Spawn Available", 1)
		
	end
end

function ENT:GetUserEntity(SteamID)
	if (SinglePlayer()) then
		return self.OwnerSteamID
	else
		for k, v in pairs(player.GetAll()) do
			if (v:SteamID() == SteamID) then return v end
		end
	end
	return nil
end
