package Spinner
import EscaperSensor
import SpinnerObjects


class SpinNode
	SpinNode next = null
	SpinNode prev = null
	SetupObject obj
	real angl
	real dist

	construct(SetupObject ent, real ang, real dist)
		obj = ent
		angl = ang
		this.dist = dist

	ondestroy
		if next != null
			next.prev = prev
		if prev != null
			prev.next = next
		if not obj.done and obj.actor != null
			obj.setup.addAbility(REMOVE_OBJECT_ID)

function superformulaRadius(real t, real a, real b, real m, real n1, real n2, real n3) returns real
    let part1 = (1 / a) * Cos(m * t / 4)
    let part2 = (1 / b) * Sin(m * t / 4)
    let absPart1PowN2 = Pow((part1).abs(), n2)
    let absPart2PowN3 = Pow((part2).abs(), n3)
    let r = Pow(absPart1PowN2 + absPart2PowN3, -1 / n1)
    return r

function superformula(real t, real a, real b, real m, real n1, real n2, real n3, real n4) returns real
	let part1 = Pow((Cos(m * t / 4) / a).abs(), n2)
	let part2 = Pow((Sin(m * t / 4) / b).abs(), n3)
	let part3 = Pow(part1 + part2, -1 / n1)
	let part4 = Pow((Cos(m * t / 4) / a).abs(), n4)
	let part5 = Pow((Sin(m * t / 4) / b).abs(), n4)
	let part6 = Pow(part4 + part5, -1 / n1)
	return Pow(part3 + part6, -1 / n1)

public class Spinner extends SetupObject
	SpinNode firstNode = null
	let spinSpeed = new ConfigValue(2 * DEGTORAD, 0.25 * DEGTORAD, "Spinspeed")
	let cosFactor = new ConfigValue(1, 0.1, "Cos-Factor")
	let sinFactor = new ConfigValue(1, 0.1, "Sin-Factor")
	var useSuperFormula = false

	construct(unit existing)
		super(createUnit(existing.getOwner(), SPINNER_ID, existing.getPos(), angle(0)), existing, 0)
		sleeps = false
		setup..addAbility(TURN_OFF_ID)..addAbility(GHOST_INVIS_ID)
		EventListener.add(setup, EVENT_PLAYER_UNIT_SPELL_EFFECT, () -> onCast())

	construct(vec2 pos, player owner)
		super(createUnit(owner, SPINNER_ID, pos, angle(0)), createUnit(owner, SPINNER_ID, pos, angle(0)), 0)
		sleeps = false
		setup.addAbility(TURN_OFF_ID)
		EventListener.add(setup, EVENT_PLAYER_UNIT_SPELL_EFFECT, () -> onCast())

	override function update()
		super.update()
		let speed = spinSpeed.get()
		var node = firstNode
		while node != null
			if not node.obj.done and node.obj.actor != null
				if useSuperFormula
					// Use superformula curve
					let a = 1.0
					let b = 1.0
					let m = 4.0
					let n1 = 30.0
					let n2 = 15.0
					let n3 = 15.0

					let r = superformulaRadius(node.angl, a, b, m, n1, n2, n3)
					node.obj.setXY(vec3(pos.x + r * node.dist * Cos(node.angl) * cosFactor.get(), pos.y + r * node.dist * Sin(node.angl) * sinFactor.get(), 0.))

				else
					// Use normal circle
					node.obj.setXY(vec3(pos.x + node.dist * Cos(node.angl) * cosFactor.get(), pos.y + node.dist * Sin(node.angl) * sinFactor.get(), 0.))

				if enabled
					node.angl += speed
			else
				let tmp = node
				if node == firstNode
					firstNode = node.next
				destroy tmp
			node = node.next

	function checkObj(Entity e)
		if e instanceof SetupObject and not e instanceof StaticEscaperSensorSetup
			let dobj = e castTo SetupObject
			boolean isNew = true
			var node = firstNode
			while node != null
				if node.obj == dobj
					isNew = false
					if firstNode == node
						firstNode = node.next
					destroy node
					break
				node = node.next
			if isNew and (dobj.setup.hasAbility(REMOVE_OBJECT_ID) or dobj.owner == STAFF_PLAYER)
				dobj.setup.removeAbility(REMOVE_OBJECT_ID)
				let newnode = new SpinNode(dobj, getPos().angleTo2d(dobj.getPos()).radians(), getPos().distanceTo(dobj.getPos()))
				if firstNode != null
					firstNode.prev = newnode
					newnode.next = firstNode
				firstNode = newnode

	ondestroy
		var node = firstNode
		firstNode = null
		while node != null
			let tmp = node
			node = node.next
			destroy tmp

		destroy cosFactor
		destroy sinFactor
		destroy spinSpeed

	function onCast()
		let id = GetSpellAbilityId()
		switch id
			case TURN_ON_ID
				setEnabled(true)
			case TURN_OFF_ID
				setEnabled(false)
			case SPIN_ADD_SINGLE_ID
				let u = GetSpellTargetUnit()
				let data = u.getEntity()
				if owner == owner
					checkObj(data)
			case SPIN_ADD_GROUP_ID
				forUnitsInRange(EventData.getSpellTargetPos(), 300.) u ->
					if u != actor and u != setup
						if u.getUserData() > 0
							let ed = u.getEntity()
							if ed.owner == owner
								checkObj(ed)
			case SPIN_INC_SPEED_ID
				spinSpeed.increment(this, 10 * DEGTORAD)
			case SPIN_DEC_SPEED_ID
				spinSpeed.decrement(this, -10 * DEGTORAD)
			case SPIN_COS_INC_ID
				cosFactor.increment(this, 1)
			case SPIN_COS_DEC_ID
				if cosFactor.get() == 0. and sinFactor.get() == 0.
					useSuperFormula = not useSuperFormula
				else
					cosFactor.decrement(this, 0)
			case SPIN_SIN_INC_ID
				sinFactor.increment(this, 1)
			case SPIN_SIN_DEC_ID
				sinFactor.decrement(this, 0)

	override function serialize() returns Json
		let json = super.serialize()
		json.addProperty(new Property(KEY_TYPE, SPINNER_INDEX.toString()))
		return json