LayoutRotascope = {}
setmetatable(LayoutRotascope, Layout)
-- ----------------------------------------------------------------------------
LayoutRotascope.mColorCorrect = {alpha=1, red=0, green=1, blue=0}
LayoutRotascope.mColorIncorrect = {alpha=1, red=1, green=1, blue=1}
-- ----------------------------------------------------------------------------
LayoutRotascope.__index = function(t, k)

	if rawget(t, k) ~= nil then
		return rawget(t, k)
	elseif LayoutRotascope[k] ~= nil then
		return LayoutRotascope[k]
	end
	
end
-- ----------------------------------------------------------------------------
function LayoutRotascope:setup(pivots, elipses, pins, resetButton)

	assert(type(pivots) == "table")
	assert(type(elipses) == "table")
	
	self.mPivots = pivots
	self.mElipses = elipses
	self.mPins = pins
	self.mReset = resetButton
	self.mPuzzle = true
	self.mColor = true

end
-- ----------------------------------------------------------------------------
function LayoutRotascope:onInitialize()

	assert(#self.mPivots > 0)
	assert(#self.mElipses > 0)
	assert(#self.mPins > 0)
	
	-- all elipses are center anchored
	for i, elipse in ipairs(self.mElipses) do
		elipse:relocateAnchorPoint("center")
		elipse:relocatePivotPoint("center")
	end
	
	-- all pivots are center anchored
	for i, pivot in ipairs(self.mPivots) do
		pivot:relocateAnchorPoint("center")
		pivot:relocatePivotPoint("center")
	end	
	
	-- all pins are center anchored
	for i, pin in ipairs(self.mPins) do
		pin:relocateAnchorPoint("center")
		pin:relocatePivotPoint("center")
	end		
		
	-- store all solve positions of elipses
	-- these need to be their positions for the puzzle to be solved
	for i, elipse in ipairs(self.mElipses) do
		elipse.rootLX = elipse:x()
		elipse.rootLY = elipse:y()
		elipse.solveSX = {elipse:screenX()}
		elipse.solveSY = {elipse:screenY()}
		elipse.solveD = 0		
		if (elipse.alts) then
			for i, alt in ipairs(elipse.alts) do
				assert(alt.type)
				assert(alt.type == kObject)
				assert(alt ~= elipse)
				table.insert(elipse.solveSX, alt:screenX())
				table.insert(elipse.solveSY, alt:screenY())
			end
		end
	end
	
	-- generate an initial starting position
	local moves = 80
	for i = 1, moves do
		local pivotIndex = math.random(#self.mPivots)
		local pivot = self.mPivots[pivotIndex]
		local elipses = self:calcElipses(pivot)
		
		-- rotate the elipse
		for i, elipse in ipairs(elipses) do
			elipse:rotate(360 / #elipses)
		end
		
		-- rotate the pin
		local pin = self.mPins[pivotIndex]
		if (pin) then
			pin:rotate(360 / #elipses)		
		end
	end
	
	-- set initial colors
	self:calcColors()
	
	-- relocate all pivots from elipses to the center
	for i, elipse in ipairs(self.mElipses) do
		elipse:relocatePivotPoint("center")
	end	
	
	-- store all starting positions of elipses
	-- used by resetPuzzle
	-- local space!
	for i, elipse in ipairs(self.mElipses) do
		elipse.startX = elipse:x()
		elipse.startY = elipse:y()
		elipse.startR = elipse:rotation()
	end	
	
	-- register all pivots as IOs
	for i, pivot in ipairs(self.mPivots) do
		self.mIOs[pivot] = {}	
	end
	
	-- active all pivots
	for i, pivot in ipairs(self.mPivots) do
		pivot:ss(kActive)
	end	
	
	-- register reset button as IO
	if (self.mReset) then
		self.mButtons[self.mReset] = {}
	end
	
	-- activate reset button
	if (self.mReset) then
		self.mReset:ss(kActive)
	end
	
	-- now call the base onInitialize,
	-- so our newly created props will still be handled
	Layout.onInitialize(self)	
	
end
-- ----------------------------------------------------------------------------
function LayoutRotascope:onClickIO(object, x, y)

	if (self:isPuzzling()) then
		local isPivotObject = false
		local pivotIndex = -1
		for i, pivot in ipairs(self.mPivots) do
			if (pivot == object) then
				isPivotObject = true
				pivotIndex = i
				break
			end
		end
		
		if (isPivotObject) then
			local rotatables = self:calcElipses(object)
			assert(rotatables)
			
			-- animate the elipses
			local duration = 500
			for i, elipse in ipairs(rotatables) do
				elipse:animate("Rotate" .. elipse:name() .. "_blocking",
					{ drotation = 360 / #rotatables, duration = duration }				
				)
				self:playRotateSound()
				-- if it's correct now, it can't be correct after rotation
				if (self.mColor) then
					if (elipse.correct) then
						elipse:setColor(self.mColorIncorrect)
					end
				end
			end
			
			-- animate the pin
			local pin = self.mPins[pivotIndex]
			if (pin) then
				pin:relocatePivotPoint("center")
				pin:animate("Rotate" .. pin:name() .. "_blocking",
					{ drotation = 360 / #rotatables, duration = duration }				
				)
			end
			
			-- block input during animation
			self:animate("RotateAroundPivot_blocking",
				{ delay = duration }
			)
			
			-- handled
			return true, false
			
		end
	end
	
	return Layout.onClickIO(self, object, x, y)
	
end

-- ----------------------------------------------------------------------------
function LayoutRotascope:playRotateSound()
	-- empty
end
-- ----------------------------------------------------------------------------
function LayoutRotascope:onClickButton(button)

	if (button == self.mReset) then
		-- return to initial state
		self:resetPuzzle()
		
		-- useful to test end animation			
		-- self:solve()
		
		return true			
	end
	
	return Layout.onClickButton(self, button)

end
-- ----------------------------------------------------------------------------
function LayoutRotascope:onAnimationFinish(animationName)

	Layout.onAnimationFinish(self, animationName)
	
	if (animationName == "RotateAroundPivot_blocking") then
		-- calculate the colors of the elipses
		self:calcColors()
		
		-- check if all are correct, if so, call solve()
		local solve = true
		for i, elipse in ipairs(self.mElipses) do
			if (not elipse.correct) then
				solve = false
				break
			end
		end
		
		if (solve) then
			self:solve()
		end
	end

end
-- ----------------------------------------------------------------------------
function LayoutRotascope:onSolve()

	Layout.onSolve(self)
	
	local solvedManually = true
	for i, elipse in ipairs(self.mElipses) do
		if (not elipse.correct) then
			solvedManually = false
			break
		end	
	end
	
	-- set all solve positions
	if (not solvedManually) then
		for i, elipse in ipairs(self.mElipses) do
			assert(elipse.rootLX)
			assert(elipse.rootLY)
			elipse:relocatePivotPoint("center")		
			elipse:setRotation(0)		
			elipse:setX(elipse.rootLX)
			elipse:setY(elipse.rootLY)
		end	
	end
	
	-- calculate colors
	self:calcColors()
	
	-- set all pins in original rotation
	for i, pin in ipairs(self.mPins) do
		pin:setRotation(0)	
	end
	
	-- make pivots idle
	for i, pivot in ipairs(self.mPivots) do
		pivot:ss(kIdle)
	end
	
	-- make reset idle
	if (self.mReset) then
		self.mReset:ss(kIdle)
	end

end
-- ----------------------------------------------------------------------------
function LayoutRotascope:resetPuzzle()

	-- relocate all pivots from elipses to the center
	for i, elipse in ipairs(self.mElipses) do
		elipse:relocatePivotPoint("center")
	end	
	
	-- restore all starting positions
	-- local space!
	for i, elipse in ipairs(self.mElipses) do
		assert(elipse.startX)
		assert(elipse.startY)
		assert(elipse.startR)
		
		elipse:setX(elipse.startX)
		elipse:setY(elipse.startY)
		elipse:setRotation(elipse.startR)
	end	

end
-- ----------------------------------------------------------------------------
-- WARNING:
-- this function changes the pivot points of the found elipses!
function LayoutRotascope:calcElipses(pivot)

	assert(pivot)
	
	-- calculate the distance from the pivot to every elipse
	local x1 = pivot:screenX()
	local y1 = pivot:screenY()
	for i, elipse in ipairs(self.mElipses) do
		local x2 = elipse:screenX()
		local y2 = elipse:screenY()
		elipse.distance = math.distance(x1, y1, x2, y2)
	end
	
	-- sort the elipses list on shortest distance
	table.sort(self.mElipses, function(a, b) return a.distance < b.distance end)
	
	-- get the first three objects
	local rotatables = {}
	for i = 1, 3 do
		table.insert(rotatables, self.mElipses[i])
	end
	
	-- relocate their pivot points
	local pivotX = pivot:width()/2
	local pivotY = pivot:height()/2
	pivotX = pivot:screenX()
	pivotY = pivot:screenY()
	for i, elipse in ipairs(rotatables) do
		local localPivotX = elipse:localX(pivotX, pivotY)
		local localPivotY = elipse:localY(pivotX, pivotY)
		elipse:relocatePivotPoint(localPivotX, localPivotY)
	end	
	
	return rotatables

end
-- ----------------------------------------------------------------------------
function LayoutRotascope:calcCorrect()

	for i, elipse in ipairs(self.mElipses) do
		assert(elipse.solveSX)
		assert(elipse.solveSY)
		assert(type(elipse.solveSX) == "table")
		assert(type(elipse.solveSY) == "table")
		assert(#elipse.solveSX == #elipse.solveSY)
		
		local elipseSX = elipse:screenX()
		local elipseSY = elipse:screenY()
				
		local correct = false
		local count = #elipse.solveSX
		local error = 2
		for i = 1, count do
			local solveSX = elipse.solveSX[i]
			local solveSY = elipse.solveSY[i]
			local distance = math.distance(elipseSX, elipseSY, solveSX, solveSY)
			
			correct = (distance < (elipse.solveD + error))
			if (correct) then
				break
			end
		end

		elipse.correct = correct
	end

end
-- ----------------------------------------------------------------------------
function LayoutRotascope:calcColors()

	self:calcCorrect()

	if (self.mColor) then
		for i, elipse in ipairs(self.mElipses) do
			if (elipse.correct) then
				elipse:setColor(self.mColorCorrect)
			else
				elipse:setColor(self.mColorIncorrect)
			end
		end
	end

end