local npcManager = require("npcManager")
local npcutils = require("npcs/npcutils")

local onlinePlay = require("scripts/onlinePlay")
local onlinePlayNPC = require("scripts/onlinePlay_npc")
local onlinePlayPlayers = require("scripts/onlinePlay_players")

local battlePlayer = require("scripts/battlePlayer")
local battlePhanto = require("scripts/battlePhanto")

local easing = require("ext/easing")


local phantoFire = {}
local npcID = NPC_ID

local phantoFireSettings = {
	id = npcID,
	
	gfxwidth = 32,
	gfxheight = 32,

	gfxoffsetx = 0,
	gfxoffsety = 4,
	
	width = 16,
	height = 16,
	
	frames = 4,
	framestyle = 0,
	framespeed = 4,
	
	speed = 1,
	
	npcblock = false,
	npcblocktop = false, --Misnomer, affects whether thrown NPCs bounce off the NPC.
	playerblock = false,
	playerblocktop = false, --Also handles other NPCs walking atop this NPC.

	nohurt = true,
	nogravity = true,
	noblockcollision = true,
	nofireball = true,
	noiceball = true,
	noyoshi = true,
	nowaterphysics = true,
	
	jumphurt = true,
	spinjumpsafe = false,
	harmlessgrab = false,
	harmlessthrown = false,

	notcointransformable = true,
	ignorethrownnpcs = true,
	staticdirection = true,
	luahandlesspeed = true,

	grabside = false,
	grabtop = false,

	foreground = true,


	lightcolor = Color.lightblue,
	lightbrightness = 1,
	lightradius = 128,

	ishot = true,


	speed = 6,

	lifetime = 48,

	initialGrowTime = 12,
	lateGrowTime = 12,

	accelerateTime = 16,
	decelerateTime = 32,

	scaleSpeedFactor = 0.25,
	inheritSpeedFactor = 0.2,

	explosionEffectRadius = 64,

	explosionID = Explosion.register(56,113,22,false,true),
}

npcManager.setNpcSettings(phantoFireSettings)
npcManager.registerHarmTypes(npcID,
	{
		--HARM_TYPE_JUMP,
		--HARM_TYPE_FROMBELOW,
		--HARM_TYPE_NPC,
		--HARM_TYPE_PROJECTILE_USED,
		--HARM_TYPE_LAVA,
		--HARM_TYPE_HELD,
		--HARM_TYPE_TAIL,
		--HARM_TYPE_SPINJUMP,
		--HARM_TYPE_OFFSCREEN,
		--HARM_TYPE_SWORD
	},
	{
		[HARM_TYPE_JUMP]            = 10,
		[HARM_TYPE_FROMBELOW]       = 10,
		[HARM_TYPE_NPC]             = 10,
		[HARM_TYPE_PROJECTILE_USED] = 10,
		[HARM_TYPE_LAVA]            = {id=13, xoffset=0.5, xoffsetBack = 0, yoffset=1, yoffsetBack = 1.5},
		[HARM_TYPE_HELD]            = 10,
		[HARM_TYPE_TAIL]            = 10,
		[HARM_TYPE_SPINJUMP]        = 10,
	}
)

battlePhanto.fireNPCID = npcID

function phantoFire.onInitAPI()
	npcManager.registerEvent(npcID, phantoFire, "onTickNPC")
	npcManager.registerEvent(npcID, phantoFire, "onDrawNPC")

	registerEvent(phantoFire, "onTickEnd")
	registerEvent(phantoFire, "onCameraDraw")
	registerEvent(phantoFire, "onExplosion")
	registerEvent(phantoFire, "onPostExplosion")
end


onlinePlayNPC.onlineHandlingConfig[npcID] = {
	synchronisationType = onlinePlayNPC.SYNCHRONISATION.IGNORED,
}


local explosionGradient = Graphics.loadImageResolved("resources/phanto_explosionGradient.png")
local explosionFaceMask = Graphics.loadImageResolved("resources/phanto_explosionFaceMask.png")
local explosionShader = Shader.fromFile(nil,"resources/phanto_explosion.frag")

local explosionEffects = {}


local explosionScale


local claimHitCommand = onlinePlay.createCommand("battle_phanto_fireHit",onlinePlay.IMPORTANCE_MAJOR)

function claimHitCommand.onReceive(sourcePlayerIdx)
	battlePhanto.transferMask(Player(onlinePlay.playerIdx),Player(sourcePlayerIdx),battlePhanto.PUT_ON_STYLE.FORCED_STATE)
end


local function initialise(v,data,config)
	data.sprite = Sprite{texture = Graphics.sprites.npc[v.id].img,frames = npcutils.getTotalFramesByFramestyle(v),pivot = Sprite.align.CENTRE}

	data.timer = 0

	data.baseScale = data.baseScale or 1
	data.scale = 0

	data.inheritSpeed = v.speedX*config.inheritSpeedFactor

	v.x = v.x + v.width*0.5
	v.y = v.y + v.height*0.5
	v.width = 0
	v.height = 0

	data.initialized = true
end


local function canHitPlayer(p,shooterIdx)
	if (not battlePhanto.playerWearableForcedStateMap[p.forcedState] and not p.inClearPipe) or battlePhanto.isWearingMask(p) then
		return false
	end

	if shooterIdx > 0 then
		if shooterIdx == p.idx or battlePlayer.playersAreOnSameTeam(shooterIdx,p.idx) then
			return false
		end
	end

	if p.hasStarman then
		return false
	end

	if p.invincibilityTimer > 0 then
		-- To prevent some really lame cheese, the invincibility you get from respawning does not make you immune to this
		local playerData = battlePlayer.getPlayerData(p)

		if not playerData.invincibilityIsFromRespawn then
			return false
		end
	end

	return true
end


local function doExplosion(v)
	local config = NPC.config[v.id]
	local data = v.data

	local ownerPlayer = Player(v:mem(0x130,FIELD_WORD))

	explosionScale = data.baseScale
	Explosion.spawn(v.x + v.width*0.5,v.y + v.height*0.5,config.explosionID,ownerPlayer)
	explosionScale = nil

	table.insert(explosionEffects,{
		x = v.x + v.width*0.5,
		y = v.y + v.height*0.5,
		radius = config.explosionEffectRadius*data.baseScale,
		scale = data.baseScale,
		randomValue = RNG.random(),
		timer = 0,
	})

	onlinePlayNPC.forceKillNPC(v,HARM_TYPE_VANISH)
end


function phantoFire.onTickNPC(v)
	if Defines.levelFreeze then return end
	
	local data = v.data
	
	if v.despawnTimer <= 0 then
		data.initialized = false
		return
	end

	local config = NPC.config[v.id]

	if not data.initialized then
		initialise(v,data,config)
	end

	-- Don't hurt the player
	if v:mem(0x12E,FIELD_WORD) > 0 then
		v:mem(0x12E,FIELD_WORD,100)
	end

	local ownerPlayer = Player(v:mem(0x130,FIELD_WORD))

	data.timer = data.timer + 1

	data.scale = data.baseScale
	data.scale = data.scale*easing.outQuad(math.min(1,data.timer/config.initialGrowTime),0,1,1)
	data.scale = data.scale*easing.inQuad(math.max(0,data.timer - config.lifetime + config.lateGrowTime),1,0.5,config.lateGrowTime)

	local speedTime = math.max(0,math.min(1,data.timer/config.accelerateTime,(config.lifetime - data.timer - 4)/config.decelerateTime))

	v.speedX = easing.outQuad(speedTime,0,config.speed*v.direction,1)*math.lerp(1,data.baseScale,config.scaleSpeedFactor) + data.inheritSpeed

	local newWidth = config.width*data.scale
	local newHeight = config.height*data.scale

	v.x = v.x + (v.width - newWidth)*0.5
	v.y = v.y + (v.height - newHeight)*0.5
	v.width = newWidth
	v.height = newHeight

	if data.timer >= config.lifetime then
		doExplosion(v)
		return
	end


	-- Hit players
	if v:mem(0x130,FIELD_WORD) > 0 and battlePhanto.isWearingMask(ownerPlayer) then
		for _,p in ipairs(Player.getIntersecting(v.x,v.y,v.x + v.width,v.y + v.height)) do
			--[[if onlinePlayPlayers.ownsPlayer(p) and canHitPlayer(p,v:mem(0x130,FIELD_WORD)) then
				if onlinePlayPlayers.ownsPlayer(p) then
					battlePhanto.transferMask(ownerPlayer,p,battlePhanto.PUT_ON_STYLE.FORCED_STATE)
				end

				-- Disappear
				Effect.spawn(10,v.x + v.width*0.5 - 16,v.y + v.height*0.5 - 16)
				onlinePlayNPC.forceKillNPC(v,HARM_TYPE_VANISH)

				return
			end]]

			if canHitPlayer(p,v:mem(0x130,FIELD_WORD)) then
				doExplosion(v)
				return
			end
		end
	end

	-- Harm NPC's
	local npcs = Colliders.getColliding{a = v,b = NPC.HITTABLE,btype = Colliders.NPC}

	for _,n in ipairs(npcs) do
		if v:mem(0x130,FIELD_WORD) == 0 or n.heldIndex ~= v:mem(0x130,FIELD_WORD) then
			n:harm(HARM_TYPE_NPC)
		end
	end
end


function phantoFire.onDrawNPC(v)
	if v.despawnTimer <= 0 then
		return
	end

	local texture = Graphics.sprites.npc[v.id].img

	if texture == nil then
		return
	end


	local config = NPC.config[v.id]
	local data = v.data

	if not data.initialized then
		initialise(v,data,config)
	end


	local priority = (config.foreground and -15) or -45

	data.sprite.x = v.x + v.width*0.5 + config.gfxoffsetx
	data.sprite.y = v.y + (v.height + config.height - config.gfxheight)*0.5 + config.gfxoffsety*data.scale

	data.sprite.scale = vector.v2(data.scale)

	data.sprite:draw{frame = v.animationFrame+1,priority = priority,sceneCoords = true}
	
	-- Debug
	--Colliders.getHitbox(v):draw()

	npcutils.hideNPC(v)
end


function phantoFire.onTickEnd()
	local i = 1

	while (explosionEffects[i] ~= nil) do
		local effect = explosionEffects[i]

		effect.timer = effect.timer + 1/16

		if effect.timer >= 1 then
			table.remove(explosionEffects,i)
		else
			i = i + 1
		end
	end
end

function phantoFire.onCameraDraw(camIdx)
	for _,effect in ipairs(explosionEffects) do
		--local timer = math.floor(effect.timer*6)/6
		local timer = effect.timer
		local easedTime = easing.outCubic(timer,0,1,1)

		Graphics.drawBox{
			texture = explosionGradient,
			centred = true,sceneCoords = true,
			priority = -5,

			x = effect.x,
			y = effect.y,
			width = effect.radius*2,
			height = effect.radius*2,

			shader = explosionShader,uniforms = {
				faceMaskTexture = explosionFaceMask,
				noiseTexture = Graphics.sprites.hardcoded["53-1"].img,

				timer = timer,
				easedTime = easedTime/1.01,

				radius = effect.radius*0.5,

				randomValue = effect.randomValue,

				size = vector(effect.radius,effect.radius),

				faceMaskSize = vector(explosionFaceMask.width,explosionFaceMask.height)*effect.scale*math.lerp(0.6,2,easedTime),
			},
		}
	end
end


function phantoFire.onExplosion(eventObj,explosion,shooterPlayer)
	if explosion.id ~= NPC.config[npcID].explosionID then
		return
	end

	explosion.collider.radius = explosion.collider.radius*(explosionScale or 1)
end

function phantoFire.onPostExplosion(explosion,shooterPlayer)
	if explosion.id ~= NPC.config[npcID].explosionID then
		return
	end

	if onlinePlay.currentMode == onlinePlay.MODE_OFFLINE then
		-- Find the closest player who got hit (if any)
		local closestSquareDistance = math.huge
		local closestPlayer

		for _,p in ipairs(Player.get()) do
			if explosion.collider:collide(p) and canHitPlayer(p,shooterPlayer.idx) then
				local distX = explosion.collider.x - (p.x + p.width*0.5)
				local distY = explosion.collider.y - (p.y + p.height*0.5)
				local squareDistance = math.sqrt(distX*distX + distY*distY)

				if squareDistance < closestSquareDistance then
					closestSquareDistance = squareDistance
					closestPlayer = p
				end
			end
		end

		if closestPlayer ~= nil then
			battlePhanto.transferMask(shooterPlayer,closestPlayer,battlePhanto.PUT_ON_STYLE.FORCED_STATE)
		end
	else
		-- If we, personally, got hit, then tell the shooter
		local p = Player(onlinePlay.playerIdx)

		if explosion.collider:collide(p) and canHitPlayer(p,shooterPlayer.idx) then
			claimHitCommand:send(shooterPlayer.idx)
		end
	end
end


function battlePlayer.onPlayerHarmCustom(eventObj,p,harmType)
	if explosionScale ~= nil then
		-- Prevent Phanto explosions from actually harming players
		eventObj.cancelled = true
	end
end


return phantoFire