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

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

local phantoCollection = require("maskCollection/scripts/phantoCollection")
local tilemap = require("maskCollection/scripts/tilemap")

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

local easing = require("ext/easing")
local spline = require("spline")


local redPhanto = {}
local npcID = NPC_ID

local redPhantoSettings = {
	id = npcID,

	gfxwidth = 32,
	gfxheight = 32,

	width = 32,
	height = 32,

	gfxoffsetx = 0,
	gfxoffsety = 0,

	frames = 8,
	framestyle = 0,
	framespeed = 8,

	luahandlesspeed = false,
	nowaterphysics = false,
	cliffturn = false,
	staticdirection = true,

	npcblock = false,
	npcblocktop = false,
	playerblock = false,
	playerblocktop = false,

	nohurt = false,
	nogravity = true,
	noblockcollision = false,
	notcointransformable = true,
	nofireball = false,
	noiceball = false,
	noyoshi = true,

	jumphurt = false,
	spinjumpsafe = false,
	harmlessgrab = false,
	harmlessthrown = false,
	nowalldeath = false,

	linkshieldable = false,
	noshieldfireeffect = false,

	grabside = false,
	grabtop = false,

	isinteractable = true,

	terminalvelocity = -1,


	wornImage = Graphics.loadImageResolved("resources/phanto_mask_red.png"),
	wornAuraEffectColor = Color.red,

	pathMoveDuration = 80,

	launchInitialSpeed = 10,
	launchDeceleration = 0.15,


	floatCycleDuration = 128,
	floatMagnitude = 4,

	appearSpinCycles = 2,
	appearSpinDuration = 64,

	launchSpinCycles = 3,
	launchSpinDuration = 96,

	launchMaxFloorDistance = 160,

	launchCollectCooldown = 12,
}

npcManager.setNpcSettings(redPhantoSettings)


onlinePlayNPC.onlineHandlingConfig[npcID] = {
	synchronisationType = onlinePlayNPC.SYNCHRONISATION.NO_UPDATES,
	--updateFrequency = 0,
	
	useFairCollection = true,

	getExtraData = function(v)
		local data = v.data

		if data.wasLaunched then
			return {
				wasLaunched = true,
				launchPlayerIdx = data.launchPlayerIdx or 0,
			}
		end

		return {
			wasLaunched = false,
			goalX = data.goalX,
			goalY = data.goalY,
		}
	end,
	setExtraData = function(v,receivedData)
		local data = v.data

		if not data.initialised then
			if receivedData.wasLaunched then
				data.wasLaunched = true
				data.launchPlayerIdx = receivedData.launchPlayerIdx
			else
				data.wasLaunched = false
				data.goalX = receivedData.goalX
				data.goalY = receivedData.goalY
			end

			redPhanto.initialise(v)
		end
	end,

	shouldStealFunc = function(v)
		return false
	end,
}


battlePhanto.takenOffNPCFunctions[npcID] = function(v,p)
	if p.data.maskCollection ~= nil and p.data.maskCollection.groundPosition ~= nil then
		local data = v.data

		local groundPosition = p.data.maskCollection.groundPosition
		local sourceX = groundPosition.x
		local sourceY = groundPosition.y - 32
		local goalX = groundPosition.x
		local goalY = groundPosition.y - 64

		local hit,hitPointX,hitPointY,_,_ = tilemap.lineCast(sourceX,sourceY,goalX,goalY)

		if hit then
			data.goalX = hitPointX
			data.goalY = hitPointY
		else
			data.goalX = goalX
			data.goalY = goalY
		end
	end

	redPhanto.initialise(v)
end

battlePhanto.launchNPCFunctions[npcID] = function(v,p,launchDirection)
	local config = NPC.config[v.id]
	local data = v.data

	v.speedX = config.launchInitialSpeed*launchDirection.x
	v.speedY = config.launchInitialSpeed*launchDirection.y

	data.launchPlayerIdx = p.idx
	data.wasLaunched = true

	redPhanto.initialise(v)
end


function redPhanto.onInitAPI()
	npcManager.registerEvent(npcID, redPhanto, "onTickNPC")
	npcManager.registerEvent(npcID, redPhanto, "onDrawNPC")
	
	registerEvent(redPhanto, "onNPCCollect")
	registerEvent(redPhanto, "onPostNPCCollect")
end


local STATE = {
	MOVE_TO_GOAL = 0,
	LAUNCHED = {
		MOVE = 1,
		STILL = 2,
	},
}


local function isDoingFairCollection(v)
	if onlinePlay.currentMode == onlinePlay.MODE_OFFLINE then
		return false
	end

    local onlineData = onlinePlayNPC.getData(v)

    return onlineData.isDoingFairCollection
end


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

	data.goalX = data.goalX or (v.x + v.width*0.5)
	data.goalY = data.goalY or (v.y + v.height*0.5)

	data.splineTimer = 0

	data.floatTimer = RNG.random(0,config.floatCycleDuration)
	data.floatOffset = 0

	data.wasLaunched = data.wasLaunched or false
	data.launchPlayerIdx = data.launchPlayerIdx or 0
	data.launchCollectCooldown = (data.wasLaunched and config.launchCollectCooldown) or 0

	if data.wasLaunched then
		data.state = STATE.LAUNCHED.MOVE

		data.spinCycles = config.launchSpinCycles
		data.spinDuration = config.launchSpinDuration
	else
		data.state = STATE.MOVE_TO_GOAL

		data.splineSegment = spline.segment{
			startTan = vector(0,-100),
			stopTan = vector(0,50),
			start = vector(v.x + v.width*0.5,v.y + v.height*0.5),
			stop = vector(data.goalX,data.goalY),
		}

		data.spinCycles = config.appearSpinCycles
		data.spinDuration = config.appearSpinDuration

		v.noblockcollision = true
	end

	data.oldSpeedY = v.speedY

	data.spinTime = data.spinDuration

	data.initialised = true
end


function redPhanto.onTickNPC(v)
	if Defines.levelFreeze then return end

	local data = v.data

	if v.despawnTimer <= 0 then
		data.initialised = false
		return
	end

	local config = NPC.config[v.id]

	if not data.initialised then
		redPhanto.initialise(v)
	end

	if data.state == STATE.MOVE_TO_GOAL then
		if data.splineTimer < 1 then
			data.splineTimer = math.min(1,data.splineTimer + 1/config.pathMoveDuration)

			local point = data.splineSegment:evaluate(easing.outQuad(data.splineTimer,0,1,1))

			v.x = point.x - v.width*0.5
			v.y = point.y - v.height*0.5
		end
	elseif data.state == STATE.LAUNCHED.MOVE then
		local speed = vector(v.speedX,v.speedY)

		speed = speed:normalise()*math.max(0,speed.length - config.launchDeceleration)

		v.speedX = speed.x
		v.speedY = speed.y

		data.launchCollectCooldown = math.max(0,data.launchCollectCooldown - 1)

		-- Bouncing off of things
		if not v.collidesBlockLeft and not v.collidesBlockRight then
			v:mem(0x120,FIELD_BOOL,false)
		end

		if v.collidesBlockUp then
			v.speedY = math.abs(data.oldSpeedY)
		elseif v.collidesBlockBottom then
			v.speedY = -math.abs(data.oldSpeedY)
		end

		data.oldSpeedY = v.speedY

		-- Get collected
		if data.launchPlayerIdx > 0 and onlinePlayNPC.ownsNPC(v) and v.killFlag == 0 then
			for _,block in Block.iterateIntersecting(v.x,v.y,v.x + v.width,v.y + v.height) do
				if not block.isHidden and block.id == 752 then
					phantoCollection.collectFromNPC(v,data.launchPlayerIdx,vector(block.x + block.width*0.5,block.y - 16),vector(0,-1))
					break
				end
			end
		end

		-- Stop moving
		if v.speedX == 0 and v.speedY == 0 then
			local floorY = tilemap.boxCastDown(v.x,v.width, v.y + v.height*0.5,v.sectionObj.boundary.bottom, true)

			if floorY ~= nil and (v.y + v.height*0.5) < (floorY - config.launchMaxFloorDistance) then
				data.state = STATE.MOVE_TO_GOAL

				data.splineSegment = spline.segment{
					startTan = vector(0,0),
					stopTan = vector(0,200),
					start = vector(v.x + v.width*0.5,v.y + v.height*0.5),
					stop = vector(v.x + v.width*0.5,floorY - config.launchMaxFloorDistance),
				}
			else
				data.state = STATE.LAUNCHED.STILL
			end

			v.noblockcollision = true
		end
	end

	data.spinTime = math.max(0,data.spinTime - 1)

	data.floatTimer = data.floatTimer + 1
	data.floatOffset = math.sin(data.floatTimer/config.floatCycleDuration*math.pi*2)*config.floatMagnitude
end

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

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

	if not data.initialised then
		redPhanto.initialise(v)
	end

	local image = Graphics.sprites.npc[v.id].img
	local x = v.x + (v.width - config.gfxwidth)*0.5 + config.gfxoffsetx
	local y = v.y + v.height - config.gfxheight + config.gfxoffsety + data.floatOffset

	local opacity = (isDoingFairCollection(v) and 0.5) or 1

	local frame = 0

	if data.spinTime > 0 then
		frame = math.floor(easing.inCubic(data.spinTime,0,-config.frames*data.spinCycles*v.direction,data.spinDuration) + 0.5)%config.frames
	end	

	v.animationFrame = npcutils.getFrameByFramestyle(v,{frame = frame})

	Graphics.drawImageToSceneWP(image,x,y,0,v.animationFrame*config.gfxheight,config.gfxwidth,config.gfxheight,opacity,-46)

	npcutils.hideNPC(v)
end


local function playerIsDead(p)
	local playerData = battlePlayer.getPlayerData(p)

	return playerData.isDead
end

function redPhanto.onNPCCollect(eventObj,v,p)
	if v.id ~= npcID then
		return
	end

	local data = v.data

	if battlePhanto.isWearingMask(p) or (data.wasLaunched and data.launchPlayerIdx == p.idx and data.launchCollectCooldown > 0) or playerIsDead(p) then
		eventObj.cancelled = true
	end
end

function redPhanto.onPostNPCCollect(v,p)
	if v.id ~= npcID then
		return
	end

	battlePhanto.putOnMask(p,v.id,battlePhanto.PUT_ON_STYLE.KEEP_MOVEMENT,v.x + v.width*0.5,v.y + v.height*0.5)

	if onlinePlayPlayers.ownsPlayer(p) then
		maskCollection.progressGuide(maskCollection.GUIDE_STATE.PHANTOS,maskCollection.GUIDE_STATE.PIPE)
	end
end


return redPhanto