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

------------------------------//
function ENT:SpawnFunction(ply, tr)
	if (not tr.Hit) then return end
	local SpawnPos = tr.HitPos + tr.HitNormal * 1
	local ent = ents.Create(self.ClassName)
	local angle = ply:GetAimVector():Angle()
	angle = Angle(0, angle.yaw, 0)
	angle:RotateAroundAxis(angle:Up(), 180)
	ent:SetAngles(angle)
	ent:SetPos(SpawnPos)
	ent:Spawn()
	ent:Activate()
	zrmine.f.SetOwnerID(ent, ply)

	return ent
end

function ENT:Initialize()
	self:SetModel(self.Model)
	self:PhysicsInit(SOLID_VPHYSICS)
	self:SetMoveType(MOVETYPE_VPHYSICS)
	self:SetSolid(SOLID_VPHYSICS)
	self:SetUseType(SIMPLE_USE)
	local phys = self:GetPhysicsObject()

	if (phys:IsValid()) then
		phys:Wake()
		phys:EnableMotion(true)
		phys:EnableDrag(true)
		phys:SetDragCoefficient(1)
	end

	self.PhysgunDisabled = false
	self:UseClientSideAnimation()
	self:Spawn_RefinedGravel()
	self:Spawn_OutputGravel()
	self.connected_Basket = nil
	self.connected_Parent = nil
	self.connected_module = nil
	self.StoredResource = {}
	self.StoredResource["Coal"] = 0
	self.StoredResource["Iron"] = 0
	self.StoredResource["Bronze"] = 0
	self.StoredResource["Silver"] = 0
	self.StoredResource["Gold"] = 0
	self.RefineryPos = -1
end

function ENT:Spawn_OutputGravel()
	self.outputgravel = ents.Create("zrms_gravelanim")
	self.outputgravel:SetAngles(self:GetAngles())
	self.outputgravel:SetPos(self:GetPos() + self:GetForward() * -40)
	self.outputgravel:Spawn()
	self.outputgravel:Activate()
	self.outputgravel:SetParent(self)
	self.outputgravel:SetNoDraw(true)
end

function ENT:Spawn_RefinedGravel()
	self.refinedgravel = ents.Create("zrms_gravelanim")
	self.refinedgravel:SetAngles(self:GetAngles())
	self.refinedgravel:SetPos(self:GetPos())
	self.refinedgravel:Spawn()
	self.refinedgravel:Activate()
	self.refinedgravel:SetModel("models/zerochain/props_mining/zrms_refinedgravel01.mdl")
	self.refinedgravel:SetParent(self)
	self.refinedgravel:SetModelScale(1)
	self.refinedgravel:SetNoDraw(true)
end

function ENT:Use(activator, caller)
	if IsValid(caller) and caller:IsPlayer() then
		if not zrmine.f.IsOwner(activator, self) then
			zrmine.f.Notify(activator, zrmine.language.Module_DontOwn, 1)

			return
		end
		
		if (zrmine.f.IsAdmin(activator) == false and self.IsPipeLineEntity) then return end

		if (IsValid(self) and self:GetCurrentState() == "WAITING_FOR_ORDER" and self:GetHasChild() == false and self:GetBuildMode() == false) then
			if (IsValid(self.connected_Basket)) then
				self:Detach_Basket(self.connected_Basket)
			end

			self:Detach_Refiner()
		end
	end
end

-- Logic
function ENT:Receive_Resources(rTable)
	for k, v in pairs(rTable) do
		self.StoredResource[k] = self.StoredResource[k] + rTable[k]
	end

	self:NET_UpdateResource()
	self:CreateEffect_Table(nil, "zrmine_sfx_addgravel", self, self:GetAngles(), self:GetPos())
end

function ENT:Clear_Resource()
	self:CreateEffect_Table(nil, "zrmine_sfx_addgravel", self, self:GetAngles(), self:GetPos())
	self:SetInMachine_Coal(0)
	self:SetInMachine_Iron(0)
	self:SetInMachine_Bronze(0)
	self:SetInMachine_Silver(0)
	self:SetInMachine_Gold(0)
end

-- Here we send resources to our Child
function ENT:Send_Resources(target, rtype, ramount)
local sendTable = {}
local sendAmount = zrmine.f.Calculate_AmountCap(self.StoredResource[rtype], ramount)
self.StoredResource[rtype] = self.StoredResource[rtype] - sendAmount
sendTable[rtype] = sendAmount
target:Receive_Resources(sendTable)
self:NET_UpdateResource()
end

-- Gives how many resources we have stored
function ENT:Return_StoredAmount()
	local StoredAmount
	StoredAmount = self.StoredResource["Coal"] + self.StoredResource["Iron"] + self.StoredResource["Bronze"]
	StoredAmount = StoredAmount + self.StoredResource["Silver"] + self.StoredResource["Gold"]

	return StoredAmount
end

-- Gives returns a stored resource other then the refiner type one
function ENT:Calc_NextGiveType()
	local NextGiveType = nil

	for k, v in pairs(self.StoredResource) do
		if (k == self.RefinerType) then
			if (v > self.WorkAmount) then
				NextGiveType = k
				break
			end
		elseif (v > 0) then
			NextGiveType = k
			break
		end
	end

	return NextGiveType
end

-- Updates our net Resource vars
function ENT:NET_UpdateResource()
	self:SetInMachine_Coal(self.StoredResource["Coal"] or 0)
	self:SetInMachine_Iron(self.StoredResource["Iron"] or 0)
	self:SetInMachine_Bronze(self.StoredResource["Bronze"] or 0)
	self:SetInMachine_Silver(self.StoredResource["Silver"] or 0)
	self:SetInMachine_Gold(self.StoredResource["Gold"] or 0)
end

function ENT:Calculate_RefinedOutput(ResourceAmount, resourceType)
	local RefinedOutput = (ResourceAmount / 100) * zrmine.config.ResourceChance[resourceType]

	return RefinedOutput
end

-- Main function
function ENT:Think()
	if ((self.lastthink or -1) < CurTime()) then
		self.lastthink = CurTime() + 1
		self:ModuleUpdate()
	end
end

function ENT:ModuleUpdate()
	if (self:GetCurrentState() == "WAITING_FOR_ORDER" and self:Return_StoredAmount() > 0) then
		if (IsValid(self.connected_Basket) and self.StoredResource[self.RefinerType] > 0) then
			local basketType = self.connected_Basket:GetResourceType()

			if ((basketType == "Empty" or basketType == self.RefinerType) and self.connected_Basket:GetResourceAmount() < zrmine.config.ResourceCrates_Capacity) then
				self:Refine()
			end
		elseif (IsValid(self.connected_module)) then
			if (self.connected_module:Return_StoredAmount() < self.connected_module.HoldAmount) then
				self:MoveOre()
			end
		else
			if (IsValid(self.connected_Basket) and self.connected_Basket:GetResourceAmount() >= zrmine.config.ResourceCrates_Capacity) then
				-- Detaches our crate and places it a bit away from the machine
				self.connected_Basket:SetParent(nil)
				local pos = self:GetPos() + self:GetUp() * 3 + self:GetRight() * 50
				self.connected_Basket:SetPos(pos)
				self:Detach_Basket(self.connected_Basket)
			end
		end
	end
end

function ENT:Refine()
	local machineTime = self.RefiningTime
	-- Sets the current state to busy
	self:SetCurrentState("REFINING")
	--Plays the REFINING animation
	self:CreateAnim_Table("refine", 2)
	--Plays the crush sound
	self:CreateEffect_Table(nil, "zrmine_sfx_crush", self, self:GetAngles(), self:GetPos())

	-- Sends the crushed gravel too the connected module and basked
	timer.Simple(machineTime - 2, function()
		if (IsValid(self)) then
			-- Here we calculate the amount we want to send
			local WorkAmount = zrmine.f.Calculate_AmountCap(self.StoredResource[self.RefinerType], self.WorkAmount)
			local AnimationTime = 2
			-- Plays the output gravel animation on the Refiner if we have a output
			local outputgravel_animTime = (1 / AnimationTime) * 5.5
			self:Play_RefinedGravel_anim(outputgravel_animTime, self.RefinerType)

			--Vrondakis
			if (LevelSystemConfiguration or manolis) then
				local ply = zrmine.f.GetOwner(self)

				if IsValid(ply) then
					ply:addXP(zrmine.config.Vrondakis["Refining"].XP, " ", true)
				end
			end

			-- Plays the give gravel animation if we have some other resources in the inv and a module is connected
			local NextGiveType = self:Calc_NextGiveType()
			local NextGiveAmount = 0

			if (NextGiveType == self.RefinerType) then
				-- We subtract the ore amount we working on right now from our total stored ore amount of that type
				-- and multiply it by 0.5 so we give half of the rest amount to the next module
				NextGiveAmount = (self.StoredResource[NextGiveType] - self.WorkAmount) * 0.5
			else
				NextGiveAmount = self.StoredResource[NextGiveType]
			end

			if (IsValid(self.connected_module) and self.connected_module:Return_StoredAmount() < self.connected_module.HoldAmount and NextGiveType ~= nil) then
				local givegravel_animTime = (1 / 2) * 4.6
				self:Play_GravelGive_anim(givegravel_animTime, 2, NextGiveType)
			end

			timer.Simple(2, function()
				if (IsValid(self)) then
					-- Here we Remove the amount we processed
					self.StoredResource[self.RefinerType] = self.StoredResource[self.RefinerType] - WorkAmount
					-- Here we fill the RefinedOutput in to our basked
					local RefinedOutput = WorkAmount * self.RefineAmount

					if (IsValid(self.connected_Basket) and RefinedOutput > 0) then
						self.connected_Basket:Add_Resource(RefinedOutput, self.RefinerType)
						self:Create_InsertEffect(self.RefinerType)
					end

					--Here we send a workamount load of the next valid resource too the connected part if we have one
					if (IsValid(self.connected_module) and self.connected_module:Return_StoredAmount() < self.connected_module.HoldAmount and NextGiveType ~= nil) then
						self:Send_Resources(self.connected_module, NextGiveType, NextGiveAmount)
					end

					-- This makes sure we update our display
					self:NET_UpdateResource()
					self:CreateAnim_Table("idle", 1)
					self:SetCurrentState("WAITING_FOR_ORDER")
				end
			end)
		end
	end)
end

function ENT:MoveOre()
	local SendResource = self:FindValidResource()
	-- Sets the current state to busy
	self:SetCurrentState("TRANSPORTING")
	--Plays the crush sound
	self:CreateEffect_Table(nil, "zrmine_sfx_crush", self, self:GetAngles(), self:GetPos())

	-- Plays the give gravel animation
	if (IsValid(self.connected_module) and self:Return_StoredAmount() > 0) then
		local givegravel_animTime = (1 / 2) * 4.6
		self:Play_GravelGive_anim(givegravel_animTime, 2, SendResource)
	end

	-- Sends the crushed gravel too the connected module
	timer.Simple(2, function()
		if (IsValid(self)) then
			-- Here we send the stored Rest amount to the next module
			if (IsValid(self.connected_module) and self:Return_StoredAmount() > 0) then
				self:Send_Resources(self.connected_module, SendResource, self.WorkAmount)
			end

			-- This makes sure we update our display
			self:NET_UpdateResource()
			self:CreateAnim_Table("idle", 1)
			self:SetCurrentState("WAITING_FOR_ORDER")
		end
	end)
end

-- Utility Function
function ENT:FindValidResource()
	local validResource = nil

	for k, v in pairs(self.StoredResource) do
		local sendAmount = zrmine.f.Calculate_AmountCap(v, self.WorkAmount)

		if (sendAmount > 0) then
			validResource = k
			break
		end
	end

	return validResource
end

-- Construction
-- A Helper funcion for placing a module
function ENT:ConnectModule(amodule)
	local offset = -40

	if (amodule:GetClass() == "zrms_conveyorbelt_c_right") then
		offset = -40
	elseif (amodule:GetClass() == "zrms_conveyorbelt_c_left") then
		offset = -40
	elseif (amodule:GetClass() == "zrms_conveyorbelt_n") then
		offset = -40
	elseif (amodule:GetClass() == "zrms_conveyorbelt_s") then
		offset = -40
	elseif (string.sub(amodule:GetClass(), 1, 12) == "zrms_refiner") then
		offset = -45
	elseif (amodule:GetClass() == "zrms_inserter") then
		offset = -40
	end

	self.connected_module = amodule
	self.connected_module.connected_Parent = self
	self:SetHasChild(true)
	amodule:SetPos(self:GetPos() + self:GetForward() * offset)
	amodule:SetAngles(self:GetAngles())
	amodule:SetBuildMode(false)
	self:SetBuildMode(false)
	self.PhysgunDisabled = true
	local rphys = amodule:GetPhysicsObject()

	if (rphys:IsValid()) then
		rphys:Wake()
		rphys:EnableMotion(false)
	end

	if (amodule:GetClass() == "zrms_inserter") then
		amodule:Inserter_SearchEndPart()
	end

	if (string.sub(amodule:GetClass(), 1, 12) == "zrms_refiner") then
		self:EmitSound("zrmine_sfx_connect_refinery")
	else
		self:EmitSound("zrmine_sfx_connect_belt")
	end

	hook.Run("zrms_Verify_Inserter_connection", self)
end

-- This detaches the the child refinery
function ENT:Detach_Refiner()
	if (IsValid(self.connected_Parent)) then
		self:EmitSound("zrmine_sfx_placecrate")
		self.connected_Parent.connected_module = nil
		self.connected_Parent:SetHasChild(false)
		self.connected_Parent = nil
		self:SetBuildMode(true)
		self.PhysgunDisabled = false
		local phys = self:GetPhysicsObject()

		if (phys:IsValid()) then
			phys:Wake()
			phys:EnableMotion(true)
		end

		hook.Run("zrms_Verify_Inserter_connection", self)
	end
end

function ENT:SpawnBasket()
	local ent = ents.Create("zrms_basket")
	ent:SetAngles(Angle(0, 0, 0))
	ent:SetPos(self:GetPos())
	ent:Spawn()
	ent:Activate()
	ent.PublicEntity = true
	self:Place_Basket(ent)
end

-- This Attaches the Collection Crate to the Refiner
function ENT:Place_Basket(basket)
	if (self.connected_Basket ~= nil) then return end
	if (basket:GetResourceType() ~= "Empty" and basket:GetResourceType() ~= self.RefinerType) then return end
	self:EmitSound("zrmine_sfx_placecrate")
	local pos = self:GetPos() + self:GetUp() * 1 + self:GetRight() * 50
	local ang = self:GetAngles()
	ang:RotateAroundAxis(self:GetUp(), 180)
	basket:SetAngles(ang)
	basket:SetPos(pos)
	basket:SetParent(self)
	local phys = basket:GetPhysicsObject()

	if (phys:IsValid()) then
		phys:Wake()
		phys:EnableMotion(false)
	end

	self.connected_Basket = basket
	self:SetHasBasket(true)
	basket:SetInUse(true)
end

-- This detaches the collection crate
function ENT:Detach_Basket(basket)
	self:EmitSound("zrmine_sfx_placecrate")
	self.connected_Basket = nil
	self:SetHasBasket(false)
	basket:SetInUse(false)
	basket:SetParent(nil)
	local phys = basket:GetPhysicsObject()

	if (phys:IsValid()) then
		phys:Wake()
		phys:EnableMotion(true)
	end
end

function ENT:OnRemove()
	self:Detach_Refiner()
end

-- Plays the give gravel Animation
function ENT:Play_GravelGive_anim(speed, resettime, rtype)
	self:CreateEffect_Table("zrms_refiner_refine", nil, self, self:GetAngles(), self:GetPos() + self:GetForward() * -20 + self:GetUp() * 15)
	zrmine.f.CreateAnimTable(self.outputgravel, "output", speed)

	if (rtype == "Iron") then
		self.outputgravel:SetSkin(1)
	elseif (rtype == "Bronze") then
		self.outputgravel:SetSkin(2)
	elseif (rtype == "Silver") then
		self.outputgravel:SetSkin(3)
	elseif (rtype == "Gold") then
		self.outputgravel:SetSkin(4)
	elseif (rtype == "Coal") then
		self.outputgravel:SetSkin(5)
	else
		self.outputgravel:SetSkin(0)
	end

	timer.Simple(0.1, function()
		if (IsValid(self)) then
			self.outputgravel:SetNoDraw(false)
		end
	end)

	timer.Simple(resettime, function()
		if (IsValid(self)) then
			self.outputgravel:SetNoDraw(true)
		end
	end)
end

-- Plays our refined our animation
function ENT:Play_RefinedGravel_anim(speed, resourceType)
	if (resourceType == "Iron") then
		self.refinedgravel:SetSkin(1)
	elseif (resourceType == "Bronze") then
		self.refinedgravel:SetSkin(2)
	elseif (resourceType == "Silver") then
		self.refinedgravel:SetSkin(3)
	elseif (resourceType == "Gold") then
		self.refinedgravel:SetSkin(4)
	elseif (resourceType == "Coal") then
		self.refinedgravel:SetSkin(5)
	end

	self.refinedgravel:SetBodygroup(0, 0)
	zrmine.f.CreateAnimTable(self.refinedgravel, "refined", speed)

	timer.Simple(0.1, function()
		if (IsValid(self)) then
			self.refinedgravel:SetNoDraw(false)
		end
	end)

	timer.Simple(self.RefiningTime, function()
		if (IsValid(self)) then
			self.refinedgravel:SetNoDraw(true)
		end
	end)
end

-- Damage Stuff
function ENT:OnTakeDamage(dmg)
	self:TakePhysicsDamage(dmg)
	local damage = dmg:GetDamage()
	local entHealth = zrmine.config.Damageable["Refinery"].EntityHealth

	if (entHealth > 0) then
		self.CurrentHealth = (self.CurrentHealth or entHealth) - damage

		if (self.CurrentHealth <= 0) then
			self:Destruct()
			self:Remove()
		end
	end
end

function ENT:Destruct()
	local vPoint = self:GetPos()
	local effectdata = EffectData()
	effectdata:SetStart(vPoint)
	effectdata:SetOrigin(vPoint)
	effectdata:SetScale(1)
	util.Effect("Explosion", effectdata)
end

-- Animation
function ENT:CreateAnim_Table(anim, speed)
	if (self:LookupSequence(anim) == self:GetSequence()) then return end
	zrmine.f.CreateAnimTable(self, anim, speed)
end

--Effects
function ENT:CreateEffect_Table(effect, sound, parent, angle, position)
	zrmine.f.CreateEffectTable(effect, sound, parent, angle, position)
end

function ENT:Create_InsertEffect(rtype)
	zrmine.f.CreateInsertEffect(self, rtype)
end
