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

local basic3DWorld = require("basic3DWorld")
local lib3d = require("lib3d")

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


local tnt = {}
local npcID = NPC_ID

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

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

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

	grabside = true,
	grabtop = true,


	fuseSound = Misc.resolveSoundFile("tnt_fuse"),
	fuseDuration = 128,

	explosionSounds = {
		Misc.resolveSoundFile("tnt_explode_1"),
		Misc.resolveSoundFile("tnt_explode_2"),
		Misc.resolveSoundFile("tnt_explode_3"),
		Misc.resolveSoundFile("tnt_explode_4"),
	},
}

npcManager.setNpcSettings(tntSettings)
npcManager.registerHarmTypes(npcID,
	{
		HARM_TYPE_FROMBELOW,
		HARM_TYPE_NPC,
		HARM_TYPE_LAVA,
		HARM_TYPE_TAIL,
		HARM_TYPE_SWORD
	},
	{}
)


local igniteCommand = onlinePlayNPC.createNPCCommand("tnt_ignite",onlinePlay.IMPORTANCE_MAJOR)
local explodeCommand = onlinePlayNPC.createNPCCommand("tnt_explode",onlinePlay.IMPORTANCE_MAJOR)

local tntExplosion = Explosion.register(112,176,Misc.resolveSoundFile("resources/nothing"),true,false)

local respawnTime = 512


function tnt.onInitAPI()
	npcManager.registerEvent(npcID, tnt, "onTickNPC")
	npcManager.registerEvent(npcID, tnt, "onDrawNPC")
	npcManager.registerEvent(npcID, tnt, "onPostExplosionNPC")
	registerEvent(tnt, "onPostExplosion")
	registerEvent(tnt, "onNPCHarm")
end


local texture = Graphics.loadImageResolved("npc-".. npcID.. "-main.png")
local baseMaterial = lib3d.Material(Misc.resolveFile("tnt_shader.glsl"),{texture = texture},{},{UNLIT = true,TONEMAP = false,HAS_WHITEN_EFFECT = 1})
local model = lib3d.loadMesh(Misc.resolveFile("tnt_model.obj"),{scale = 32})

local function initialise(v,data,config)
	data.initialized = true

	data.ownerPlayer = nil

	data.isLit = false
	data.fuseTimer = 0
	
	data.scale = 1
	data.whiten = 0

	-- Model
	if not basic3DWorld.useSimpleMode() and (data.mesh == nil or not data.mesh.isValid) then
		data.material = lib3d.Material(baseMaterial)
		data.mesh = lib3d.Mesh{meshdata = model,material = data.material}
	end
end


local function hotNPCFilter(v)
	if not Colliders.FILTER_COL_NPC_DEF(v) then
		return false
	end

	local config = NPC.config[v.id]

	return config.ishot
end

local function ignite(v,data,config)
	data.fuseTimer = config.fuseDuration
	data.isLit = true
	SFX.play(config.fuseSound)
end

local function explode(v,data,config)
	Explosion.spawn(v.x + v.width*0.5,v.y + v.height*0.5,tntExplosion,data.ownerPlayer)
	onlinePlayNPC.forceKillNPC(v,HARM_TYPE_VANISH)
	--v:kill(HARM_TYPE_VANISH)
end

local function shouldBlowUp(v,data,config)
	if not onlinePlayNPC.ownsNPC(v) then
		return false
	end

	if data.fuseTimer <= 0 then
		return true
	end

	-- Hit a wall/ceiling
	if v.collidesBlockUp or v:mem(0x120,FIELD_BOOL) then
		return true
	end

	-- Touching a player
	for _,p in ipairs(Player.getIntersecting(v.x,v.y,v.x + v.width,v.y + v.height)) do
		if p.forcedState == FORCEDSTATE_NONE and p.idx ~= v.heldIndex and p.idx ~= v:mem(0x130,FIELD_WORD) and p ~= data.ownerPlayer then
			return true
		end
	end

	return false
end


function tnt.onTickNPC(v)
	if Defines.levelFreeze then return end
	
	local data = v.data
	
	if v.despawnTimer <= 0 then
		if v:mem(0x06,FIELD_WORD) > respawnTime then -- respawn delay
			v:mem(0x06,FIELD_WORD,respawnTime)
		end

		data.initialized = false
		return
	end

	local config = NPC.config[v.id]

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

	if v:mem(0x12C,FIELD_WORD) > 0 then
		data.ownerPlayer = Player(v:mem(0x12C,FIELD_WORD))
	end

	if data.isLit then
		data.fuseTimer = data.fuseTimer - 1

		if shouldBlowUp(v,data,config) then
			if onlinePlay.currentMode ~= onlinePlay.MODE_OFFLINE then
				explodeCommand:send(v,0)
			end

			explode(v,data,config)
		end

		-- Effects
		local t = 1 - data.fuseTimer/config.fuseDuration

		data.scale = 1 + math.min(0.75,t)*0.5 + math.cos(data.fuseTimer)*0.05
		data.whiten = math.min(1,t*1.25)
	else
		if v:mem(0x12C,FIELD_WORD) > 0 or v:mem(0x136,FIELD_BOOL) then
			if onlinePlay.currentMode ~= onlinePlay.MODE_OFFLINE then
				igniteCommand:send(v,0)
			end

			ignite(v,data,config)
		end

		-- Get lit by fireballs
		local col = Colliders.getHitbox(v)

		col.x = col.x - 6
		col.y = col.y - 6
		col.width = col.width + 12
		col.height = col.height + 12

		for _,n in ipairs(Colliders.getColliding{a = col,btype = Colliders.NPC,filter = hotNPCFilter}) do
			if n.id == 13 then
				if n.ai3 > 0 then
					data.ownerPlayer = Player(n.ai3)
				end

				n:harm(HARM_TYPE_NPC)
			end

			if onlinePlay.currentMode ~= onlinePlay.MODE_OFFLINE then
				igniteCommand:send(v,0)
			end

			ignite(v,data,config)
		end
	end

	-- Slow it down
	local deceleration = (v.collidesBlockBottom and 0.35) or 0.05

	if v.speedX > 0 then
		v.speedX = math.max(0,v.speedX - deceleration)
	elseif v.speedX < 0 then
		v.speedX = math.min(0,v.speedX + deceleration)
	end

	v:mem(0x134,FIELD_WORD,0) -- stop it from dying when getting thrown into walls
end


local mainShader = Shader.fromFile(nil,"resources/solidColor.frag")
local activeNPCs = {}

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

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

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

	if not basic3DWorld.useSimpleMode() then
		if not data.isActive then
			table.insert(activeNPCs,v)
			data.isActive = true
		end

		npcutils.hideNPC(v)

		return
	end

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

	local priority = -45
	if v:mem(0x12C,FIELD_WORD) > 0 then
		priority = -30
	elseif config.foreground then
		priority = -15
	end

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

	Graphics.drawBox{
		texture = texture,priority = priority,
		sceneCoords = true,centred = true,

		x = x,y = y,
		width = config.gfxwidth*data.scale,
		height = config.gfxheight*data.scale,
		sourceWidth = config.gfxwidth,
		sourceHeight = config.gfxheight,
		sourceX = 0,
		sourceY = v.animationFrame*config.gfxheight,

		shader = mainShader,uniforms = {
			color = Color.white,
			extremity = data.whiten,
		},
	}

	npcutils.hideNPC(v)
end


function basic3DWorld.onPreDraw(camIdx,camera3D)
	for i = #activeNPCs,1,-1 do
		local v = activeNPCs[i]
		local data = v.data

		if v.isValid and v.despawnTimer > 0 and data.isActive then
			local mesh = data.mesh

			mesh.transform.position = vector(v.x + v.width*0.5,v.y + v.height,0)
			mesh.transform.scale = vector.v3(data.scale)

			data.material:setUniform("whiten",data.whiten)

			mesh.active = true
		else
			data.isActive = false
			table.remove(activeNPCs,i)
		end
	end
end

function basic3DWorld.onPostDraw(camIdx,camera3D)
	for _,v in ipairs(activeNPCs) do
		local data = v.data

		data.mesh.active = false
	end
end


function tnt.onNPCHarm(eventObj,v,reason,culprit)
	if v.id ~= npcID then
		return
	end

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

	if reason == HARM_TYPE_TAIL or reason == HARM_TYPE_FROMBELOW then
		if type(culprit) == "Player" or type(culprit) == "NPC" then
			if (culprit.x + culprit.width*0.5) > (v.x + v.width*0.5) then
				v.speedX = -5
			else
				v.speedX = 5
			end
		end
		
		v.speedY = -3

		SFX.play(2)
	elseif reason == HARM_TYPE_SWORD then
		for _,p in ipairs(Player.get()) do
			if Colliders.slash(p,v) then
				v.speedX = 4*p.direction
			end
		end

		v.speedY = -3

		SFX.play(9)
	elseif reason == HARM_TYPE_NPC then
		if type(culprit) == "NPC" then
			if culprit:mem(0x136,FIELD_BOOL) then
				if onlinePlay.currentMode ~= onlinePlay.MODE_OFFLINE then
					explodeCommand:send(v,0)
				end

				explode(v,data,config)
				culprit:harm(HARM_TYPE_NPC)
				eventObj.cancelled = true
				return
			end
		else
			eventObj.cancelled = true
			return
		end
	end

	if not data.isLit then
		if onlinePlay.currentMode ~= onlinePlay.MODE_OFFLINE then
			igniteCommand:send(v,0)
		end

		ignite(v,data,config)
	end

	eventObj.cancelled = true
end

function tnt.onPostExplosionNPC(v,ex,culprit)
	if v.despawnTimer <= 0 then
		return
	end

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

	if not data.initialized or not ex.collider:collide(v) then
		return
	end

	local speed = vector((v.x + v.width*0.5) - ex.collider.x,(v.y + v.height*0.5) - ex.collider.y):normalise()*10
	
	v.speedX = speed.x
	v.speedY = speed.y - 6
	v:mem(0x136,FIELD_BOOL,true)

	if not data.isLit then
		if onlinePlay.currentMode ~= onlinePlay.MODE_OFFLINE then
			igniteCommand:send(v,0)
		end

		ignite(v,data,config)
		data.fuseTimer = data.fuseTimer*0.5
	else
		data.fuseTimer = data.fuseTimer*0.9
	end
end


function tnt.onPostExplosion(ex,culprit)
	if ex.id ~= tntExplosion then
		return
	end

	local config = NPC.config[npcID]
	
	local radius = ex.collider.radius
	local x = ex.collider.x
	local y = ex.collider.y


	-- Spawn particles
	for i = 1,30 do
		local pos = vector.randomInCircle(radius)

		local id
		if i%3 == 0 then
			id = 752
		else
			id = 751
		end

		local e = Effect.spawn(id,x + pos.x,y + pos.y)

		e.startTimes[1] = RNG.randomInt(0,16)
		e.framespeed = RNG.randomInt(1,2)
	end

	SFX.play(RNG.irandomEntry(config.explosionSounds))

	Defines.earthquake = 5
end


function explodeCommand.onReceive(v,sourcePlayerIdx)
	if v.id ~= npcID then
		return
	end

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

	explode(v,data,config)
end

function igniteCommand.onReceive(v,sourcePlayerIdx)
	if v.id ~= npcID then
		return
	end

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

	ignite(v,data,config)
end


return tnt