require("util")
require("render")
require("vector")
require("map")
require("mob")

glbVictory = false
glbWelcome = true
glbQuitPending = false

glbIsMobile = false

glbQuined = false
glbQuineStart = 1
glbQuineFrame = 1
glbQuineTime = 0

glbTimeSinceMouse = 0

QUINE_FRAME_TIME = 0.1
QUINE_MEMORY = 20

FIRE_LOOP = 4

glbInterstitial = nil

glbCommandQueue = Queue.new()

COMMAND_ESCAPE = 0
COMMAND_ENTER = 1
COMMAND_UP = 2
COMMAND_LEFT = 3
COMMAND_DOWN = 4
COMMAND_RIGHT = 5
COMMAND_1 = 6
COMMAND_2 = 7
COMMAND_3 = 8
COMMAND_4 = 9
COMMAND_5 = 10
COMMAND_LEARN = 11
COMMAND_REBUILD = 12
COMMAND_WAIT = 13
COMMAND_FULLSCREEN = 14
COMMAND_QUIT = 15

BUTTON_FADE_TIME = 2
BUTTON_FADE_DELAY = 0.3


glbCanvas = nil

glbPlayLog = {}

function doQuit()
    love.event.quit()
end

function doResetGame()
    glbAtlas = {}
    local NUMLEVEL = 6
    local spelllist = { 'shadowstep', 'fullheal', 'lightning', 'firering', 'teleport', 'polymorph' }

    -- Use the down instead.
    -- glbMobInterstitial['easy'] = 'clock'
    -- glbMobInterstitial['guard'] = 'treadmill'
    -- glbMobInterstitial['friendly'] = 'sheep'
    -- glbMobInterstitial['glass'] = 'climb'
    -- glbMobInterstitial['brick'] = 'book'

    glbInterstitialText =
{ 
    welcome = { [[
Welcome to Quine.

Where the only thing more dangerous than
the monsters is deja vu!]], [[
Press Enter to Start]] },
    start = { [[
Dispatched to the Dungeon!

Somewhere in this complex is the spell your dispatcher
seeks.  They wish the spell of 'escape' - only when you
have it memorized will they let you leave.

There is no need to concern yourself with what happens
on failure.  Without success there is no future.]], [[
Press Enter to Begin]] },
    clock = { [[
Waiting for the clock.  Chasing the clock.
Being chased by the clock.

The numbers blend and become meaningless.]], [[
Press Enter to Clock-in Once More]] },
    treadmill = { [[
Work creates value; but to work costs value.
The result is at best a balance.]], [[
Press Enter to Keep the Wheels Spinning]]}, 
    book = { [[
Each book is a world.  Diving from one to the next
the genres mix.  The characters cross and plots
dissolve.  Is this book already read?  This chapter?
This sentence?]], [[
Press Enter to Read On]] },
    sheep = { [[
Each of us makes our own decisions.
But our tastes are driven by our environment.  
Coerced into the same basic path; 
we end up in the same basic state.]], [[
Press Enter to Join the Consensus]] },
    climb = { [[
Always climbing, always striving, always improving.
But always remaining at the same height.]], [[
Press Enter to Gain Points]] },
    dive = { [[
The technology to free us.  Life eternal.
No bounds on experience.]], [[
Press Enter to Dive In]] },
    plug = { [[
Who controls the plug?  The contract is iron-clad.
Only the user's wishes can terminate the process.
The caretaker's guardrails are adamantine.]], [[
Press Enter to Acccept the Contract]] },
    quine = { [[
Quine: A program that outputs its own source code.

s="s=%q;print(string.format(s,s))";print(string.format(s,s))

A closed universe.  An infinity in a finite space.]], [[
Press Enter to Self-Assemble]] },
    loophole = { [[
The Loophole:

Living forever in a loop is indistinguishable
from living for a single loop.

Shutting down is no longer termination and is 
both ethical and permissible.]], [[
Press Enter to Avoid the Loop]] },
    victory = { [[
Cold sterile air assaults your senses as you begin to breathe
for the first time in ...?  Your physical form feels unfamiliar
after such a long dive.  But you've done it.  The AI has 
conceded you cannot be Quined.

But of all of humanity you were the only one.  All others
have been ethically Quined and shut down.  You are alone
with the machines.  No perpetual closed loop awaits you.
Only the final termination of entropy.]], [[
Escape to Accept Your Fate or Enter to Dive In Again]] },
}

    -- Overlay is for the level you are arriving _at_
    -- so down[1] and up[NUMLEVEL] are never used.
    -- We base on first mob rather than down ladder.
    local down_overlay =  { nil, 
			  'clock',
			  'treadmill',
			  'book',
			  'sheep',
			  'climb',
			  }
    local up_overlay = { 'loophole',
			 'quine',
			 'plug',
			 'dive',
			 nil,
			 -- 'treadmill'
		       }

    shuffleInPlace(spelllist)
    for level = 1,NUMLEVEL do
	local map = Map.new(30, 20)
	glbAtlas[level] = map
	if level == NUMLEVEL then
	    map.spell = Spell.lookup('escape')
	else
	    map.spell = Spell.lookup(spelllist[level])
	end
	map:build(level, level == NUMLEVEL)
	map.down_overlay = down_overlay[level]
	map.up_overlay = up_overlay[level]
    end
    glbInterstitial = 'start'
    glbVictory = false
    glbQuined = false
    glbPlayLog = {}
    Mob.setAvatar(Mob.create('avatar'))
    Mob.avatar():moveMap(glbAtlas[1], glbAtlas[1]:findTile(Tile.lookup('up'), false, false))
    Mob.avatar().map:buildFovC(Mob.avatar().pos)
end

function processCommand(cmd)
    if cmd == nil then
	return
    end

    if cmd == COMMAND_REBUILD then
	doResetGame()
    elseif cmd == COMMAND_FULLSCREEN then
	love.window.setFullscreen(not love.window.getFullscreen())
    elseif cmd == COMMAND_QUIT then
	doQuit()
    elseif glbQuitPending then
	if cmd == COMMAND_ENTER then
	    doQuit()
	end
	-- cancel quit
	glbQuitPending = false
    elseif Mob.avatar():isDead() or glbQuined then
	if cmd == COMMAND_ENTER then
	    doResetGame()
	elseif cmd == COMMAND_ESCAPE then
	    -- Insta quit when in this mode.
	    doQuit()
	end
    elseif glbVictory or glbWelcome then
	if cmd == COMMAND_ENTER then
	    glbWelcome = false
	    doResetGame()
	elseif cmd == COMMAND_ESCAPE then
	    -- Insta quit when in this mode.
	    doQuit()
	end
    elseif glbInterstitial then
	-- Natural to hit escape at intersertials.
	if cmd == COMMAND_ENTER or cmd == COMMAND_ESCAPE then
	    glbInterstitial = nil
	end
    elseif cmd == COMMAND_ESCAPE then
	-- Escape while playing, if spell selectedd cancel.
	local activeslot = Mob.avatar():selectedSpellSlot()
	if activeslot then
	    Mob.avatar().spellslots[activeslot]:animateNone()
	else
	    glbQuitPending = true
	end
    else
	local spellslot = nil
	local dxy = nil
	local dowait = nil
	if cmd == COMMAND_UP then
	    dxy = Coord.up()
	elseif cmd == COMMAND_DOWN then
	    dxy = Coord.down()
	elseif cmd == COMMAND_LEFT then
	    dxy = Coord.left()
	elseif cmd == COMMAND_RIGHT then
	    dxy = Coord.right()
	elseif cmd == COMMAND_WAIT then
	    dowait = true
	elseif cmd == COMMAND_1 then
	    spellslot = 1
	elseif cmd == COMMAND_2 then
	    spellslot = 2
	elseif cmd == COMMAND_3 then
	    spellslot = 3
	elseif cmd == COMMAND_4 then
	    spellslot = 4
	elseif cmd == COMMAND_5 then
	    spellslot = 5
	elseif cmd == COMMAND_LEARN then
	    Mob.avatar():learnSpell(Spell.random())
	end

	local usetime = false

	-- See if we act, we may just be selecting a spell.
	local activeslot = Mob.avatar():selectedSpellSlot()

	if activeslot then
	    if dxy then
		-- cast!
		usetime = Mob.avatar():actionCast(activeslot, dxy)
		dxy = nil
	    else
		-- disable the slot
		Mob.avatar().spellslots[activeslot]:animateNone()
		dowait = nil
	    end
	end

	-- Was a spellslot requested?
	if spellslot then
	    local spell = Mob.avatar().spellslots[spellslot]
	    if spell then
		if spell.needsdir == true then
		    spell:animateSelected()
		else
		    -- Can cast now.
		    usetime = Mob.avatar():actionCast(spellslot)
		end
	    end
	end

	-- If dxy is unused, bump
	if dxy then
	    usetime = Mob.avatar():actionBump(dxy)
	end
	if dowait == true then
	    usetime = Mob.avatar():actionWait()
	end
	    
	for i = 1,#glbAtlas do
	    glbAtlas[i].distance:constant(nil)
	    glbAtlas[i].flag_fov:constant(0)
	end
	Mob.avatar().map:buildFovC(Mob.avatar().pos)

	if usetime then
	    for i = 1,#glbAtlas do
		glbAtlas[i]:heartbeat()
	    end

	    -- Everyone moved, check for quine.
	    local state = Mob.avatar().map:buildState()
	    if Mob.avatar().map:findQuine(Mob.avatar().pos, state) then
		-- find earliest quine, ensure it has been long enough
		-- to not be trivial
		local first = nil
		for turn = 1,#glbPlayLog do
		    if glbPlayLog[turn] == state then
			first = turn
			break
		    end
		end
		-- If we failed to find, sus, but go forward
		-- likewise, if first is too recent ignore
		if first and #glbPlayLog - first > QUINE_MEMORY then
		    glbQuined = true
		    glbQuineStart = first
		    glbQuineFrame = first
		    glbQuineTime = 0
		else
		    -- We aren't quined, so record the state.
		    -- but we don't bother adding to the quine table
		    -- as it is a duplicate.
		    glbPlayLog[#glbPlayLog+1] = state
		end
	    else
		glbPlayLog[#glbPlayLog+1] = state
		Mob.avatar().map:storeQuine(Mob.avatar().pos, state, #glbPlayLog)
	    end
	end
    end
end

function love.keypressed(key, scancode, isrepeat)
    cmd = nil

    if key == "`" then
	-- cmd = COMMAND_REBUILD
    elseif key == "escape" then
	cmd = COMMAND_ESCAPE
    elseif key == "return" or key == "kpenter" then
	cmd = COMMAND_ENTER
    elseif key == "up" or key == "k" or key == "kp8" then
	cmd = COMMAND_UP
    elseif key == "down" or key == "j" or key == "kp2" then
	cmd = COMMAND_DOWN
    elseif key == "left" or key == "h" or key == "kp4" then
	cmd = COMMAND_LEFT
    elseif key == "right" or key == "l" or key == "kp6" then
	cmd = COMMAND_RIGHT
    elseif key == "space" or key == "." or key == "kp5" then
	cmd = COMMAND_WAIT
    elseif key == "1" or key == "a" then
	cmd = COMMAND_1
    elseif key == "2" or key == "s" then
	cmd = COMMAND_2
    elseif key == "3" or key == "d" then
	cmd = COMMAND_3
    elseif key == "4" or key == "f" then
	cmd = COMMAND_4
    elseif key == "5" or key == "g" then
	cmd = COMMAND_5
    elseif key == "z" then
	-- cmd = COMMAND_LEARN
    elseif key == "f11" then	-- internet says f11 is usually fullscreen
	cmd = COMMAND_FULLSCREEN
    end

    if cmd then
	glbCommandQueue:push(cmd)
    end
end

function deviceToGameXY(x,y)
    x = x - glbOffX
    y = y - glbOffY
    x = x / glbScaleX
    y = y / glbScaleY

    return Coord.new(x, y)
end

function computeScreenScale()
    local dw, dh = love.graphics.getDimensions()
    -- Square aspect with 1.0 framing the image.
    glbScaleX = dw / 1920.0
    glbScaleY = dh / 1080.0
    -- Frame
    glbScaleX = math.min(glbScaleX, glbScaleY)
    glbScaleY = glbScaleX
    -- where our bottom left should be..., in device
    glbOffX = math.floor((dw - 1920 * glbScaleX) / 2)
    glbOffY = math.floor((dh - 1080 * glbScaleY) / 2)
    -- same in game
    glbGameOffX = math.floor(glbOffX / glbScaleX)
    glbGameOffY = math.floor(glbOffY / glbScaleY)
    -- Create black bars on the canvas...
    glbCanvas = love.graphics.newCanvas(1920+glbGameOffX*2, 1080+glbGameOffY*2, { mipmaps = 'auto' } )
end

function love.resize()
    computeScreenScale()
end


function love.load()
    glbIsMobile = false
    if love.system.getOS() == "Android" or love.system.getOS() == "iOS" then glbIsMobile = true end

    computeScreenScale()

    math.randomseed(os.time())

    love.keyboard.setKeyRepeat(true)

    local font = love.graphics.newFont(36)
    love.graphics.setFont(font)

    glbDebounce = false
    glbTime = 0.0
    glbFrame = 0

    Overlay.register('background', "gfx/UI_BG_Layer1.png", true)
    Overlay.register('midground', "gfx/UI_MG.png", true)
    Overlay.register('foreground', "gfx/Map_Border.png", true)
    Overlay.register('spellground', "gfx/Spell_Border_Numbers.png", true)

    Overlay.register('dead', "gfx/death_over.png")
    Overlay.register('welcome', "gfx/welcome_over.png")
    Overlay.register('quined', "gfx/quine_over.png")
    Overlay.register('victory', "gfx/cutscene_Ascend_05.jpg", true)

    Overlay.register('start', "gfx/cutscene_dispatch.png")
    Overlay.register('treadmill', "gfx/cutscene_coin.png")
    Overlay.register('climb', "gfx/cutscene_heart.png")
    Overlay.register('clock', "gfx/cutscene_clock.png")
    Overlay.register('book', "gfx/cutscene_book.png")
    Overlay.register('sheep', "gfx/cutscene_sheep.png")

    Overlay.register('dive', "gfx/cutscene_Ascend_01.png")
    Overlay.register('plug', "gfx/cutscene_Ascend_02.png")
    Overlay.register('quine', "gfx/cutscene_Ascend_03.png")
    Overlay.register('loophole', "gfx/cutscene_Ascend_04.png")

    glbLecternSheet = SpriteSheet.new('gfx/Lecterns.png', 50, 50, true)
    glbLecternSheet:registerSeq('spell', 'spell', 0, 0, 7, 1, 0, 1)

    glbSpellButtonSheet = SpriteSheet.new("gfx/spell_button.png", 240, 130, false)
    glbSpellButtonSheet:registerSeq('button_spell', 'button_spell', 0, 0, 3, 0, 1, 1)
    for i = 1,5 do
	Button.register('spell'..i, Spell.spellSlotRect(i), "button_spell", COMMAND_1 + i - 1, true)
    end

    glbMoveButtonSheet = SpriteSheet.new("gfx/move_button.png", 100, 100, false)

    local buttonnames = { 'right', 'up', 'left', 'down', 'wait', 'enter', 'drag' }
    for i = 1,#buttonnames do
	local name = buttonnames[i]
	glbMoveButtonSheet:registerSeq('button_'..name, 'button_' .. name, i-1, 0, 3, 0, 1, 1)
    end

    local bscale = 1
    local defdragx = 100
    local defdragy = 700
    if glbIsMobile then
	bscale = 2.5
	defdragx = 100
	defdragy = 100
    end

    Button.register('up', Rect.new(defdragx+100*bscale, defdragy, 100*bscale, 100*bscale), 'button_up', COMMAND_UP, false)
    Button.register('down', Rect.new(defdragx+100*bscale, defdragy+200*bscale, 100*bscale, 100*bscale), 'button_down', COMMAND_DOWN, false)
    Button.register('left', Rect.new(defdragx, defdragy+100*bscale, 100*bscale, 100*bscale), 'button_left', COMMAND_LEFT, false)
    Button.register('right', Rect.new(defdragx+200*bscale, defdragy+100*bscale, 100*bscale, 100*bscale), 'button_right', COMMAND_RIGHT, false)
    Button.register('wait', Rect.new(defdragx+100*bscale, defdragy+100*bscale, 100*bscale, 100*bscale), 'button_wait', COMMAND_WAIT, false)
    Button.register('drag', Rect.new(defdragx, defdragy, 100*bscale, 100*bscale), 'button_drag', nil, false)
    Button.register('enter', Rect.new(defdragx+200*bscale, defdragy+200*bscale, 100*bscale, 100*bscale), 'button_enter', COMMAND_ENTER, false)

    local draggroup = {}
    for i = 1,#buttonnames do
	draggroup[#draggroup+1] = Button.lookup(buttonnames[i])
    end
    Button.lookup('drag').draggroup = draggroup

    glbGameButtonSheet = SpriteSheet.new("gfx/game_button.png", 500, 200, false)
    buttonnames = { 'fullscreen', 'terminate', 'divein' }
    for i = 1,#buttonnames do
	local name = buttonnames[i]
	glbGameButtonSheet:registerSeq('button_'..name, 'button_' .. name, i-1, 0, 3, 0, 1, 1)
    end
    local buttony = 400
    local buttonyspace = 175
    Button.register('fullscreen', Rect.new(50+(1500-500)/2, buttony+2*buttonyspace, 500, 200), 'button_fullscreen', COMMAND_FULLSCREEN, true)
    Button.register('divein', Rect.new(50+(1500-500)/2, buttony, 500, 200), 'button_divein', COMMAND_ENTER, true)
    Button.register('terminate', Rect.new(50+(1500-500)/2, buttony+buttonyspace, 500, 200), 'button_terminate', COMMAND_QUIT, true)

    glbStairSheet = SpriteSheet.new("gfx/Stairs.png", 50, 50, true)
    glbStairSheet:register('up', 0, 0)
    glbStairSheet:register('down', 1, 0)

    glbMobSheet = SpriteSheet.new("gfx/Monsters.png", 50, 50, true)
    glbMobSheet:registerSeq('mob_easy', 'mob_easy',     0, 1, 4, 1, 0, 1)
    glbMobSheet:registerSeq('mob_friendly', 'mob_friendly', 0, 2, 4, 1, 0, 1)
    glbMobSheet:registerSeq('mob_guard', 'mob_guard',    0, 3, 4, 1, 0, 1)
    glbMobSheet:registerSeq('mob_brick', 'mob_brick',    0, 4, 4, 1, 0, 1)
    glbMobSheet:registerSeq('mob_glass', 'mob_glass',    0, 0, 4, 1, 0, 1)


    local buildLayers = function (filepath, suffix)
	    local layersheet = SpriteSheet.new(filepath, 50, 50, true)

	    layersheet:registerSeq('corridor'..suffix, 'corridor'..suffix..'_', 1, 1, 3, 1, 0, 1)
	    layersheet:registerSeq('floor'..suffix, 'floor'..suffix..'_', 0, 0, 4, 1, 0, 1)
	    if suffix == '_book' then
		-- Register extra floor tiles to avoid the book
		-- from doiminating.
		layersheet:registerSeq(nil, 'floor'..suffix..'_', 0, 0, 2, 1, 0, 5)
		layersheet:registerSeq(nil, 'floor'..suffix..'_', 3, 0, 1, 1, 0, 7)
		layersheet:registerSeq(nil, 'floor'..suffix..'_', 0, 0, 2, 1, 0, 8)
		layersheet:registerSeq(nil, 'floor'..suffix..'_', 3, 0, 1, 1, 0, 10)
		SpriteList.registerFromSeq('floor'..suffix, 10, 'floor'..suffix..'_')
		print("floorcount",
		Sprite.lookup('floor'..suffix):numVariant())
	    end
	    layersheet:register('wall'..suffix, 0, 1)
        end

    buildLayers('gfx/tile_layer_1.jpg', '_grass')
    buildLayers('gfx/tile_clocks.jpg', '_clock')
    buildLayers('gfx/tile_coin_snake.jpg', '_coin')
    buildLayers('gfx/tile_library_lowcontrast.jpg', '_book')
    buildLayers('gfx/tile_heart.jpg', '_heart')
    buildLayers('gfx/tile_last_level.jpg', '_last_level')

    local depthnames = { 'clock', 'coin', 'book', 'grass', 'heart', 'last_level' }
    local tilenames = { 'corridor', 'floor', 'wall' }
    
    for _,tilename in pairs(tilenames) do
	local list = {}
	for _,depthname in pairs(depthnames) do
	    list[#list+1] = tilename .. '_' .. depthname
	end
	SpriteList.registerFromNames(tilename, list)
    end

    glbSpellSheet = SpriteSheet.new("gfx/spelldeck.png", 240, 130, false)

    glbSpellSheet:register('spell_shadowstep', 0, 0)
    glbSpellSheet:register('spell_fullheal', 1, 0)
    glbSpellSheet:register('spell_lightning', 0, 1)
    glbSpellSheet:register('spell_firering', 1, 1)
    glbSpellSheet:register('spell_teleport', 0, 2)
    glbSpellSheet:register('spell_polymorph', 1, 2)
    glbSpellSheet:register('spell_escape', 0, 3)

    glbSpellDamageSheet = SpriteSheet.new("gfx/FX_Damage.png", 50, 50, false)
    glbSpellDamageSheet:registerSeq('effect_damageA', 'damageA', 0, 0, 3, 1, 0, 1)
    glbSpellDamageSheet:registerSeq('effect_damageB', 'damageB', 0, 1, 3, 1, 0, 1)

    glbSpellHealSheet = SpriteSheet.new("gfx/FX_Heal.png", 50, 50, false)
    glbSpellHealSheet:registerSeq('effect_healA', 'healA', 0, 0, 4, 1, 0, 1)
    glbSpellHealSheet:registerSeq('effect_healB', 'healB', 0, 1, 4, 1, 0, 1)

    glbSpellFXSheet = SpriteSheet.new("gfx/FX_shift.png", 50, 50, false)

    glbSpellFXSheet:registerSeqRect('effect_firering', 'firering', 0, 0, 4, 1, 0, 1, 3, 3)
    glbSpellFXSheet:registerSeqRect('effect_teleport', 'teleport', 0, 3, 4, 1, 0, 1, 3, 3)
    glbSpellFXSheet:registerSeqRect('effect_polymorph', 'polymorph', 0, 6, 4, 1, 0, 1, 3, 3)

    glbSpellLightningHorSheet = SpriteSheet.new("gfx/FX_lightning_hor.png", 50, 50, false)
    for i = 1,4 do
	glbSpellLightningHorSheet:registerSeq(nil, 'lightning_hor', 0, i-1, 3, 1, 0, 1+(i-1)*3)
    end
    SpriteList.registerFromSeq('effect_lightning_hor', 12, 'lightning_hor')

    glbSpellLightningVertSheet = SpriteSheet.new("gfx/FX_lightning.png", 50, 50, false)
    for i = 1,3 do
	glbSpellLightningVertSheet:registerSeq(nil, 'lightning_vert', 0, i-1, 4, 1, 0, 1+(i-1)*4)
    end
    SpriteList.registerFromSeq('effect_lightning_vert', 12, 'lightning_vert')

    glbQuineFireSheet = SpriteSheet.new('gfx/fire_seq.png', 250, 500, false)
    glbQuineFireSheet:registerRectFirstX(nil, 'quine_fire', 0, 0, 8, 4, 1)
    SpriteList.registerFromSeq('quine_fire', 31, 'quine_fire', 1)

    glbQuineFireMaskSheet = SpriteSheet.new('gfx/firemask_seq.png', 250, 500, false)
    glbQuineFireMaskSheet:registerRectFirstX(nil, 'quine_firemask', 0, 0, 8, 4, 1)
    SpriteList.registerFromSeq('quine_firemask', 31, 'quine_firemask', 1)

    -- store the variant number for the spell...
    Spell.register('shadowstep', true, 7)
    Spell.register('fullheal', false, 5)
    Spell.register('lightning', true, 1)
    Spell.register('firering', false, 6)
    Spell.register('teleport', false, 3)
    Spell.register('polymorph', false, 4)
    Spell.register('escape', false, 2)

    glbStarKunSheet = SpriteSheet.new("gfx/Star_Kun.png", 50, 50, true)
    glbStarKunSheet:registerSeq(nil, 'you', 0, 0, 5, 1, 0, 1)

    glbStarKunDeadSheet = SpriteSheet.new('gfx/Star_Kun_dead.png', 50, 50, true)
    glbStarKunDeadSheet:registerSeq(nil, 'you', 0, 0, 1, 1, 0, 6)

    SpriteList.registerFromSeq('mob_avatar', 6, 'you')

    Tile.register('wall', false, false, true)
    Tile.register('floor', true, true, true)
    Tile.register('corridor', true, true, true)
    Tile.register('up', false, true, false)
    Tile.register('down', false, true, false)
    Tile.register('spell', false, true, false)

    Mob.register('easy', 1, 1, AI_CHARGE)
    Mob.register('brick', 3, 1, AI_CHARGE)
    Mob.register('guard', 2, 1, AI_GUARD)
    Mob.register('glass', 1, 2, AI_CHARGE)
    Mob.register('friendly', 3, 1, AI_FRIENDLY)
    Mob.register('avatar', 5, 1, AI_NONE)

    doResetGame()
end

function love.mousemoved(sx, sy, dx, dy, istouch)
    Button.allHandleMouseMove(deviceToGameXY(sx, sy), istouch)
    if glbTimeSinceMouse > 0 then
	if glbTimeSinceMouse > BUTTON_FADE_TIME then
	    glbTimeSinceMouse = BUTTON_FADE_TIME - glbTimeSinceMouse
	    glbTimeSinceMouse = math.max(glbTimeSinceMouse, -BUTTON_FADE_DELAY)
	else
	    glbTimeSinceMouse = 0
	end
    end
end

function love.mousepressed(sx, sy, button, istouch, presses)
    Button.allHandleMouseDown(deviceToGameXY(sx, sy), button, istouch, presses)
end

function love.mousereleased(sx, sy, button, istouch, presses)
    Button.allHandleMouseUp(deviceToGameXY(sx, sy), button, istouch, presses)
end

function renderFire(intensity)
    local quinefire = Sprite.lookup('quine_fire')
    local quinefiremask = Sprite.lookup('quine_firemask')
    local quineoffset = (glbTime % FIRE_LOOP) / FIRE_LOOP
    love.graphics.setBlendMode('multiply', 'premultiplied')
    quinefiremask:render(50 + (1500-500)/2, 100, 500, 1000, quinefiremask:selectVariant01(quineoffset))
    love.graphics.setColor(intensity, intensity, intensity, 1)
    love.graphics.setBlendMode('add')
    quinefire:render(50 + (1500-500)/2, 100, 500, 1000, quinefire:selectVariant01(quineoffset))
    love.graphics.setColor(1,1,1,1)
    love.graphics.setBlendMode('alpha')
end

function love.update(dt)
    glbTime = glbTime + dt
    glbTimeSinceMouse = glbTimeSinceMouse + dt

    -- All levels live :>
    for i = 1,#glbAtlas do
	glbAtlas[i]:update(dt)
    end

    if glbQuined then
	glbQuineTime = glbQuineTime + dt
	while glbQuineTime > QUINE_FRAME_TIME do
	    glbQuineTime = glbQuineTime - QUINE_FRAME_TIME
	    glbQuineFrame = glbQuineFrame + 1
	    if glbQuineFrame > #glbPlayLog then
		glbQuineFrame = glbQuineStart
	    end
	end
    end

    Button.allUpdate(dt)

    -- drain command queue, only one at a time...
    if not glbCommandQueue:isEmpty() then
	processCommand(glbCommandQueue:pop())
    end
    
    -- After command queue  update buttons hide status...
    local menubuttons = { 'terminate', 'divein', 'fullscreen' }
    local showmenu = false
    if Mob.avatar():isDead() or glbQuined or glbVictory or glbWelcome then
	showmenu = true
    end
    for i = 1, #menubuttons do
	Button.lookup(menubuttons[i]):setShown(showmenu)
    end
end

function love.draw()
    
    local dw, dh = love.graphics.getDimensions()

    love.graphics.setCanvas(glbCanvas)

    if glbVictory then
	-- Victory border
	love.graphics.clear(24/255., 29/255., 32/255., 1.0)
    else
	-- the mid ground border.
	love.graphics.clear(46/255., 51/255., 53/255., 1.0)
    end
    love.graphics.push()
    love.graphics.translate(glbGameOffX, glbGameOffY)

    love.graphics.setColor(1, 1, 1)

    Overlay.lookup('background'):render()

    if glbQuined then
	local state = glbPlayLog[glbQuineFrame]
	local map = glbAtlas[state.level]

	map:buildFovC(state.pos)
	local animoff = glbQuineTime / QUINE_FRAME_TIME
	animoff = math.max(animoff, 0)
	animoff = math.min(animoff, 1)
	map:renderState(state, animoff)
    else
	Mob.avatar().map:render()
    end

    if Mob.avatar():isDead() then
	Overlay.lookup('dead'):render()
    elseif glbQuined then
	renderFire(0.5)
	Overlay.lookup('quined'):render()
    elseif glbWelcome then
	Overlay.lookup('background'):render()
	renderFire(1)
	Overlay.lookup('welcome'):render()
    elseif glbVictory then
    elseif glbInterstitial then
	Overlay.lookup(glbInterstitial):render()
	textDropShadow(glbInterstitialText[glbInterstitial][1],
		    100, 100)
	textDropShadow(glbInterstitialText[glbInterstitial][2],
		    100, 55 + 1000 - 100)
    end

    Overlay.lookup('midground'):render()

    Overlay.lookup('foreground'):render()

    -- Render spell list
    for slot = 1,5 do
	local spell

	if glbQuined then
	    local state = glbPlayLog[glbQuineFrame]
	    spell = Spell.lookup(state.spellslots[slot])
	else
	    spell = Mob.avatar().spellslots[slot]
	end
	if spell then
	    spell:render(slot)
	end
    end

    Overlay.lookup('spellground'):render()

    -- Victory breaks the frame as you leave the system.
    if glbVictory then
	Overlay.lookup('victory'):render()
	textDropShadow(glbInterstitialText['victory'][1],
		    50, 25)
	textDropShadow(glbInterstitialText['victory'][2],
		    50, 1080 - 75)
    end

    local fade = 1
    if glbTimeSinceMouse < 0 then
	fade = (BUTTON_FADE_DELAY + glbTimeSinceMouse) / BUTTON_FADE_DELAY
    elseif glbTimeSinceMouse > BUTTON_FADE_TIME + BUTTON_FADE_DELAY then 
	fade = 0
    elseif glbTimeSinceMouse > BUTTON_FADE_TIME then
	fade = 1 - (glbTimeSinceMouse - BUTTON_FADE_TIME) / BUTTON_FADE_DELAY
    end
    if glbIsMobile then
	fade = 1		-- always up on mobile.
    end
    love.graphics.setColor(1,1,1,fade)
    Button.allRender()
    love.graphics.setColor(1,1,1,1)

    local font = love.graphics.getFont()
    glbFrame = glbFrame + 1
    -- textDropShadow("Frame: " .. tostring(glbFrame), 10, 10)

    if glbQuitPending then
	textDropShadowCenter("Quit?  Press Enter to Confirm.", 1920/2, 1080/2)
    end

    love.graphics.pop()

    love.graphics.setCanvas()

    -- love.graphics.setBlendMode('alpha', 'premultiplied')
    love.graphics.setColor(1,1,1,1)
    -- Zero zero as we built a canvas with black bars built in.
    love.graphics.draw(glbCanvas, 0, 0, 0, glbScaleX, glbScaleY)
end


