-- Global sprite list.
glbSprites = {}

glbButtons = {}

BUTTON_REPEAT_FIRSTDELAY = 0.5
BUTTON_REPEAT_DELAY = 0.15

glbOverlays = {}

Overlay = {}
Overlay.__index = Overlay

function Overlay.new(imagepath, fullscreen)
    local v = {}
    v.image = love.graphics.newImage(imagepath)
    v.fullscreen = fullscreen
    setmetatable(v, Overlay)
    return v
end

function Overlay.register(name, imagepath, fullscreen)
    local o = Overlay.new(imagepath, fullscreen)
    glbOverlays[name] = o
    return o
end

function Overlay.lookup(name)
    return glbOverlays[name]
end

function Overlay:render()
    if self.fullscreen then
	local w, h = 1920, 1080
	love.graphics.draw(self.image, 0, 0, 0, w/self.image:getWidth(), h/self.image:getHeight())
    else
	local w, h = 1500, 1000
	love.graphics.draw(self.image, 50, 55, 0, w/self.image:getWidth(), h/self.image:getHeight())
    end
end

function textJustifyCenter(text, x, y)
    local font = love.graphics.getFont()
    x = x or 0
    y = y or 0
    x = x - font:getWidth(text) / 2
    -- sadly assume one line :>
    y = y - font:getHeight() / 2
    return x, y
end

function textDropShadowCenter(text, x, y)
    x, y = textJustifyCenter(text, x, y)
    textDropShadow(text, x, y)
end

function textDropShadow(text, x, y)
    x = x or 0
    y = y or 0
    local font = love.graphics.getFont()

    love.graphics.setColor(0,0,0)
    love.graphics.print(text, x+1, y+1)
    love.graphics.print(text, x+2, y+2)
    love.graphics.print(text, x+3, y+3)
    -- The colour from map border
    local brightness = 1.3
    love.graphics.setColor(brightness * 170/255.,brightness * 169/255.,brightness * 150/255.)
    love.graphics.print(text, x, y)
    love.graphics.setColor(1,1,1,1)
end

Button = {}
Button.__index = Button

function Button.new(name, rect, sprite, command, triggerup)
    local v = {}

    v.name = name
    v.rect = rect
    v.hidden = false
    v.sprite = sprite
    v.located = false
    v.pressed = false
    v.lastmousexy = nil
    v.time = 0
    v.hasrepeat = false
    v.draggroup = {}

    v.command = command
    v.triggerup = triggerup

    setmetatable(v, Button)
    return v
end

function Button.register(name, rect, spritename, command, triggerup)
    glbButtons[name] = Button.new(name, rect, Sprite.lookup(spritename), command, triggerup)
end

function Button.allRender()
    for _,v in pairs(glbButtons) do
	v:render()
    end
end

function Button.lookup(name)
    return glbButtons[name]
end

function Button:render()
    if self.hidden then return end
    local variant = 1
        if self.pressed then variant = 3 
    elseif self.located and not glbIsMobile then variant = 2 
        end
    self.sprite:render(self.rect, variant)
end

function Button:translate(dxy)
    self.rect:translate(dxy)
end

function Button.allHandleMouseMove(xy, istouch)
    for _,v in pairs(glbButtons) do
	v:handleMouseMove(xy, istouch)
    end
end

function Button.allHandleMouseDown(xy, button, istouch, presses)
    for _,v in pairs(glbButtons) do
	v:handleMouseDown(xy, button, istouch, presses)
    end
end

function Button.allHandleMouseUp(xy, button, istouch, presses)
    for _,v in pairs(glbButtons) do
	v:handleMouseUp(xy, button, istouch, presses)
    end
end

function Button:handleMouseMove(xy, istouch)
    if self.hidden then 
	self.lastmousexy = xy
	self.pressed = false
	return
    end

    -- Drag before locating or we could lose our press
    if self.pressed == true and #self.draggroup > 0 then
	local dxy = xy - self.lastmousexy
	for i = 1,#self.draggroup do
	    self.draggroup[i]:translate(dxy)
	end
    end
    self.lastmousexy = xy
    self.located = self.rect:contains(xy)
    if not self.located then self.pressed = false end
end

function Button:setShown(doshow)
    -- this way nil will cause us to show!
    self.hidden = not doshow
end

function Button:hide()
    self.hidden = true
end

function Button:show()
    self.hidden = false
end

function Button:handleMouseDown(xy)
    self.lastmousexy = xy
    if self.rect:contains(xy) then
	self.pressed = true
	self.time = 0
	self.hasrepeat = false
	if not self.triggerup then
	    glbCommandQueue:push(self.command)
	end
    end
end

function Button:handleMouseUp(xy)
    self.lastmousexy = xy
    if self.hidden then return end
    if self.rect:contains(xy) then
	if self.pressed then
	    self.pressed = false

	    if self.triggerup then
		glbCommandQueue:push(self.command)
	    end
	end
    end
end

function Button.allUpdate(dt)
    for _,v in pairs(glbButtons) do
	v:update(dt)
    end
end

function Button:update(dt)
    if self.hidden then 
	self.located = false
	self.pressed = false
	return 
    end
    if self.pressed then
	self.time = self.time + dt
	if not self.hasrepeat then
	    if self.time > BUTTON_REPEAT_FIRSTDELAY then
		self.time = self.time - BUTTON_REPEAT_FIRSTDELAY
		if not self.triggerup then
		    glbCommandQueue:push(self.command)
		end
		self.hasrepeat = true
	    end
	else
	    if self.time > BUTTON_REPEAT_DELAY then
		self.time = self.time - BUTTON_REPEAT_DELAY
		if not self.triggerup then
		    glbCommandQueue:push(self.command)
		end
		self.hasrepeat = true
	    end
	end
    end
end


Sprite = {}
Sprite.__index = Sprite

function Sprite.new(imagesheet, tx, ty, width, height)
    local v = {}
    tx = tx or 0
    ty = ty or 0
    v.width = width or imagesheet:getWidth()
    v.height = height or imagesheet:getHeight()
    v.quad = love.graphics.newQuad(tx*width, ty*height, width, height, imagesheet)
    v.image = imagesheet
    setmetatable(v, Sprite)
    return v
end

function Sprite.alias(newname, origname)
    glbSprites[newname] = glbSprites[origname]
end

function Sprite.lookup(name)
    return glbSprites[name]
end

-- w & h optional, uses native res if not provided.
-- variant is ignored, used by lists
function Sprite:render(x, y, w, h, variant)
    if getmetatable(x) == Rect then
	return self:render(x.x, x.y, x.w, x.h, y)
    end
    w = w or self.width
    h = h or self.height
    love.graphics.draw(self.image, self.quad, x, y, 0, w/self.width, h/self.height)
end

function Sprite:numVariant(depth)
    return 1
end

function Sprite:selectVariant01(ratio)
    return 1
end

SpriteList = {}
SpriteList.__index = SpriteList

function SpriteList.new(list)
    local v = {}
    v.list = list
    setmetatable(v, SpriteList)
    return v
end

function SpriteList.newFromNames(list)
    local v = {}
    v.list = {}
    for i = 1,#list do
	v.list[#v.list+1] = glbSprites[list[i]]
    end
    setmetatable(v, SpriteList)
    return v
end

function SpriteList.registerFromNames(name, list)
    local s = SpriteList.newFromNames(list)
    glbSprites[name] = s
    return s
end

function SpriteList.registerFromSeq(name, count, srcname, start)
    start = start or 1
    count = count or 1
    srcname = srcname or name

    local list = {}
    for i = start, start+count-1 do
	list[#list+1] = srcname .. tostring(i)
    end

    return SpriteList.registerFromNames(name, list)
end

-- w & h optional, uses native res if not provided.
function SpriteList:render(x, y, w, h, variant)
    if getmetatable(x) == Rect then
	return self:render(x.x, x.y, x.w, x.h, y)
    end
    local nextvariant = nil
    if type(variant) == 'table' then
	nextvariant = variant
	variant = variant[#variant]
	nextvariant[#nextvariant] = nil
    end
    variant = variant or 1
    variant = math.min(variant, #self.list)
    -- max second so zero sized gets a 1 index.
    variant = math.max(1, variant)
    self.list[variant]:render(x, y, w, h, nextvariant)
end

function SpriteList:numVariant(depth)
    depth = depth or 1
    if depth > 1 then
	return self.list[1]:numVariant(depth - 1)
    end
    return #self.list
end

function SpriteList:selectVariant01(ratio)
    local 	total = self:numVariant()

    -- we want to equally distribute 1..total
    ratio = ratio * total	-- base 1 for the win
    local	choice = math.floor(ratio) + 1	-- base 1 for the loss
    choice = math.min(total, choice)
    choice = math.max(1, choice)
    return choice
end
    
SpriteSheet = {}
SpriteSheet.__index = SpriteSheet

function SpriteSheet.new(path, width, height, usenearest)
    local v = {}
    v.image = love.graphics.newImage(path)
    if usenearest then
	v.image:setFilter("nearest", "nearest")
    end
    v.width = width
    v.height = height
    setmetatable(v, SpriteSheet)
    return v
end

function SpriteSheet:register(name, x, y)
    glbSprites[name] = Sprite.new(self.image, x, y, self.width, self.height)
    return glbSprites[name]
end

function SpriteSheet:registerRect(newname, name, x, y, w, h, start)
    start = start or 1
    local origstart = start

    for dx = 1,w do
	self:registerSeq(nil, name, x+dx-1, y, h, 0, 1, start)
	start = start + h
    end
    if newname then
	SpriteList.registerFromSeq(newname, w*h, name, origstart)
    end
end

function SpriteSheet:registerRectFirstX(newname, name, x, y, w, h, start)
    start = start or 1
    local origstart = start

    for dy = 1,h do
	self:registerSeq(nil, name, x, y+dy-1, w, 1, 0, start)
	start = start + w
    end
    if newname then
	SpriteList.registerFromSeq(newname, w*h, name, origstart)
    end
end

function SpriteSheet:registerSeqRect(newname, name, x, y, count, dx, dy, start, w, h)
    dx = dx or 1
    dy = dy or 0
    w = w or 1
    h = h or 1
    start = start or 1
    for i = start,start+count-1 do
	-- sub list is one based.
	self:registerRect(name..i, name..i.."_", x, y, w, h, 1)
	x = x + dx * w
	y = y + dy * h
    end
    if newname then
	SpriteList.registerFromSeq(newname, count, name, start)
    end
end

function SpriteSheet:registerSeq(newname, name, x, y, count, dx, dy, start)
    dx = dx or 1
    dy = dy or 0
    start = start or 1
    for i = start,start+count-1 do
	glbSprites[name .. tostring(i)] = Sprite.new(self.image, x, y, self.width, self.height)
	x = x + dx
	y = y + dy
    end
    if newname then
	SpriteList.registerFromSeq(newname, count, name, start)
    end
end
