-------------------------------------------------------------------------------
-- Spine Runtimes Software License v2.5
--
-- Copyright (c) 2013-2016, Esoteric Software
-- All rights reserved.
--
-- You are granted a perpetual, non-exclusive, non-sublicensable, and
-- non-transferable license to use, install, execute, and perform the Spine
-- Runtimes software and derivative works solely for personal or internal
-- use. Without the written permission of Esoteric Software (see Section 2 of
-- the Spine Software License Agreement), you may not (a) modify, translate,
-- adapt, or develop new applications using the Spine Runtimes or otherwise
-- create derivative works or improvements of the Spine Runtimes or (b) remove,
-- delete, alter, or obscure any trademarks or any copyright, trademark, patent,
-- or other intellectual property or proprietary rights notices on or in the
-- Software, including any copy thereof. Redistributions in binary or source
-- form must include this license and terms.
--
-- THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
-- IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
-- MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
-- EVENT SHALL ESOTERIC SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-- SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-- PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS INTERRUPTION, OR LOSS OF
-- USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
-- IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-- POSSIBILITY OF SUCH DAMAGE.
-------------------------------------------------------------------------------

spine = {}

spine.utils = require "spine-lua.utils"
spine.SkeletonJson = require "spine-lua.SkeletonJson"
spine.SkeletonData = require "spine-lua.SkeletonData"
spine.BoneData = require "spine-lua.BoneData"
spine.SlotData = require "spine-lua.SlotData"
spine.IkConstraintData = require "spine-lua.IkConstraintData"
spine.Skin = require "spine-lua.Skin"
spine.Attachment = require "spine-lua.attachments.Attachment"
spine.BoundingBoxAttachment = require "spine-lua.attachments.BoundingBoxAttachment"
spine.RegionAttachment = require "spine-lua.attachments.RegionAttachment"
spine.MeshAttachment = require "spine-lua.attachments.MeshAttachment"
spine.VertexAttachment = require "spine-lua.attachments.VertexAttachment"
spine.PathAttachment = require "spine-lua.attachments.PathAttachment"
spine.PointAttachment = require "spine-lua.attachments.PointAttachment"
spine.ClippingAttachment = require "spine-lua.attachments.ClippingAttachment"
spine.Skeleton = require "spine-lua.Skeleton"
spine.Bone = require "spine-lua.Bone"
spine.Slot = require "spine-lua.Slot"
spine.IkConstraint = require "spine-lua.IkConstraint"
spine.AttachmentType = require "spine-lua.attachments.AttachmentType"
spine.AttachmentLoader = require "spine-lua.AttachmentLoader"
spine.Animation = require "spine-lua.Animation"
spine.AnimationStateData = require "spine-lua.AnimationStateData"
spine.AnimationState = require "spine-lua.AnimationState"
spine.EventData = require "spine-lua.EventData"
spine.Event = require "spine-lua.Event"
spine.SkeletonBounds = require "spine-lua.SkeletonBounds"
spine.BlendMode = require "spine-lua.BlendMode"
spine.TextureAtlas = require "spine-lua.TextureAtlas"
spine.TextureRegion = require "spine-lua.TextureRegion"
spine.TextureAtlasRegion = require "spine-lua.TextureAtlasRegion"
spine.AtlasAttachmentLoader = require "spine-lua.AtlasAttachmentLoader"
spine.Color = require "spine-lua.Color"
spine.Triangulator = require "spine-lua.Triangulator"
spine.SkeletonClipping = require "spine-lua.SkeletonClipping"
spine.JitterEffect = require "spine-lua.vertexeffects.JitterEffect"
spine.SwirlEffect = require "spine-lua.vertexeffects.SwirlEffect"
spine.Interpolation = require "spine-lua.Interpolation"

spine.utils.readFile = function (fileName, base)
	if not base then base = system.ResourceDirectory end
	local path = system.pathForFile(fileName, base)
	local file = io.open(path, "r")
	if not file then return nil end
	local contents = file:read("*a")
	io.close(file)
	return contents
end

local json = require "json"
spine.utils.readJSON = function (text)
	return json.decode(text)
end

local QUAD_TRIANGLES = { 1, 2, 3, 3, 4, 1 }
spine.Skeleton.new_super = spine.Skeleton.new
spine.Skeleton.updateWorldTransform_super = spine.Skeleton.updateWorldTransform
spine.Skeleton.new = function(skeletonData, group)
	self = spine.Skeleton.new_super(skeletonData)
	self.group = group or display.newGroup()
	self.drawingGroup = nil
	self.premultipliedAlpha = false
	self.batches = 0
	self.tempColor = spine.Color.newWith(1, 1, 1, 1)
	self.tempColor2 = spine.Color.newWith(-1, 1, 1, 1)
	self.tempVertex = {
		x = 0,
		y = 0,
		u = 0,
		v = 0,
		light = spine.Color.newWith(1, 1, 1, 1),
		dark = spine.Color.newWith(0, 0, 0, 0)
	}
	self.clipper = spine.SkeletonClipping.new()
	return self
end

local function colorEquals(color1, color2)
	return color1.r == color2.r and color1.g == color2.g and color1.b == color2.b and color1.a == color2.a
end

local function toCoronaBlendMode(blendMode)
	if blendMode == spine.BlendMode.normal then
		return "normal"
	elseif blendMode == spine.BlendMode.additive then
		return "add"
	elseif blendMode == spine.BlendMode.multiply then
		return "multiply"
	elseif blendMode == spine.BlendMode.screen then
		return "screen"
	end
end

local worldVertices = spine.utils.newNumberArray(10000 * 8)

function spine.Skeleton:updateWorldTransform()
	spine.Skeleton.updateWorldTransform_super(self)
	local premultipliedAlpha = self.premultipliedAlpha

	self.batches = 0
	
	if (self.vertexEffect) then self.vertexEffect:beginEffect(self) end

	-- Remove old drawing group, we will start anew
	if self.drawingGroup then self.drawingGroup:removeSelf() end
	local drawingGroup = display.newGroup()
	self.drawingGroup = drawingGroup
	self.group:insert(drawingGroup)

	local drawOrder = self.drawOrder
	local currentGroup = nil
	local groupVertices = {}
	local groupIndices = {}
	local groupUvs = {}
	local color = self.tempColor
	local lastColor = self.tempColor2
	lastColor.r = -1
	local texture = nil
	local lastTexture = nil
	local blendMode = nil
	local lastBlendMode = nil
	local renderable = {
		vertices = nil,
		uvs = nil
	}
	
	for i,slot in ipairs(drawOrder) do
		local attachment = slot.attachment
		local vertices = nil
		local uvs = nil
		local numVertices = 0
		local indices = nil
		if attachment then
			if attachment.type == spine.AttachmentType.region then
				numVertices = 4
				vertices = worldVertices
				attachment:computeWorldVertices(slot.bone, vertices, 0, 2)
				uvs = attachment.uvs
				indices = QUAD_TRIANGLES
				texture = attachment.region.renderObject.texture				
				blendMode = toCoronaBlendMode(slot.data.blendMode)
			elseif attachment.type == spine.AttachmentType.mesh then
				numVertices = attachment.worldVerticesLength / 2
				vertices = worldVertices
				attachment:computeWorldVertices(slot, 0, attachment.worldVerticesLength, vertices, 0, 2)
				uvs = attachment.uvs
				indices = attachment.triangles
				texture = attachment.region.renderObject.texture
				blendMode = toCoronaBlendMode(slot.data.blendMode)
			elseif attachment.type == spine.AttachmentType.clipping then
				self.clipper:clipStart(slot, attachment)
			end

			if texture and vertices and indices then
				local skeleton = slot.bone.skeleton
				local skeletonColor = skeleton.color
				local slotColor = slot.color
				local attachmentColor = attachment.color
				local alpha = skeletonColor.a * slotColor.a * attachmentColor.a
				local multiplier = alpha
				if premultipliedAlpha then multiplier = 1 end
				color:set(skeletonColor.r * slotColor.r * attachmentColor.r * multiplier,
									skeletonColor.g * slotColor.g * attachmentColor.g * multiplier,
									skeletonColor.b * slotColor.b * attachmentColor.b * multiplier,
									alpha)
				
				if not lastTexture then lastTexture = texture end
				if lastColor.r == -1 then lastColor:setFrom(color) end
				if not lastBlendMode then lastBlendMode = blendMode end

				if (texture ~= lastTexture or not colorEquals(color, lastColor) or blendMode ~= lastBlendMode) then
					self:flush(groupVertices, groupUvs, groupIndices, lastTexture, lastColor, lastBlendMode, drawingGroup)
					lastTexture = texture
					lastColor:setFrom(color)
					lastBlendMode = blendMode
					groupVertices = {}
					groupUvs = {}
					groupIndices = {}
				end
				
				if self.clipper:isClipping() then
					self.clipper:clipTriangles(vertices, uvs, indices, #indices)
					vertices = self.clipper.clippedVertices
					numVertices = #vertices / 2
					uvs = self.clipper.clippedUVs
					indices = self.clipper.clippedTriangles
				end

				self:batch(vertices, uvs, numVertices, indices, groupVertices, groupUvs, groupIndices)
			end
			
			self.clipper:clipEnd(slot)
		end
	end

	if #groupVertices > 0 then
		self:flush(groupVertices, groupUvs, groupIndices, texture, color, blendMode, drawingGroup)
	end
	
	self.clipper:clipEnd2()
	if (self.vertexEffect) then self.vertexEffect:endEffect() end
end

function spine.Skeleton:flush(groupVertices, groupUvs, groupIndices, texture, color, blendMode, drawingGroup)
	local mesh = display.newMesh(drawingGroup, 0, 0, {
			mode = "indexed",
			vertices = groupVertices,
			uvs = groupUvs,
			indices = groupIndices
	})
	mesh.fill = texture
	mesh:setFillColor(color.r, color.g, color.b)
	mesh.alpha = color.a
	mesh.blendMode = blendMode
	mesh:translate(mesh.path:getVertexOffset())
	self.batches = self.batches + 1
end

function spine.Skeleton:batch(vertices, uvs, numVertices, indices, groupVertices, groupUvs, groupIndices)
	local numIndices = #indices
	local i = 1
	local indexStart = #groupIndices + 1
	local offset = #groupVertices / 2
	local indexEnd = indexStart + numIndices

	while indexStart < indexEnd do
		groupIndices[indexStart] = indices[i] + offset
		indexStart = indexStart + 1
		i = i + 1
	end

	i = 1
	local vertexStart = #groupVertices + 1
	local vertexEnd = vertexStart + numVertices * 2
	if (self.vertexEffect) then
		local effect = self.vertexEffect
		local vertex = self.tempVertex
		while vertexStart < vertexEnd do
			vertex.x = vertices[i]
			vertex.y = vertices[i+1]
			vertex.u = uvs[i]
			vertex.v = uvs[i+1]
			effect:transform(vertex);
			groupVertices[vertexStart] = vertex.x
			groupVertices[vertexStart+1] = vertex.y
			groupUvs[vertexStart] = vertex.u
			groupUvs[vertexStart+1] = vertex.v
			vertexStart = vertexStart + 2
			i = i + 2
		end
	else
		while vertexStart < vertexEnd do
			groupVertices[vertexStart] = vertices[i]
			groupVertices[vertexStart+1] = vertices[i+1]
			groupUvs[vertexStart] = uvs[i]
			groupUvs[vertexStart+1] = uvs[i+1]
			vertexStart = vertexStart + 2
			i = i + 2
		end
	end
end

return spine
