library HunterCamera initializer ini
	import Math
	import Units
	import Players
	import Game

	native UnitAlive takes unit u returns boolean

	constant real CLOCK_PERIOD = 1. / 3.

	constant real OFFSET_STATIC  = 300.
	constant real OFFSET_JITTER  = 80.
	constant real OFFSET_DYNAMIC = 400.

	constant real JITTER_THRESHOLD = bj_PI / 3.

	constant integer KERNEL_SIZE = 4

	// filter kernel for estimating jitter
	real array kernel
	integer array kernelIndex

	function kernelApply takes integer index, real facing returns real
		real min = bj_PI
		real max = -1. * bj_PI
		integer i = 0

		// append kernel
		facing = Math.radiansNormalizeHalf(facing)
		kernel[index * KERNEL_SIZE + kernelIndex[index]] = facing

		// fetch minimax
		loop
			exitwhen i >= KERNEL_SIZE
			if i != index then
				min = Math.minReal(min, Math.radiansNormalizeHalf(facing - kernel[index * KERNEL_SIZE + i]))
				max = Math.maxReal(max, Math.radiansNormalizeHalf(facing - kernel[index * KERNEL_SIZE + i]))
			end
			i++
		end

		// increment kernel index
		kernelIndex[index]++
		kernelIndex[index] = ModuloInteger(kernelIndex[index], KERNEL_SIZE)

		return Math.radiansNormalizeHalfModulo(max - min)
	end

	function act takes nothing returns nothing
		integer index = 0
		real facing
		real minimax
		real offset = OFFSET_STATIC

		//if (not Cinematic.enabled) then
		if Game.roundEnabled then
			loop
				exitwhen index >= Players.FIRST_BEAST
				if UnitAlive(Units.hero[index]) then
					facing = GetUnitFacing(Units.hero[index]) * bj_DEGTORAD
					minimax = kernelApply(index, facing)
					if (minimax > JITTER_THRESHOLD) then
						offset = OFFSET_JITTER
					elseif (GetUnitCurrentOrder(Units.hero[index]) == OrderId("smart")) then
						offset = OFFSET_DYNAMIC
					end
					if Players.locl == Players.fromId[index] then
						PanCameraToTimed(GetUnitX(Units.hero[index]) + offset*Cos(facing), GetUnitY(Units.hero[index]) + offset*Sin(facing), CLOCK_PERIOD*2.)
					end
				end
				index++
			end
		end
	end

	public struct HunterCamera
		static method resetKernel takes nothing returns nothing
			integer index = 0
			integer j

			loop
				exitwhen index >= Players.COUNT
				kernelIndex[index] = 0
				j = 0

				loop
					exitwhen j >= KERNEL_SIZE

					kernel[index * KERNEL_SIZE + j] = 270.

					j++
				end
				index++
			end
		end
	end

	function ini takes nothing returns nothing
		TimerStart(CreateTimer(), CLOCK_PERIOD, true, function act)
		HunterCamera.resetKernel()
	end
endlibrary
