local warphub = {}

-- warphub.lua v1.1
-- Created by SetaYoshi

-- SFX list
local sfx_openmenu = 13
local sfx_closemenu = 35
local sfx_warpmenu = 12
local sfx_scrollmenu = 29

local npcID = NPC_ID

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

local cps = require("checkpoints")
local cpai = require("npcs/AI/checkpoints")
cps.registerNPC(npcID)
cpai.addID(npcID, true)

SaveData.warphub = SaveData.warphub or {}
SaveData.warphub[Level.filename()] = SaveData.warphub[Level.filename()] or {}
local SD = SaveData.warphub[Level.filename()]

local config = npcManager.setNpcSettings({
	id = npcID,
	gfxwidth = 56,
	gfxheight = 64,
	width = 56,
	height = 64,
	frames = 3,
	framespeed = 8,
	score = 0,
	speed = 0,
	playerblock = false,
	npcblock = false,
	nogravity = true,
	noblockcollision = true,
	nofireball = true,
	noiceball = true,
	noyoshi = true,
	grabside = false,
	isshoe = false,
	isyoshi = false,
	nohurt = true,
	iscoin = false,
	jumphurt = true,
	spinjumpsafe = false,
	notcointransformable = true,

  -- The title in the list
  title = "- Warp List -",
	boxwidth = 200,

	-- Change this to true, and you can warp to any warp at any time without having it to be activated
	unlockwarps = false,

	-- Change this to true and the player can hold drop item + down to open the warp menu anytime
	pocketwarp = false,

	-- Change this to true and the warphubs will stop behaving like checkpoints
	disablecheckpoint = false,

	-- Always show the screen where the checkpoint is at
	allowpreview = false
})

-- Get npc object based on warp ID
local function getNPCbyID(idx)
  for _, n in ipairs(NPC.get(npcID)) do
    if n.data.id == idx then
			return n
		end
	end
end

-- Save a collected warp
local function updateWarp(n)
	SD[n.data.id] = true
	if not config.disablecheckpoint then
		-- Misc.dialog("A")
		n.data._basegame.checkpoint:reset()
		n.data._basegame.checkpoint:collect(playerinput)
	end
end

-- Initialize a warp npc
local totalWarps = 0
local iniNPC = function(n)
  if not n.data.ini then
    n.data.ini = true
    totalWarps = totalWarps + 1
    n.data.id = totalWarps
		n.data.state = SD[n.data.id]
		n.data._basegame.checkpoint.powerup = nil
		n.data._basegame.checkpoint.sound = nil
    n.data.name = n.data._settings.name
    if n.data.name == "" then
      n.data.name = "Warp - "..n.data.id
    end
  end
end

-- Menu variables
local activated = false
local playerinput = player
local playersection = 0
local playervalidinput = false

local exclamation = Graphics.sprites.hardcoded[43].img

local mx = 400
local my = 100

local list = {}
local maxlines = 8
local arrowoffset = 0
local listoffset = 0
local scrolltimerup = 0
local scrolltimerdown = 0
local maxarrowoffset = math.min(maxlines, #list) - 1
local maxlistoffset = math.max(0, #list - maxlines)
local option = 1

-- Move the list
local move = function(n, allowloop, nosound)
  if arrowoffset == 0 and listoffset == 0 and  n < 0 then
    if not allowloop then return end
    arrowoffset = maxarrowoffset
    listoffset = maxlistoffset
  elseif arrowoffset == maxarrowoffset and listoffset == maxlistoffset and n > 0 then
    if not allowloop then return end
    arrowoffset = 0
    listoffset = 0
  else
    local newarrowoffset = arrowoffset + n
    arrowoffset = math.clamp(newarrowoffset, 0, maxarrowoffset)
    if newarrowoffset ~= arrowoffset then
      listoffset = math.clamp(listoffset + newarrowoffset - arrowoffset, 0, maxlistoffset)
    end
  end

  if not nosound then
    SFX.play(sfx_scrollmenu)
  end

  option = arrowoffset + listoffset + 1
end

-- Temporarily deactivate a input
local function deactiveCon(name)
  return function()
    while true do
      if not playerinput[name] then
        return
      end
      playerinput[name] = false
      Routine.waitFrames(1)
    end
  end
end

-- Open and close the menu
local function toggle()
  activated = not activated
  if activated then
    SFX.play(sfx_openmenu)
    Misc.pause()
    playersection = player.section
    playervalidinput = false

    list = {}
    arrowoffset = 0
    listoffset = 0
    option = 1
    for _, n in ipairs(NPC.get(npcID)) do
			list[n.data.id] = n.data.name
    end
    maxarrowoffset = math.min(maxlines, #list) - 1
    maxlistoffset = math.max(0, #list - maxlines)
  else
		player.section = playersection
    Misc.unpause()
  end
end

function warphub.onTick()
  if not activated then
    for _, p in ipairs(Player.get()) do
			-- Activate menu when player presses up and hold item if pocketwarp option is on
			if config.pocketwarp and p.keys.up and p.keys.dropItem then
        toggle()
			end

			-- Activate menu when a player presses up on an NPC
      for _, n in ipairs(Colliders.getColliding{a = p, b = npcID, atypec= Colliders.PLAYER, btype = Colliders.NPC, filter = function() return true end}) do
        Graphics.drawImageToSceneWP(exclamation, n.x + n.width*0.5 - 0.5*exclamation.width, n.y - exclamation.height - 4, -40)
        if p.rawKeys.up == KEYS_PRESSED and n.data.ini then
					playerinput = p
          updateWarp(n)
          toggle()
					move(n.data.id - 1, false, true)
        end
      end
    end
  end
end

local colorbox = Color(0.1, 0.1, 0.1, 0.8)
local colorlock = Color(0.6, 0.6, 0.6, 1)
local drawMenu = function()
    -- Draw list
    Graphics.drawBox{x = mx - 100, y = my, width = config.boxwidth, height = (maxarrowoffset + 2)*32, color = colorbox, pivot = {0.5, 0}}
    textplus.print{text = config.title, x = mx, y = my + 8, priority = 5, xscale = 2, yscale = 2, pivot = {0.5, 0}, plaintext = true}
    for k = 1, maxarrowoffset + 1 do
      local pre = " "
      if k == arrowoffset + 1 then
        pre = "> "
      end
			local color
      if not config.unlockwarps and not SD[k + listoffset] then
        color = colorlock
      end
      textplus.print{text = pre..list[k + listoffset], x = mx + 8, y = my + k*32 + 8, priority = 5, color = color, xscale = 2, yscale = 2, pivot = {0.5, 0}, plaintext = true}
    end
end

local function readInput()
	-- Listen to inputs
	if playerinput.keys.run == KEYS_PRESSED then
		toggle()
		SFX.play(sfx_closemenu)
		Routine.run(deactiveCon("runKeyPressing"))
	elseif playerinput.keys.jump == KEYS_PRESSED and (SD[option] or config.unlockwarps) then
		local n = getNPCbyID(option)
		if n then
			toggle()
			updateWarp(n)
			SFX.play(sfx_warpmenu)
			playerinput.section = playersection
			playerinput:teleport(n.x + 0.5*(n.width - playerinput.width), n.y + n.height - playerinput.height)
			playerinput.speedX = 0
			playerinput.speedY = 0
			playerinput:mem(0x140,	FIELD_WORD, 50)
			local e = Animation.spawn(10, playerinput.x + 0.5*player.width, playerinput.y + 0.5*player.height)
			e.x, e.y = e.x - 0.5*e.width, e.y - 0.5*e.height
			Routine.run(deactiveCon("jumpKeyPressing"))
		end
	end
	if playerinput.keys.up then
		scrolltimerup = scrolltimerup + 1
		if playervalidinput then
			if scrolltimerup == 1 then
				move(-1, true)
			elseif scrolltimerup > 20 and scrolltimerup%5 == 0 then
				move(-1, false)
			end
		end
	else
		playervalidinput = true
		scrolltimerup = 0
	end
	if playerinput.keys.down then
		scrolltimerdown = scrolltimerdown + 1
		if playervalidinput then
			if scrolltimerdown == 1 then
				move(1, true)
			elseif scrolltimerdown > 20 and scrolltimerdown%5 == 0 then
				move(1, false)
			end
		end
	else
		scrolltimerdown = 0
	end
end

local function getSection(x,y,w,h)
	for k, v in ipairs(Section.get()) do
		local b = v.boundary
		if (x + w >= b.left and x <= b.right) and (y + h >= b.top and y <= b.bottom) then
			return k - 1
		end
	end
	return -1
end

-- Disable split-screen when menu is activated
function warphub.onCameraUpdate(idx)
  if activated then
    if idx == 1 then
      local n = getNPCbyID(option)
      camera.renderX, camera.renderY, camera.width, camera.height = 0, 0, 800, 600
			drawMenu()
			readInput()

			if n then
				if SD[n.data.id] or config.allowpreview or config.unlockwarps then
					camera.x = n.x + 0.5*n.width - 400
					camera.y = n.y + n.height - 300
				end

        local s = getSection(camera.x + 2, camera.y + 2, 800, 600)
        if s == -1 then s = playerinput.section end
				if activated then
			  	player.section = s
			  end

        sec = Section(s).boundary
        camera.x, camera.y = math.clamp(sec.left, camera.x, sec.right - 800), math.clamp(sec.top, camera.y, sec.bottom - 600)
      end
    else
      camera2.renderY = 800
    end
  end
end

function warphub.onTickNPC(n)
  iniNPC(n)
end

function warphub.onDrawNPC(n)
	if not config.nospecialanimation then
		local frames = math.floor(config.frames/3)
		local offset = frames
		if n.data._basegame.checkpoint.id == GameData.__checkpoints[Level.filename()].current then
			offset = 0
		elseif not SD[n.data.id] then
      offset = frames*2
		end
		n.animationFrame = npcutils.getFrameByFramestyle(n, {frames = frames, offset = offset})
	end
end


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

  registerEvent(warphub, "onTick", "onTick")
  registerEvent(warphub, "onCameraUpdate", "onCameraUpdate", true)
end

return warphub
