AddCSLuaFile()

ENT.Type            = "anim"
ENT.DoNotDuplicate = true

ENT._LVS = true

function ENT:SetupDataTables()
	self:NetworkVar( "Entity",0, "Base" )
	self:NetworkVar( "Entity",1, "DoorHandler" )

	self:NetworkVar( "Float",1, "HP" )
	self:NetworkVar( "Float",2, "MaxHP" )

	self:NetworkVar( "Bool",0, "Destroyed" )

	if SERVER then
		self:SetMaxHP( 100 )
		self:SetHP( 100 )
	end
end

if SERVER then
	function ENT:Initialize()	
		self:SetMoveType( MOVETYPE_NONE )
		self:SetSolid( SOLID_NONE )
		self:DrawShadow( false )
	end

	function ENT:CheckWater( Base )
		local EntTable = self:GetTable()

		if bit.band( util.PointContents( self:GetPos() ), CONTENTS_WATER ) ~= CONTENTS_WATER then
			if EntTable.CountWater then
				EntTable.CountWater = nil
			end

			return
		end

		if Base.WaterLevelAutoStop > 3 then return end

		EntTable.CountWater = (EntTable.CountWater or 0) + 1

		if EntTable.CountWater < 4 then return end

		Base:StopEngine()
	end

	function ENT:Think()

		local Base = self:GetBase()

		if IsValid( Base ) and Base:GetEngineActive() then
			self:CheckWater( Base )
		end

		self:NextThink( CurTime() + 1 )

		return true
	end

	function ENT:OnDestroyed()
		local effectdata = EffectData()
			effectdata:SetOrigin( self:GetPos() )
		util.Effect( "lvs_trailer_explosion", effectdata, true, true )

		self:EmitSound("physics/metal/metal_box_break"..math.random(1,2)..".wav",75,100,1)

		local base = self:GetBase()

		if not IsValid( base ) then return end

		net.Start( "lvs_car_break" )
			net.WriteEntity( base )
		net.Broadcast()

		if base:GetEngineActive() then
			self:EmitSound("npc/manhack/bat_away.wav",75,100,0.5)

			timer.Simple(1, function()
				if not IsValid( self ) then return end
				self:EmitSound("npc/manhack/gib.wav",75,90,1)
			end)
		end
	
		base:ShutDownEngine()
	end

	function ENT:TakeTransmittedDamage( dmginfo )
		if self:GetDestroyed() then return end

		local Damage = dmginfo:GetDamage()

		if Damage <= 0 then return end

		local CurHealth = self:GetHP()

		local NewHealth = math.Clamp( CurHealth - Damage, 0, self:GetMaxHP() )

		self:SetHP( NewHealth )

		if NewHealth <= 0 then
			self:SetDestroyed( true )

			self:OnDestroyed()
		end
	end

	function ENT:OnTakeDamage( dmginfo )
	end

	function ENT:UpdateTransmitState() 
		return TRANSMIT_ALWAYS
	end

	function ENT:OnRemove()
		local base = self:GetBase()

		if not IsValid( base ) or base.ExplodedAlready then return end

		base:SetMaxThrottle( 1 )
	end

	return
end

ENT._oldEnActive = false
ENT._ActiveSounds = {}

function ENT:Initialize()
end

function ENT:StopSounds()
	for id, sound in pairs( self._ActiveSounds ) do
		if istable( sound ) then
			for _, snd in pairs( sound ) do
				if snd then
					snd:Stop()
				end
			end
		else
			sound:Stop()
		end
		self._ActiveSounds[ id ] = nil
	end
end

function ENT:OnEngineActiveChanged( Active )
	if not Active then self:StopSounds() return end

	for id, data in pairs( self.EngineSounds ) do
		if not isstring( data.sound ) then continue end

		self.EngineSounds[ id ].Pitch = data.Pitch or 100
		self.EngineSounds[ id ].PitchMul = data.PitchMul or 100
		self.EngineSounds[ id ].Volume = data.Volume or 1
		self.EngineSounds[ id ].SoundType = data.SoundType or LVS.SOUNDTYPE_NONE
		self.EngineSounds[ id ].UseDoppler = data.UseDoppler ~= false
		self.EngineSounds[ id ].SoundLevel = data.SoundLevel or 85

		if data.sound_int and data.sound_int ~= data.sound then
			local sound = CreateSound( self, data.sound )
			sound:SetSoundLevel( data.SoundLevel )
			sound:PlayEx(0,100)

			if data.sound_int == "" then
				self._ActiveSounds[ id ] = {
					ext = sound,
					int = false,
				}
			else
				local sound_interior = CreateSound( self, data.sound_int )
				sound_interior:SetSoundLevel( data.SoundLevel )
				sound_interior:PlayEx(0,100)

				self._ActiveSounds[ id ] = {
					ext = sound,
					int = sound_interior,
				}
			end
		else
			local sound = CreateSound( self, data.sound )
			sound:SetSoundLevel( data.SoundLevel )
			sound:PlayEx(0,100)

			self._ActiveSounds[ id ] = sound
		end
	end
end

function ENT:SetGear( newgear )
	self._CurGear = newgear
end

function ENT:GetGear()
	return (self._CurGear or 1)
end

function ENT:SetRPM( rpm )
	self._CurRPM = rpm
end

function ENT:GetRPM()
	local base = self:GetBase()

	if not IsValid( base ) or not base:GetEngineActive() then return 0 end

	return math.abs(self._CurRPM or 0)
end

function ENT:GetClutch()
	return self._ClutchActive == true
end

function ENT:SetEngineVolume( volume )
	self._engineVolume = volume

	return volume
end

function ENT:GetEngineVolume()
	return (self._engineVolume or 0)
end

function ENT:HandleEngineSounds( vehicle )
	local ply = LocalPlayer()
	local pod = ply:GetVehicle()
	local Throttle = vehicle:GetThrottle()
	local MaxThrottle = vehicle:GetMaxThrottle()
	local Doppler = vehicle:CalcDoppler( ply )

	local EntTable = self:GetTable()

	local DrivingMe = ply:lvsGetVehicle() == vehicle

	local IsManualTransmission = vehicle:IsManualTransmission()

	local VolumeSetNow = false

	local FirstPerson = false
	if IsValid( pod ) then
		local ThirdPerson = pod:GetThirdPersonMode()

		if ThirdPerson ~= EntTable._lvsoldTP then
			EntTable._lvsoldTP = ThirdPerson
			VolumeSetNow = DrivingMe
		end

		FirstPerson = DrivingMe and not ThirdPerson
	end

	if DrivingMe ~= EntTable._lvsoldDrivingMe then
		EntTable._lvsoldDrivingMe = DrivingMe

		self:StopSounds()

		EntTable._oldEnActive = nil

		return
	end

	local FT = RealFrameTime()
	local T = CurTime()

	local Reverse = vehicle:GetReverse()
	local vehVel = vehicle:GetVelocity():Length()
	local wheelVel = vehicle:GetWheelVelocity()

	local IsHandBraking = wheelVel == 0 and vehicle:GetNWHandBrake()

	local Vel = 0
	local Wobble = 0

	if vehVel / wheelVel <= 0.8 then
		Vel = wheelVel
		Wobble = -1
	else
		Vel = vehVel
	end

	local NumGears = vehicle.TransGears
	local MaxGear = Reverse and vehicle.TransGearsReverse or NumGears

	local VolumeValue = self:SetEngineVolume( LVS.EngineVolume )
	local PitchValue = vehicle.MaxVelocity / NumGears

	local DesiredGear = 1

	local subGeared = vehVel - (EntTable._smVelGeared or 0)
	local VelocityGeared = vehVel

	if IsHandBraking then
		VelocityGeared = PitchValue * Throttle
		Vel = VelocityGeared
	end

	--[[ workaround ]]-- TODO: Fix it properly
	if vehicle:Sign( subGeared ) < 0 then
		self._smVelGeared = (EntTable._smVelGeared or 0) + subGeared * FT * 5
		VelocityGeared = EntTable._smVelGeared
	else
		EntTable._smVelGeared = VelocityGeared 
	end
	--[[ workaround ]]--


	while (VelocityGeared > PitchValue) and DesiredGear< NumGears do
		VelocityGeared = VelocityGeared - PitchValue

		DesiredGear = DesiredGear + 1
	end

	if IsManualTransmission then
		EntTable._NextShift = 0

		if IsHandBraking then
			DesiredGear = 1
		else
			DesiredGear = vehicle:GetGear()
		end
	else
		DesiredGear = math.Clamp( DesiredGear, 1, MaxGear )
	end

	local CurrentGear = math.Clamp(self:GetGear(),1,NumGears)

	local RatioThrottle = 0.5 + (Throttle ^ 2) * 0.5

	local RatioPitch = math.max(Vel - (CurrentGear - 1) * PitchValue,0)

	if (not IsManualTransmission or IsHandBraking or vehicle.EngineRevLimited) then
		RatioPitch = math.min( PitchValue, RatioPitch )
	end

	local preRatio = math.Clamp(Vel / (PitchValue * (CurrentGear - 1)),0,1)
	local Ratio = (RatioPitch / PitchValue) * RatioThrottle

	if CurrentGear ~= DesiredGear then
		if (EntTable._NextShift or 0) < T then
			EntTable._NextShift = T + vehicle.TransMinGearHoldTime

			if CurrentGear < DesiredGear then
				EntTable._ShiftTime = T + vehicle.TransShiftSpeed
				EntTable._WobbleTime = T + vehicle.TransWobbleTime
			end

			vehicle:OnChangeGear( CurrentGear, DesiredGear )

			self:SetGear( DesiredGear )
		end
	end

	if Throttle > 0.5 then
		local FullThrottle = Throttle >= 0.99

		if EntTable._oldFullThrottle ~= FullThrottle then
			EntTable._oldFullThrottle = FullThrottle

			if FullThrottle then
				EntTable._WobbleTime = T + vehicle.TransWobbleTime
			end
		end

		if Wobble == 0 then
			local Mul = math.Clamp( (EntTable._WobbleTime or 0) - T, 0, 1 )

			Wobble = (math.cos( T * (20 + CurrentGear * 10) * vehicle.TransWobbleFrequencyMultiplier ) * math.max(1 - Ratio,0) * vehicle.TransWobble * math.max(1 - vehicle:AngleBetweenNormal( vehicle:GetUp(), Vector(0,0,1) ) / 5,0) ^ 2) * Mul 
		end
	end

	local FadeSpeed = 0.15
	local PlayIdleSound = CurrentGear == 1 and Throttle == 0 and Ratio < 0.5
	local rpmSet = false
	local rpmRate = PlayIdleSound and 1 or 5

	if IsManualTransmission and (self:GetRPM() < vehicle.EngineIdleRPM or EntTable.ForcedIdle) then
		if EntTable.ForcedIdle then
			self:SetRPM(  vehicle.EngineIdleRPM )
			PlayIdleSound = true
			rpmRate = 1
			EntTable._ClutchActive = true

			if Ratio > 0 or Throttle > 0 then
				EntTable.ForcedIdle = nil
			end
		else
			if Ratio == 0 and Throttle == 0 then
				EntTable.ForcedIdle = true
			end
		end
	end

	EntTable._smIdleVolume = EntTable._smIdleVolume and EntTable._smIdleVolume + ((PlayIdleSound and 1 or 0) - EntTable._smIdleVolume) * FT or 0
	EntTable._smRPMVolume = EntTable._smRPMVolume and EntTable._smRPMVolume + ((PlayIdleSound and 0 or 1) - EntTable._smRPMVolume) * FT * rpmRate or 0

	if (EntTable._ShiftTime or 0) > T or PlayIdleSound then
		PitchAdd = 0
		Ratio = 0
		Wobble = 0
		Throttle = 0
		FadeSpeed = PlayIdleSound and 0.25 or 3
		EntTable._ClutchActive = true
	else
		EntTable._ClutchActive = false
	end

	if IsManualTransmission and IsHandBraking then
		EntTable._ClutchActive = true
	end

	if not EntTable.EnginePitchStep then
		EntTable.EnginePitchStep = math.Clamp(vehicle.EngineMaxRPM / 10000, 0.6, 0.9)

		return
	end

	for id, sound in pairs( EntTable._ActiveSounds ) do
		if not sound then continue end

		local data = EntTable.EngineSounds[ id ]

		local Vol03 = data.Volume * 0.3
		local Vol02 = data.Volume * 0.2

		local Volume = (Vol02 + Vol03 * Ratio + (Vol02 * Ratio + Vol03) * Throttle) * VolumeValue

		local PitchAdd = CurrentGear * (data.PitchMul / NumGears * EntTable.EnginePitchStep) * MaxThrottle

		local Pitch = data.Pitch + PitchAdd + (data.PitchMul - PitchAdd) * Ratio + Wobble
		local PitchMul = data.UseDoppler and Doppler or 1

		if IsManualTransmission and Ratio == 0 and preRatio < 1 and not PlayIdleSound then
			Pitch = (PitchAdd / CurrentGear) * (1 - preRatio) + (data.Pitch + PitchAdd) * preRatio + Wobble
		end

		local SoundType = data.SoundType

		if SoundType ~= LVS.SOUNDTYPE_ALL then
			Volume = Volume  * EntTable._smRPMVolume

			if SoundType == LVS.SOUNDTYPE_IDLE_ONLY then
				Volume = EntTable._smIdleVolume * data.Volume * VolumeValue
				Pitch = data.Pitch + data.PitchMul * Ratio
			end

			if SoundType == LVS.SOUNDTYPE_REV_UP then
				Volume = Throttle == 0 and 0 or Volume
			end

			if SoundType == LVS.SOUNDTYPE_REV_DOWN then
				Volume =  Throttle == 0 and Volume or 0
			end
		end

		if istable( sound ) then
			sound.ext:ChangePitch( math.Clamp( Pitch * PitchMul, 0, 255 ), FadeSpeed )
	
			if sound.int then
				sound.int:ChangePitch( math.Clamp( Pitch, 0, 255 ), FadeSpeed )
			end

			local fadespeed = VolumeSetNow and 0 or 0.15

			if FirstPerson then
				sound.ext:ChangeVolume( 0, 0 )

				if vehicle:HasActiveSoundEmitters() then
					Volume = Volume * 0.25
					fadespeed = fadespeed * 0.5
				end

				if sound.int then sound.int:ChangeVolume( Volume, fadespeed ) end
			else
				sound.ext:ChangeVolume( Volume, fadespeed )
				if sound.int then sound.int:ChangeVolume( 0, 0 ) end
			end
		else
			sound:ChangePitch( math.Clamp( Pitch * PitchMul, 0, 255 ), FadeSpeed )
			sound:ChangeVolume( Volume, 0.15 )
		end

		if rpmSet then continue end

		if PlayIdleSound then self:SetRPM( vehicle.EngineIdleRPM ) rpmSet = true continue end

		if data.SoundType == LVS.SOUNDTYPE_IDLE_ONLY then continue end

		if istable( sound ) then
			if sound.int then
				rpmSet = true
				self:SetRPM( vehicle.EngineIdleRPM + ((sound.int:GetPitch() - data.Pitch) / data.PitchMul) * (vehicle.EngineMaxRPM - vehicle.EngineIdleRPM) )
			else
				if not sound.ext then continue end

				rpmSet = true
				self:SetRPM( vehicle.EngineIdleRPM + ((sound.ext:GetPitch() - data.Pitch) / data.PitchMul) * (vehicle.EngineMaxRPM - vehicle.EngineIdleRPM) )
			end
		else
			rpmSet = true
			self:SetRPM( vehicle.EngineIdleRPM + ((sound:GetPitch() - data.Pitch) / data.PitchMul) * (vehicle.EngineMaxRPM - vehicle.EngineIdleRPM) )
		end
	end
end

function ENT:Think()
	local vehicle = self:GetBase()

	if not IsValid( vehicle ) then return end

	local EntTable = self:GetTable()

	self:DamageFX( vehicle )

	if not EntTable.EngineSounds then
		EntTable.EngineSounds = vehicle.EngineSounds

		return
	end

	local EngineActive = vehicle:GetEngineActive()

	if EntTable._oldEnActive ~= EngineActive then
		EntTable._oldEnActive = EngineActive

		self:OnEngineActiveChanged( EngineActive )
	end

	if EngineActive then
		self:HandleEngineSounds( vehicle )
		self:ExhaustFX( vehicle )
	end
end

function ENT:RemoveFireSound()
	if self.FireBurnSND then
		self.FireBurnSND:Stop()
		self.FireBurnSND = nil
	end

	self.ShouldStopFire = nil
end

function ENT:StopFireSound()
	if self.ShouldStopFire or not self.FireBurnSND then return end

	self.ShouldStopFire = true

	self:EmitSound("ambient/fire/mtov_flame2.wav")

	self.FireBurnSND:ChangeVolume( 0, 0.5 )

	timer.Simple( 1, function()
		if not IsValid( self ) then return end

		self:RemoveFireSound()
	end )
end

function ENT:StartFireSound()
	if self.ShouldStopFire or self.FireBurnSND then return end

	self.FireBurnSND = CreateSound( self, "ambient/fire/firebig.wav" )
	self.FireBurnSND:PlayEx(0,100)
	self.FireBurnSND:ChangeVolume( LVS.EngineVolume, 1 )

	self:EmitSound("ambient/fire/ignite.wav")
end

function ENT:OnRemove()
	self:StopSounds()
	self:RemoveFireSound()
end

function ENT:Draw()
end

function ENT:DrawTranslucent()
end

function ENT:ExhaustFX( vehicle )
	if not istable( vehicle.ExhaustPositions ) then return end

	local T = CurTime()

	if (self.nextEFX or 0) > T then return end

	self.nextEFX = T + 0.1

	vehicle:DoExhaustFX( (self:GetRPM() / vehicle.EngineMaxRPM) * 0.5 + 0.5 * vehicle:GetThrottle() )
end

function ENT:DamageFX( vehicle )
	local T = CurTime()
	local HP = self:GetHP()
	local MaxHP = self:GetMaxHP()

	local EntTable = self:GetTable()

	if HP >= MaxHP * 0.5 then self:StopFireSound() return end

	if (EntTable.nextDFX or 0) > T then return end

	EntTable.nextDFX = T + 0.05

	if self:GetDestroyed() then
		if not EntTable._FireStopTime then
			EntTable._FireStopTime = T + math.random(20,40)
		end

		if EntTable ._FireStopTime < T then
			self:StopFireSound()

			local effectdata = EffectData()
				effectdata:SetOrigin( self:GetPos() )
				effectdata:SetEntity( vehicle )
			util.Effect( "lvs_carengine_blacksmoke", effectdata )

			return
		end

		self:StartFireSound()

		local effectdata = EffectData()
			effectdata:SetOrigin( self:GetPos() )
			effectdata:SetEntity( vehicle )
		util.Effect( "lvs_carengine_fire", effectdata )
	else
		EntTable._FireStopTime = nil

		self:StopFireSound()

		local effectdata = EffectData()
			effectdata:SetOrigin( self:GetPos() )
			effectdata:SetEntity( vehicle )
			effectdata:SetMagnitude( math.max(HP,0) / (MaxHP * 0.5) )
		util.Effect( "lvs_carengine_smoke", effectdata )
	end
end

