-- Playdate CoreLibs: Sprites
-- Copyright (C) 2014 Panic, Inc.

import "CoreLibs/animator"
import "CoreLibs/object"


local Rect = playdate.geometry.rect
local Point = playdate.geometry.point
local gfx = playdate.graphics
local spritelib = gfx.sprite


local newfunc = spritelib.new


-- make sprite inherit from Object
spritelib.__index = spritelib
spritelib.super = Object
setmetatable(spritelib, Object)
spritelib.className = "Sprite"


-- Object's "extends" function uses the result of this function to allow inheritance from non-Object types
spritelib.baseObject = newfunc


-- init function for Object subclass
function spritelib.init(self, imageOrTilemap)
	if imageOrTilemap ~= nil then
		if getmetatable(imageOrTilemap) == gfx.image then
			self:setImage(imageOrTilemap)
		elseif getmetatable(imageOrTilemap) == gfx.tilemap then
			self:setTilemap(imageOrTilemap)
		end
	end

	self:moveTo(0,0)
end


-- allow sprites to work without using object.lua
function spritelib.new(arg1, arg2)
	local o = newfunc(arg2 or arg1) -- allow for both spritelib.new() and spritelib:new()
	o:init()
	return o
end


function spritelib.performOnAllSprites(func)
	local allSprites = spritelib:getAllSprites()
	for i = 1, #allSprites do
		func(allSprites[i])
	end
end


local _copy = spritelib.copy
function spritelib:copy()
	local copy = _copy(self)
	setmetatable(copy, getmetatable(self))	
	return copy
end


function spritelib.addEmptyCollisionSprite(x, ...)

	local y, w, h, radius
	if (type(x) == "userdata") then		-- check if x is a playdate.geometry.rect object
		x, y, w, h = x.x, x.y, x.width, x.height
		radius = select(1, ...)
	else
		y, w, h, radius = select(1, ...)
	end
	
	local sprite = spritelib.new()
	sprite:setBounds(x, y, w, h)
	sprite:setCollideRect(0, 0, w, h)
	sprite:setUpdatesEnabled(false)
	sprite:setVisible(false)
	sprite:add()
	return sprite
end


function spritelib.addWallSprites(tilemap, emptyIDs, xOffset, yOffset)

	xOffset = xOffset or 0
	yOffset = yOffset or 0

	local tileWidth, tileHeight = tilemap:getSize()
	local pixelsWide, pixelsHigh = tilemap:getPixelSize()
	local tileWidthInPixels = pixelsWide / tileWidth
	local tileHeightInPixels = pixelsHigh / tileHeight

	local collisionRects = tilemap:getCollisionRects((emptyIDs or {}))
	local wallSprites = {}

	for i = 1, #collisionRects do
		
		local r = collisionRects[i]
		
		-- Create an invisible sprite (no image or update()) to be used by the collision system
	
		local x = r.x * tileWidthInPixels
		local y = r.y * tileHeightInPixels		
		local w = r.width * tileWidthInPixels
		local h = r.height * tileHeightInPixels
	
		local sprite = spritelib.new()
		sprite:setBounds(x+xOffset, y+yOffset, w, h)
		sprite:setCollideRect(0, 0, w, h)
		sprite:setUpdatesEnabled(false)
		sprite:setVisible(false)
		sprite:add()
	
		wallSprites[i] = sprite
	end

	return wallSprites
end

local bgsprite

function spritelib.setBackgroundDrawingCallback(drawCallback)
	bgsprite = gfx.sprite.new()
	bgsprite:setSize(playdate.display.getSize())
	bgsprite:setCenter(0, 0)
	bgsprite:moveTo(0, 0)
	bgsprite:setZIndex(-32768)
	bgsprite:setIgnoresDrawOffset(true)
	bgsprite:setUpdatesEnabled(false)
	bgsprite.draw = function(s, x, y, w, h)
			drawCallback(x, y, w, h)
		end
	bgsprite:add()
	return bgsprite
end

function spritelib.redrawBackground()
	if bgsprite ~= nil then bgsprite:markDirty() end
end

function spritelib:setAnimator(animator, moveWithCollisions, removeOnCollision)
	
	assert(getmetatable(animator) == playdate.graphics.animator, "`animator` must be of type CoreLibs/animator")
	if animator.startValue ~= nil then
		assert(getmetatable(animator.startValue) == playdate.geometry.point, "playdate.graphics.sprite:setAnimator(): `animator` must use a `playdate.geometry.point` for its start value.")
	end
	if animator.endValue ~= nil then
		assert(getmetatable(animator.endValue) == playdate.geometry.point, "playdate.graphics.sprite:setAnimator()'s `animator` argument must use a `playdate.geometry.point` for its end value.")
	end
	if moveWithCollisions then
		assert(self:getCollideRect() ~= nil, "Sprite must have a collideRect set to move with collisions.")
	end
	
	if self._animator ~= nil then
		self:removeAnimator()
	end
		
	self._update = self.update
	self._animator = animator

	self.update = function()

		if self._animator ~= nil then
			local p = self._animator:currentValue()
			
			if moveWithCollisions == nil or moveWithCollisions ~= true then
				self:moveTo(p)
			else
				local _, _, _, length = self:moveWithCollisions(p)
				if removeOnCollision == true and length > 0 then
					self:removeAnimator()
				end
			end
			
			if self._animator ~= nil and self._animator:ended() then
				self:removeAnimator()
			end
		end
		
		if self._update ~= nil then self:_update() end
	end
	
end


function spritelib:removeAnimator()
	self._animator = nil
	self.update = self._update
end


-- playdate.graphics.sprite.spriteWithText(text, maxWidth, [maxHeight, [bgColor, [leadingAdjustment, [truncationString, [alignment, [font]]]]]])
-- returns sprite, textWasTruncated
function spritelib.spriteWithText(str, maxWidth, maxHeight, bgColor, lineHeightAdjustment, truncator, textAlignment, singleFont)
	local textImage, truncated = gfx.imageWithText(str, maxWidth, maxHeight, bgColor, lineHeightAdjustment, truncator, textAlignment, singleFont)
	if textImage == nil then
		return nil, false
	end
	local sprite = spritelib.new(textImage)
	return sprite, truncated
end
