library ProjectileStack
	import TerrainData
	import Units
	import DamageType
	import Players

	native UnitAlive takes unit u returns boolean

	struct ProjectileDat
		unit projectile
		unit damageSource
		real speed
		real dx
		real dy
		real damage
		real rangeLeft
	end

	public struct ProjectileStack
		private static constant string DUST_FX             = "Objects\\Spawnmodels\\Undead\\ImpaleTargetDust\\ImpaleTargetDust.mdl"
		private static constant string ERROR_NO_PROJECTILE = "Error: projectile was not found for attacking unit! Reverting to Hunter projectile."
		private static constant string ERROR_NO_SPEED      = "Error: projectile speed was not found! Reverting to 20."
		private static constant string ERROR_NO_DAMAGE     = "Error: projectile damage was not found! Reverting to 10."

		private static constant real PROJECTILE_OFFSET   =   50.
		private static constant real DEFAULT_VELOCITY    =   20.
		private static constant real BULLET_ENUM_RADIUS  =   75.
		private static constant real PROJECTILE_VELOCITY =   25.
		private static constant real PROJECTILE_DAMAGE   =   30.
		private static constant real DEFAULT_DAMAGE      =   10.
		private static constant real CLOCK_PERIOD        =    1. / 30.
		private static constant real FLY_HEIGHT          =   60.
		private static constant real RANGE               = 1200.

		private static timer time = CreateTimer()
		private static ProjectileDat array projectileDB
		private static integer dbIndex = -1
		private static group g = CreateGroup()

		private static method getUnitProjectile takes unit u, real sX, real sY, real tX, real tY returns unit
			integer uType = GetUnitTypeId(u)
			real facing = TerrainData.radiansBetweenXY(sX, tX, sY, tY)
			real x = sX + PROJECTILE_OFFSET*Cos(facing)
			real y = sY + PROJECTILE_OFFSET*Sin(facing)
			unit returnedUnit
			if uType == Units.HUNTER_ID then
				returnedUnit = CreateUnit(GetOwningPlayer(u), Units.HUNTER_PROJECTILE_ID, 0., 0., facing*bj_RADTODEG)
				SetUnitX(returnedUnit, x)
				SetUnitY(returnedUnit, y)
				return returnedUnit
			end
			debug DisplayTextToPlayer(Players.locl, 0., 0., ERROR_NO_PROJECTILE)
			returnedUnit = CreateUnit(GetOwningPlayer(u), Units.HUNTER_PROJECTILE_ID, 0., 0., GetUnitFacing(u))
			SetUnitX(returnedUnit, x)
			SetUnitY(returnedUnit, y)
			return returnedUnit
		end

		private static method getProjectileSpeed takes unit u returns real
			integer uType = GetUnitTypeId(u)
			if uType == Units.HUNTER_PROJECTILE_ID then
				return PROJECTILE_VELOCITY
			end
			debug DisplayTextToPlayer(Players.locl, 0., 0., ERROR_NO_SPEED)
			return DEFAULT_VELOCITY
		end

		private static method getUnitDamage takes unit u returns real
			integer uType = GetUnitTypeId(u)
			if uType == Units.HUNTER_ID then
				return PROJECTILE_DAMAGE
			end
			debug DisplayTextToPlayer(Players.locl, 0., 0., ERROR_NO_DAMAGE)
			return DEFAULT_DAMAGE
		end

		private static method bulletTick takes nothing returns nothing
			ProjectileDat tempDat
			integer index = 0
			real x
			real y
			real cz
			real nz
			unit iter
			boolean killBullet = false

			loop
				exitwhen index > dbIndex
				tempDat = projectileDB[index]
				x = GetUnitX(tempDat.projectile) + tempDat.dx
				y = GetUnitY(tempDat.projectile) + tempDat.dy
				nz = TerrainData.getZ(x, y)
				cz = TerrainData.getZ(GetUnitX(tempDat.projectile), GetUnitY(tempDat.projectile))
				nz = nz - cz
				SetUnitFlyHeight(tempDat.projectile, GetUnitFlyHeight(tempDat.projectile) - nz, 0.)
				SetUnitX(tempDat.projectile, x)
				SetUnitY(tempDat.projectile, y)

				tempDat.rangeLeft = tempDat.rangeLeft - tempDat.speed
				if tempDat.rangeLeft <= 0. then
					killBullet = true
				end

				GroupEnumUnitsInRange(g, x, y, BULLET_ENUM_RADIUS, null)
				loop
					iter = FirstOfGroup(g)
					exitwhen iter == null

					if IsUnitEnemy(tempDat.projectile, GetOwningPlayer(iter)) and UnitAlive(iter) then
						DamageType.dealCodeDamage(tempDat.damageSource, iter, tempDat.damage)
						killBullet = true
					end
					GroupRemoveUnit(g, iter)
				end

				if GetUnitX(tempDat.projectile) > TerrainData.MAX_X or GetUnitX(tempDat.projectile) < TerrainData.MIN_X or GetUnitY(tempDat.projectile) > TerrainData.MAX_Y or GetUnitY(tempDat.projectile) < TerrainData.MIN_Y then
					killBullet = true
				end

				if GetUnitFlyHeight(tempDat.projectile) < 1. then
					DestroyEffect(AddSpecialEffect(DUST_FX, x, y))
					killBullet = true
				end

				if killBullet then
					KillUnit(tempDat.projectile)
					destroy tempDat
					projectileDB[index] = projectileDB[dbIndex]
					dbIndex--

					if dbIndex == -1 then
						PauseTimer(time)
					end

					killBullet = false
					index--
				end

				index++
			end
		end

		static method add takes unit u, real sX, real sY, real tX, real tY, real offset returns nothing
			real rad
			ProjectileDat tempDat = new ProjectileDat()
			tempDat.projectile      = getUnitProjectile(u, sX, sY, tX, tY)
			tempDat.damageSource    = u
			tempDat.speed           = getProjectileSpeed(tempDat.projectile)
			rad                     = TerrainData.radiansBetweenXY(sX, tX, sY, tY) + offset
			tempDat.dx              = tempDat.speed * Cos(rad)
			tempDat.dy              = tempDat.speed * Sin(rad)
			tempDat.damage          = getUnitDamage(u)
			tempDat.rangeLeft       = RANGE

			SetUnitFlyHeight(tempDat.projectile, FLY_HEIGHT, 0.)

			dbIndex++
			projectileDB[dbIndex] = tempDat
			if dbIndex == 0 then
				TimerStart(time, CLOCK_PERIOD, true, function bulletTick)
			end
		end
	end
endlibrary
