-- Special thanks to Wisztom (BossStager.lua in Raid on the Spanish Armada), MaximTGMX and Zax37 (for their tips)

--[[ Inspired by Ninja Gaiden's boss Jaquio ]]--

local INITIAL_HEALTH = 200
local SECOND_PHASE_HEALTH = 150
local THIRD_PHASE_HEALTH = 100
local FINAL_PHASE_HEALTH = 25
local STATE = {
	INIT = 0,
	STAGER = 1,
	PHASE_1 = 2,
	PRE_PHASE_2 = 103,
	PHASE_2 = 3,
	PRE_PHASE_3 = 104,
	PHASE_3 = 4,
	PRE_PHASE_4 = 105,
	PHASE_4 = 5,
	PRE_PHASE_FINAL = 106,
	PHASE_FINAL = 6,
	ENDING = 190,
	RESET = 210,
	DEFEATED = 211
}

function INIT(self)
	self:SetFrame(101)
	self.BumpFlags.Enemy = true
	self.AttackFlags.Player = true
	self.HitFlags.Player = true
	self.AttackRect = {-30, -60, 30, 32}
	self.HitRect = {-25, -55, 25, 25}
	self.detectorHook = GetObject(3206)
	local difficulty = self.detectorHook.Difficulty
	self.projectileDamage = difficulty == 2 and 10 or difficulty == 1 and 2 or 5
	self.Damage = 1
	self.Health = INITIAL_HEALTH
	self.speedXPhase1 = -2
	self.speedYPhase1 = 2
	self.startingX = self.X
	self.startingY = self.Y
	self.XMinPhase1 = self.startingX - 550
    self.XMaxPhase1 = self.startingX + 491
    self.YminPhase1 = self.startingY - 283
    self.YMaxPhase1 = self.startingY + 193
	self.XMaxPhase3 = self.startingX + 11000
	self.YMinPhase3 = self.startingY - 105
	self.XMinPhase4 = self.XMaxPhase3 - 120
	self.XMaxPhase4 = self.XMaxPhase3 + 120
	self.YMinPhase4 = self.YMinPhase3 - 3500
	self.YMaxPhase4 = self.startingY + 353
	self.XMinPhaseF = self.XMinPhase4 - 500
	self.YMinPhaseF = self.startingY - 2903
	self.YMaxPhaseF = self.startingY - 2703
	self.YMinPhaseF2 = self.startingY - 3350
    self.YMaxPhaseF2 = self.startingY - 3150
	self.XMin = self.XMinPhase1
	self.XMax = self.XMaxPhase1
	self.YMin = self.YminPhase1
	self.YMax = self.YMaxPhase1
	self.peg1Pos 			= ffi.new("Point", {self.startingX + 11395, 	self.startingY - 2423	})
	self.peg2Pos 			= ffi.new("Point", {self.startingX + 11331, 	self.startingY - 2551	})
	self.healthPotion1Pos	= ffi.new("Point", {self.startingX - 448, 		self.startingY + 32		})
	self.healthPotion2Pos	= ffi.new("Point", {self.startingX + 384, 		self.startingY + 32		})
	self.healthPotion3Pos	= ffi.new("Point", {self.startingX + 1250, 		self.startingY + 160	})
	self.healthPotion4Pos	= ffi.new("Point", {self.startingX + 11200, 	self.startingY + 160	})
	self.healthPotion5Pos	= ffi.new("Point", {self.startingX + 10692, 	self.startingY - 3220	})
	self.healthPotion6Pos	= ffi.new("Point", {self.startingX + 10692, 	self.startingY - 3800	})
	self.elevatorLongPos	= ffi.new("Point", {self.startingX + 560, 		self.startingY + 256	})
	self.escapeHitTimeDelay = 0
	self.finalPhaseNo = 0
	self.hitTimeDelay = 0
	self.lastAttempt = Attempt()
end

function createPotions(self)
	for n = 1, 6 do
		local key = "healthPotion" .. n
		self[key] = CreateObject{
			X = self[key .. "Pos"].X,
			Y = self[key .. "Pos"].Y,
			Z = 1010,
			name="Arcanis:HealthPotion"
		}
	end
	self.potionsCreated = true
end

function destroyPotions(self)
	if not self.potionsCreated then
		return
	end
	for n = 1, 6 do
		local key = "healthPotion" .. n
		if self[key] ~= nil then
			self[key]:Destroy()
		end
	end
	self.potionsCreated = false
end

------------------------------------------- MOVEMENT PATTERNS -------------------------------------------

function move(self)
	self.X = self.X + self.SpeedX
	self.Y = self.Y + self.SpeedY
end

function bounce(self) -- PHASE_1, PHASE_2 and PHASE_FINAL movement
	if self.Y > self.YMax or self.Y < self.YMin then
		self.SpeedY = -self.SpeedY
		PlaySound("CUSTOM_ARCANIS_FLY1")
	end
	if self.X > self.XMax or self.X < self.XMin then
		self.SpeedX = -self.SpeedX
		PlaySound("CUSTOM_ARCANIS_FLY2")
	end
	move(self)
end


local function escape(self) -- PHASE_3 movement
	local claw = GetClaw()
	if self.X > claw.X + 400 then -- prevent boss from escaping too far
		self.SpeedX = 0
	end
	if self.X < claw.X - 400 then -- prevent boss from being left behind
		self.SpeedX = 5
	end
	if self.X <= claw.X + 400 and self.X >= claw.X - 400 and self.XMax - self.X > 8 then
		self.SpeedX = GetTime() > self.escapeHitTimeDelay and 3 or 5
	end
	bounce(self)
end


local function follow(self) -- PHASE_4 movement
	if self.Y > GetClaw().Y + 160 then
		self.SpeedY = -4
	end
	if self.Y < GetClaw().Y - 160 then
		self.SpeedY = 4
	end
	bounce(self)
end

-------------------------------------------- ATTACK PROJECTILES --------------------------------------------

local PROJ_SPEEDS = {{1, 1}, {1, 2}, {2, 1}, {2, 2}, {3, 1}, {1, 3}, {3, 2}, {2, 3}, {3, 3}, {3, 4}, {4, 3}, {4, 4}}

function createProjectile(self, shootsNb) -- PHASE_1 and PHASE_2 attack
	if not shootsNb then shootsNb = 1 end
	if shootsNb <= 0 then return end

	local rx = math.random(2) == 1 and 1 or -1
	local ry = math.random(2) == 1 and 1 or -1

	local difficulty = self.detectorHook.Difficulty
	local nbOfVariants = difficulty == 2 and 12 or difficulty == 1 and 6 or 9

	local projectileVariant = PROJ_SPEEDS[math.random(nbOfVariants)]
	Vx = rx * projectileVariant[1]
	Vy = ry * projectileVariant[2]

	for nb = 1, shootsNb, 1 do
		local rxp = AND(nb, 1) == 1 and 1 or -1
		local projectile = CreateObject{
			x = 7*rxp + self.X,
			y = self.Y + 24,
			z = 4000,
			name = "Arcanis:Projectile",
			SpeedX = Vx*rxp,
			SpeedY = Vy,
			Damage = self.projectileDamage
		}
		projectile.arcanis = self
	end

	local screen = GetMainPlane().ScreenA

    if self.X > screen.Left - 400 and self.Y > screen.Top - 300 and self.X < screen.Right + 400 and self.Y < screen.Bottom + 300  then
	    PlaySound("CUSTOM_ARCANIS_ATTACK" .. math.random(2))
    end
end

function createSniperProjectile(self, velocity, offsetY, offsetX) -- PHASE_3 and PHASE_4 attack
	local speed = velocity + math.random(10)

	local claw = GetClaw()

	if self.X == claw.X and self.Y == claw.Y then
		return
	end

	local dirX = self.X < claw.X and 1 or self.X > claw.X and -1 or 0
	local dirY = self.Y < claw.Y and 1 or self.Y > claw.Y and -1 or 0

	local angle = math.atan( math.abs(claw.Y + offsetY - self.Y - 32) / math.abs(claw.X + offsetX - self.X) )

	local projectile = CreateObject{
		x = self.X,
		y = self.Y + 32,
		z = 4000,
		name = "Arcanis:Projectile2",
		SpeedX = dirX * math.round(speed*math.cos(angle)),
		SpeedY = dirY * math.round(speed*math.sin(angle)),
		XMax = self.startingX + 11461,
		Damage = self.projectileDamage
	}
	projectile.arcanis = self

	local screen = GetMainPlane().ScreenA

    if self.X > screen.Left - 400 and self.Y > screen.Top - 300 and self.X < screen.Right + 400 and self.Y < screen.Bottom + 300 then
	    PlaySound("CUSTOM_ARCANIS_ATTACK" .. math.random(2))
    end
end


local function createRandomProjectile(self) -- PHASE_FINAL attack
	if math.random(2) == 1 then
		local randomSpeed = math.random(20,40)
		local randomOffsetX = math.random(90) - math.random(90)
		local randomOffsetY = math.random(90) - math.random(90)
		createSniperProjectile(self, randomSpeed, randomOffsetX, randomOffsetY)
	else
		createProjectile(self, 2)
	end
end

------------------------------------------------- MAIN -------------------------------------------------

function main(self)
	local claw = GetClaw()

	if self.State == STATE.INIT then
		INIT(self)
		self.State = STATE.STAGER
		return
	end

	if self.State == STATE.STAGER then
		if not self.stager then
			self.stager = CreateObject{name = "Arcanis:Stager"}
			self.stager.arcanis = self
		end
	end

	 -- General instructions:
	if self.State < STATE.RESET then

		self:AnimationStep()

        -- Updating the boss health bar:
		if self.BossBar ~= nil then
			self.BossBar.I = math.floor(40*self.Health/INITIAL_HEALTH) + 1
			self.BossBar:SetFrame(self.BossBar.I)
		end

		if self.Health <= 0 then
			self.State = STATE.ENDING
		end

		if Attempt() > self.lastAttempt and not self.bossDefeated then
			self.State = STATE.RESET
		end

		if self.Health <= THIRD_PHASE_HEALTH and self.State ~= STATE.PRE_PHASE_3 and not self.thirdPhaseUnlocked then
			self.elevatorLong = CreateObject{
				X = self.elevatorLongPos.X,
				Y = self.elevatorLongPos.Y,
				Z = -5,
				name = "Arcanis:Elevator"
			}
			self.thirdPhaseUnlocked = true
		end

		if self.Health <= FINAL_PHASE_HEALTH and not self.finalUnlocked then
            self.finalUnlocked = true
			self.Peg1 = CreateObject{x=self.peg1Pos.X, y=self.peg1Pos.Y, name="ArcanisPeg"}
			self.Peg2 = CreateObject{x=self.peg2Pos.X, y=self.peg2Pos.Y, name="ArcanisPeg"}
		end
	end

	if self.State == STATE.PHASE_1 then
		bounce(self)

		if self.I == 6 and not self.shooted then
			createProjectile(self, 1)
			self.shooted = true
		elseif self.I == 2 and self.shooted then
			self:SetAnimation"CUSTOM_ARCANIS_ATTACK1"
			self.shooted = false
		end

		if self.Health <= SECOND_PHASE_HEALTH then
			self.State = STATE.PRE_PHASE_2
		end
	end

    if self.State == STATE.PRE_PHASE_2 then
        createPotions(self)
		self:SetAnimation"CUSTOM_ARCANIS_ATTACK2"
        self.State = STATE.PHASE_2
    end

	if self.State == STATE.PHASE_2 then
		bounce(self)

		if self.I == 6 and not self.shooted then
			createProjectile(self, 2)
			self.shooted = true
		elseif self.I == 2 and self.shooted then
			self:SetAnimation"CUSTOM_ARCANIS_ATTACK2"
			self.shooted = false
		end

		if self.Health <= THIRD_PHASE_HEALTH then
			self.State = STATE.PRE_PHASE_3
		end
	end

    if self.State == STATE.PRE_PHASE_3 then
		move(self)

		if self.I == 6 and not self.shooted then
			createProjectile(self, 2)
			self.shooted = true
		elseif self.I == 2 and self.shooted then
			self:SetAnimation"CUSTOM_ARCANIS_ATTACK2"
			self.shooted = false
		end

        if self.Y <= self.YMax - 16 and self.X < self.XMax - 100 then
			self.SpeedY, self.SpeedX = 2, 2
		else
			self.SpeedY, self.SpeedX = 0, 4
			self.XMax, self.YMin = self.XMaxPhase3, self.YMinPhase3
			if self.X - self.startingX > 800 then
				self.SpeedY = 2
				self:SetAnimation"CUSTOM_ARCANIS_ATTACK1"
				self.shooted = true
				self.State = STATE.PHASE_3
			end
		end
    end

	if self.State == STATE.PHASE_3 then
		escape(self)

		if self.I == 6 and not self.shooted then
			if claw.X > self.startingX + 750 then 	-- prevent projectile from flying through the wall
				createSniperProjectile(self, 40, 10, 60)
			end
			self.shooted = true
		elseif self.I == 2 and self.shooted then
			self:SetAnimation"CUSTOM_ARCANIS_ATTACK1"
			self.shooted = false
		end

		if self.XMax - self.X < 8 then -- 8pxs, close enough to the end 
			self.State = STATE.PRE_PHASE_4
		end
	end

    if self.State == STATE.PRE_PHASE_4 then
		move(self)
        self.XMin, self.XMax = self.XMinPhase4, self.XMaxPhase4
		self.YMin, self.YMax = self.YMinPhase4, self.YMaxPhase4
		self.elevatorLong.State = 4
		PlaySound"CUSTOM_ARCANIS_LAUGH2"
		self.SpeedX, self.SpeedY = 2, -3
		self:SetAnimation"CUSTOM_ARCANIS_ATTACK2"
		self.shooted = true
		self.State = STATE.PHASE_4
    end

	if self.State == STATE.PHASE_4 then
		follow(self)

		if self.I == 6 and not self.shooted then
			createSniperProjectile(self, 20, 20, -100)
			self.shooted = true
		elseif self.I == 2 and self.shooted then
			self:SetAnimation"CUSTOM_ARCANIS_ATTACK2"
			self.shooted = false
		end

		if self.Health <= FINAL_PHASE_HEALTH then
			self.State = STATE.PRE_PHASE_FINAL
		end
	end

    if self.State == STATE.PRE_PHASE_FINAL then
		move(self)
        self.SpeedX, self.SpeedY = 0, -4
		self.XMin = self.XMinPhaseF
		self.YMin, self.YMax = self.YMinPhaseF, self.YMaxPhaseF
		self.shooted = true
		self.finalPhaseNo = 1
        self.State = STATE.PHASE_FINAL
    end

	if self.State == STATE.PHASE_FINAL then
		local subPhase = self.finalPhaseNo % 3
		if subPhase == 2 then
			bounce(self)
			if self.I == 6 and not self.shooted then
				createRandomProjectile(self)
				self.shooted = true
			end
			if self.I == 2 and self.shooted then
				self:SetAnimation"CUSTOM_ARCANIS_ATTACK1"
				self.shooted = false
			end
		else
			move(self)
		end
		if subPhase == 1 and self.Y < self.YMax then
			PlaySound"CUSTOM_ARCANIS_QUAKE"
			Earthquake(3000)
			self:SetAnimation"CUSTOM_ARCANIS_ATTACK1"
			self.SpeedY = -2
			self.SpeedX = -2
			self.finalPhaseNo = self.finalPhaseNo + 1

        elseif subPhase == 0 then
            self.SpeedX, self.SpeedY = 0, -4
            local multiplier = self.finalPhaseNo/3 - 1
			local shiftY = 360*multiplier
            self.YMin = self.YMinPhaseF2 - shiftY
            self.YMax = self.YMaxPhaseF2 - shiftY
            self.finalPhaseNo = self.finalPhaseNo + 1
		end
	end

	if self.State == STATE.RESET then
		self.Health = INITIAL_HEALTH
		self.X, self.Y = self.startingX, self.startingY
		self.SpeedX, self.SpeedY = self.speedXPhase1, self.speedYPhase1
		self.XMin = self.XMinPhase1
		self.XMax = self.XMaxPhase1
		self.YMin = self.YminPhase1
		self.YMax = self.YMaxPhase1
		if self.thirdPhaseUnlocked then
			self.elevatorLong:Destroy()
			self.elevatorLong = nil
			self.thirdPhaseUnlocked = false
		end
		destroyPotions(self)
		if self.finalUnlocked then
			self.Peg1:Destroy()
			self.Peg1 = nil
			self.Peg2:Destroy()
			self.Peg2 = nil
			self.finalUnlocked = false
		end
		self.finalPhaseNo = 0
		self:SetAnimation("CUSTOM_ARCANIS_ATTACK1")
		self.lastAttempt = Attempt()
		self.State = STATE.PHASE_1
	end

	if self.State == STATE.ENDING and not self.bossDefeated then
		self.BossBar:SetFrame(1)
		local ending = CreateObject{name = "Arcanis:Ending"}
		ending.arcanis = self
		self.bossDefeated = true
	end

end

function hit(self)
	if GetTime() < self.hitTimeDelay then
		return
	end

	-- breaking berserk mode:
	if self.State == STATE.PHASE_FINAL then
		if self.finalPhaseNo % 3 ~= 2 then
			return
		end
		self.finalPhaseNo = self.finalPhaseNo + 1
		self.hitTimeDelay = GetTime() + 1000
	end

	-- short acceleration in the "Escape" phase:
	if self.State == STATE.PHASE_3 then
		self.escapeHitTimeDelay = GetTime() + 800
	end

	-- Claw can hit the skull max 3 times in 1 sec (except for the final phase)
	if self.State ~= STATE.PHASE_FINAL then
		self.hitTimeDelay = GetTime() + 333
	end

	local clawDmg = GetClaw().Damage
	self.Health = self.Health - (clawDmg <= 16 and clawDmg or 16) -- Claw's damage limit

	CreateObject{
		X = self.X,
		Y = self.Y,
		Z = 8000,
		name = "Arcanis:HitBurst",
		image = "GAME_ENEMYHIT"
	}

	local rand = math.random(4)
	PlaySound("CUSTOM_ARCANIS_HIT" .. rand)
	if rand > 2 then
		PlaySound"GAME_SOLHITHI"
	else
		PlaySound"GAME_SOLHITL2"
	end
end
