local base_size=100 -- the size of the ring tube

-- stuff in here cannnot be teleported
local protected_entities = {
	ring_base = true,
	ring_ring = true,
	func_door = true,
	func_door_rotating = true,
	func_movelinear = true,
	func_rot_button= true,
	func_rotating = true,
	dhd_atlantis = true,
	dhd_sg1 = true,
	stargate_atlantis = true,
	stargate_sg1 = true,
	prop_door_rotating=true
}

local playersonly = CreateConVar( "rings_players_only", "0" )

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

function ENT:Initialize()

	self.Entity:SetModel("models/props_junk/sawblade001a.mdl")
	self.Entity:PhysicsInit( SOLID_VPHYSICS )
	self.Entity:SetMoveType(MOVETYPE_VPHYSICS)
	self.Entity:SetSolid(SOLID_VPHYSICS)
	self.Entity:SetCollisionGroup(COLLISION_GROUP_WEAPON); -- Nocollide with players
	self.Rings={}

	self.EndPos=Vector(0,0,0)
	self.Ready=0
	self.Other=self.Entity
	self.Master=false
	self.Busy=false
	self.Ents={}
	self.Effect=false
	self.WaitTime=0
	self.SetRange=1024
	self.Entity:SetUseType(SIMPLE_USE)
	self.Address=nil
	
	-- Wire support to the ring base using stargate lib - @aVoN
	self:CreateWireInputs("Dial Closest","Dial Number","Set Range","UnUsable");
	self:CreateWireOutputs("Usable","Active");
end

function ENT:KeyValue(key,value)
	if key=="name" then
		self.Address=value
		self.Entity:SetNetworkedString("address",value)
	end
end

function ENT:SpawnFunction( ply, tr)
	local SpawnPos = tr.HitPos + tr.HitNormal * 50
	local ang = ply:GetAimVector():Angle(); ang.p = 0; ang.r = 0; ang.y = (ang.y+180) % 360
	local ent = ents.Create( "ring_base" )
	ent:SetPos( SpawnPos )
	ent:SetAngles(ang);
	ent:Spawn()
	ent:Activate()
	ent:DropToFloor();
	ent:PhysWake();
	return ent
end

--################# Stops rings if you remove the dialling ring-transporter while it was dialling @aVoN
function ENT:OnRemove()
	if(ValidEntity(self.Other) and self.Other.Busy) then
		self.Other:ReadyChecks();
	end
end

function ENT:Use(ply)
	--if self.Address then return end -- Allow address changing!
	umsg.Start("RingTransporterShowNameWindow",ply)
	umsg.Entity(self.Entity);
	umsg.End()
	ply.RingNameEnt=self
end

function RingsNamingCallback(ply,cmd,args)
	if ply.RingNameEnt and ply.RingNameEnt~=NULL then
		-- No multiple rings please!
		for _,v in pairs(ents.FindByClass("ring_base")) do
			if(v.Address == args[1]) then 
				ply:SendLua("GAMEMODE:AddNotify(\"A Ring with this address already exists!\", NOTIFY_ERROR, 5); surface.PlaySound( \"buttons/button2.wav\" )");
				return;
			end
		end
		if args[1] then
			ply.RingNameEnt.Address=args[1]
			ply.RingNameEnt:SetNetworkedString("address",args[1])
			ply.RingNameEnt:SetEntityModifier("Address",args[1]);
		end
		ply.RingNameEnt=nil
	end
end
concommand.Add("doringname",RingsNamingCallback)

function ENT:ReportReachedPos(ent) -- this'll be called when a ring gets to its position
		self.Ready=self.Ready+1
end

function ENT:FindNearest(address)
if address=="" or not address then
	local dist=999999999999999 -- lolwut
	local nEnt=self.Entity
	local rings=ents.FindByClass("ring_base")
		for i=1,table.getn(rings) do
			if rings[i]~=self.Entity then
				local nDist=(self.Entity:GetPos()-rings[i]:GetPos()):Length()
					 if nDist<dist then
						 dist=nDist
						 nEnt=rings[i]
					 end
			end
		end
	return nEnt
else
	local rings=ents.FindByClass("ring_base")
	for _,ent in pairs(rings) do
		if ent.Address==address then
			return ent
		end
	end
	return false
end

end

local function CheckedEntRemove(ent)
	if ValidEntity(ent) then
		ent:Remove()
	end
end

function ENT:ReturnRings()
	util.PrecacheSound("rings2.wav")
	self.Entity:EmitSound("rings2.wav", 100, 100)
		local pos=nil
		local ent=nil
		ent=self.Rings[1]
		timer.Simple(0.2,ent.ReturnPos,ent)
		timer.Simple(3,CheckedEntRemove,ent)

		ent=self.Rings[2]
		timer.Simple(0.7,ent.ReturnPos,ent)
		timer.Simple(3,CheckedEntRemove,ent)

		ent=self.Rings[3]
		timer.Simple(1.0,ent.ReturnPos,ent)
		timer.Simple(3,CheckedEntRemove,ent)

		ent=self.Rings[4]
		timer.Simple(1.2,ent.ReturnPos,ent)
		timer.Simple(3,CheckedEntRemove,ent)

		ent=self.Rings[5]
		timer.Simple(1.4,ent.ReturnPos,ent)
		timer.Simple(3,CheckedEntRemove,ent)
		
		timer.Simple(3,function()
			if ValidEntity(self.Entity) then
				self.Busy=false
				self:SetWire("Active",false);
			end
		end)
	self.Ents={}
end

function ENT:DoTeleport()
	local entz=ents.FindInSphere(self.Entity:LocalToWorld(self.EndPos),80)
	for _,ent in pairs(entz) do
		if not protected_entities[ent:GetClass()] and ent~=GetWorldEntity() and not self.Other.Ents[ent] and not self.Ents[ent] and not ent.NotTeleportable and not ent:GetParent():IsValid() and ent.Pair ~= self.Entity then
			if playersonly:GetBool() and not ent:IsPlayer() then else
				if ent:GetPhysicsObject():IsValid() then
					self.Ents[ent]=true
					local offset=ent:GetPos()-self.Entity:LocalToWorld(self.EndPos)
					local destination=self.Other:LocalToWorld(self.Other.EndPos)+offset
					ent:SetPos(destination)
						if ent:GetClass()=="player" then
							umsg.Start("RingTransporterTele", ent)
								umsg.Bool(false)
							umsg.End()
						end
				end
			end
		end
	end
	-- let's have a delay before returning the rings
	timer.Simple(0.1,self.ReturnRings,self)
	self.Ready=0
end

function ENT:ReadyChecks()
	if self and ValidEntity(self.Entity) then
		-- Receiving end is not valid anymore - Retreive the rings!
		if(not ValidEntity(self.Other)) then
			if(self.Ready == 5) then 
				self:ReturnRings();
				return;
			end
			timer.Simple(0.2,self.ReadyChecks,self);
			return;
		end
		if self.Other.Ready==5 and self.Ready==5 then
			timer.Simple(0.3,self.Entity.DoTeleport,self.Entity)
			timer.Simple(0.3,self.Other.DoTeleport,self.Other)
			
	local entz=constraint.GetAllConstrainedEntities(self.Entity)
	for _,ent in pairs(entz) do
		self.Ents[ent]=true
	end
	
	local entz=constraint.GetAllConstrainedEntities(self.Other)
	for _,ent in pairs(entz) do
		self.Other.Ents[ent]=true
	end
			local effectdata = EffectData()
				effectdata:SetOrigin( self.Entity:LocalToWorld(self.EndPos) )
			util.Effect( "transportcore", effectdata )

			local effectdata = EffectData()
				effectdata:SetOrigin( self.Other:LocalToWorld(self.Other.EndPos) )
			util.Effect( "transportcore", effectdata )

			local entz=ents.FindInSphere(self.Entity:LocalToWorld(self.EndPos),80)
			for _,ent in pairs(entz) do
				if ent:GetClass()=="player" then
					umsg.Start("RingTransporterTele", ent)
						umsg.Bool(true)
					umsg.End()
				end
			end
		else
			if self.WaitTime<CurTime() then
				self.Ready=0
				self.Ents={}
				for _,ent in pairs(self.Rings) do
					ent:Remove()
				end
				self.Busy=false
				
				self.Other.Ready=0
				self.Other.Ents={}
				for _,ent in pairs(self.Other.Rings) do
					ent:Remove()
				end
				self.Other.Busy=false
				return
			end
			timer.Simple(0.2,self.ReadyChecks,self) -- and again and again and again and *
		end
	end
end

function ENT:DoRings()
	self.Busy=true
	util.PrecacheSound("rings1.wav")
	self.Entity:EmitSound("rings1.wav", 100, 100)
	for i=1,5 do
		self.Rings[i]=ents.Create("ring_ring")
		self.Rings[i]:SetPos(self.Entity:GetPos())
		self.Rings[i]:SetAngles(self.Entity:GetAngles())
		self.Rings[i]:SetParent(self.Entity)
		self.Rings[i]:Spawn()
		self.Entity:DeleteOnRemove(self.Rings[i])
		
	--[[	constraint.NoCollide(GetWorldEntity(),self.Rings[i], 0, 0 )
		constraint.NoCollide(self.Entity,self.Rings[i], 0, 0 )
		for _,ent in pairs(self.Rings) do -- what's already spawned
			constraint.NoCollide(ent,self.Rings[i], 0, 0 )
		end
	]]
	end
	
	local num=20
	
	-- Addtion by aVoN: Use "FindRange" everytime we are facing down and the angle is below 45° (pi/4)
	if(self.Entity:GetUp():DotProduct(Vector(0,0,-1)) > 1/math.sqrt(2)) then
		self.SetRange = 1024;
	end
	
	self.Ready=0
	-- find the end position
	local trace={}
		-- Instead of using a mask to avoid "stuff in the ring's way", Catdaemon, I start the traceline a bit later
		trace.start=self.Entity:GetPos()+self.Entity:GetUp()*110;
		trace.endpos=self.Entity:GetPos()+self.Entity:GetUp()*self.SetRange
		trace.filter = {self.Entity,self.Rings[1],self.Rings[2],self.Rings[3],self.Rings[4],self.Rings[5]}
		table.Add(trace.filter, player.GetAll() )
		--trace.mask=MASK_NPCWORLDSTATIC -- Disabled by avon - it fucking sucks!
	local traceRes=util.TraceLine( trace )
	-- move the rings and set the teleport position
	if traceRes.HitWorld or traceRes.HitPos:Distance(self.Entity:GetPos())<999 and traceRes.HitPos:Distance(self.Entity:GetPos())>100 then
		local pos=nil
		local ent=nil
		ent=self.Rings[5]
		pos=traceRes.HitPos-(self.Entity:GetUp()*110)+(self.Entity:GetUp()*num)*5
		timer.Simple(2,ent.GotoPos,ent,self.Entity:WorldToLocal(pos))

		ent=self.Rings[4]
		pos=traceRes.HitPos-(self.Entity:GetUp()*110)+(self.Entity:GetUp()*num)*4
		timer.Simple(2.5,ent.GotoPos,ent,self.Entity:WorldToLocal(pos))

		ent=self.Rings[3]
		pos=traceRes.HitPos-(self.Entity:GetUp()*110)+(self.Entity:GetUp()*num)*3
		timer.Simple(3.0,ent.GotoPos,ent,self.Entity:WorldToLocal(pos))

		ent=self.Rings[2]
		pos=traceRes.HitPos-(self.Entity:GetUp()*110)+(self.Entity:GetUp()*num)*2
		timer.Simple(3.5,ent.GotoPos,ent,self.Entity:WorldToLocal(pos))

		ent=self.Rings[1]
		pos=traceRes.HitPos-(self.Entity:GetUp()*110)+(self.Entity:GetUp()*num)*1
		timer.Simple(3.6,ent.GotoPos,ent,self.Entity:WorldToLocal(pos))
		
		pos=traceRes.HitPos-(self.Entity:GetUp()*50)
		self.EndPos=self.Entity:WorldToLocal(pos)
	else

		local pos=nil
		local ent=nil
		ent=self.Rings[5]
		pos=self.Entity:GetPos()+(self.Entity:GetUp()*num)*5
		timer.Simple(2,ent.GotoPos,ent,self.Entity:WorldToLocal(pos))

		ent=self.Rings[4]
		pos=self.Entity:GetPos()+(self.Entity:GetUp()*num)*4
		timer.Simple(2.5,ent.GotoPos,ent,self.Entity:WorldToLocal(pos))

		ent=self.Rings[3]
		pos=self.Entity:GetPos()+(self.Entity:GetUp()*num)*3
		timer.Simple(3.0,ent.GotoPos,ent,self.Entity:WorldToLocal(pos))

		ent=self.Rings[2]
		pos=self.Entity:GetPos()+(self.Entity:GetUp()*num)*2
		timer.Simple(3.5,ent.GotoPos,ent,self.Entity:WorldToLocal(pos))

		ent=self.Rings[1]
		pos=self.Entity:GetPos()+(self.Entity:GetUp()*num)*1
		timer.Simple(3.7,ent.GotoPos,ent,self.Entity:WorldToLocal(pos))
		
		pos=self.Entity:GetPos()+(self.Entity:GetUp()*50)
		self.EndPos=self.Entity:WorldToLocal(pos)
	end
	self:SetWire("Active",true);
end

--################# Dials @aVoN
function ENT:Dial(address)
	--wrapper function
	self.Entity:StartSequence(address)
end

function ENT:StartSequence(address)
	self.WaitTime=CurTime()+10
	-- find another ring transporter
	local nearest=self.Entity:FindNearest(address)
	if not nearest or nearest.Busy then
		util.PrecacheSound("common/warning.wav")
		self.Entity:EmitSound( "common/warning.wav", 100, 70 )
		return
	end
	if nearest==self.Entity then return end -- don't want to teleport to ourselves now do we
	self.Other=nearest
	self.Other.SetRange=self.SetRange
	self.Master=true
	nearest.Other=self.Entity
	nearest.Master=false
	self.Entity:ReadyChecks() -- go to the next stage
	
	-- set their rings going
	self.Entity:DoRings()
	self.Other:DoRings()
end

-- #############################################################
-- Wire port, first done by Meeces, implemented by Catdaemon, and now rewritten by aVoN
-- #############################################################

-- ################### This uses my Wire class @aVoN
function ENT:Think()
	self:SetWire("Usable",not self.Busy);
end

-- ################### Slightly improved wireinput version @aVoN
function ENT:TriggerInput(name,value)
	local b = util.tobool(value);
    if(name == "Dial Closest") then
		if(b and not self.Busy) then
			self:Dial("");
		end
	elseif(name == "Dial Number") then
		if(b and not self.Busy) then
			self:Dial(tostring(value));
		end
	elseif(name == "Set Range") then
		if(b and not self.Busy) then
			self.SetRange=value;
		end
	elseif(name == "UnUsable") then
		self.Busy = b;
	end
end

-- ################### This function originally has been added to the ring_panel. But my "ring caller" swep sets Player.RingDialEnt to the actual ring_base rather than the ring_panel entity so we need it here again to avoid lua errors @aVoN
function ENT:DoCallback(range,address)
	local ranger;
	if(range == "1") then
		ranger=1024;
	end
	if(not self.Busy) then
		self.SetRange = tonumber(ranger or 50);
		self:Dial(address);
	end
end

--##################################
--#### Duplicator Entity Modifiers (for the rings)
--##################################

--################# Sets a new value to one modifier @aVoN
function ENT:SetEntityModifier(k,v)
	self.Duplicator = self.Duplicator or {};
	self.Duplicator[k] = v;
	duplicator.StoreEntityModifier(self.Entity,"RingBase",self.Duplicator);
end

-- FIXME: Maybe a recode? The PostEntityPaste etc functions are already used by Wire/RD2 so I do not want to override them.
function ENT.DuplicatorEntityModifier(_,e,data)
	if(data) then
		for k,v in pairs(data) do
			if(k == "Address") then
				local allow = true;
				for _,ring in pairs(ents.FindByClass("ring_base")) do
					if(ring.Address == v) then allow = false break end;
				end
				if(allow) then
					e:SetNetworkedString("address",v);
					e.Address = v;
				end
			end
			e:SetEntityModifier(k,v);
		end
	end
end
duplicator.RegisterEntityModifier("RingBase",ENT.DuplicatorEntityModifier);