LayoutPennant = {}
setmetatable(LayoutPennant, Layout)
-- ----------------------------------------------------------------------------
LayoutPennant.__index = function(t, k)
	if rawget(t, k) ~= nil then
		return rawget(t, k)
	elseif LayoutPennant[k] ~= nil then
		return LayoutPennant[k]
	end
end
-- ----------------------------------------------------------------------------
function LayoutPennant:setup(slots, pieces, resetButton)
    self.mPieces        = pieces
    self.mSlots		    = slots
	self.mReset         = resetButton
    self.mPieceSelected = nil
    self.mMouseDX       = 0
    self.mMouseDY       = 0
    self.mPuzzle 		= true
end
-- ----------------------------------------------------------------------------
function LayoutPennant:onInitialize()
    -- initialize slots
	self:initSlots()
	-- initialize pieces
	self:initPieces()
    -- 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 LayoutPennant:onMouseDown(x, y)
	local result = Layout.onMouseDown(self, x, y)
    if (self:isPuzzling()) then
        for piece, settings in pairs(self.mPieces) do
            if (piece:isActive() and piece:isInside(x, y)) then
               -- select piece
               self:selectPiece(piece)
               self.mMouseDX = piece:localX(x, y)
               self.mMouseDY = piece:localY(x, y)
               return true
            end
        end
    end
	return result
end
-- ----------------------------------------------------------------------------
function LayoutPennant:onMouseMoved(x, y)
    local result = Layout.onMouseMoved(self, x, y)
    if (self:isPuzzling()) then
        if (self.mPieceSelected ~= nil) then
            -- get nearest free neighbor
            local minHypSlot        = math.huge
            local nearestSlot	    = nil
            local minHypNeighbor    = math.huge
            local nearestNeighbor   = nil
            local pieceSelected     = self.mPieceSelected
            local nearestPath		= {}
            x = x - self.mMouseDX
            y = y - self.mMouseDY
            for i, slot in ipairs(self.mSlots) do
                if ((pieceSelected.slot == slot) or (self:isNeighbor(pieceSelected.slot, slot) and self:isValidMove(pieceSelected, slot))) then
                    local slotx = slot.sprite:globalX(0, 0)
                    local sloty = slot.sprite:globalY(0, 0)
                    -- get delta x and y                
                    local dx = math.abs(slotx - x)
                    local dy = math.abs(sloty - y)
                    -- get delta hyp
                    local h  = math.sqrt((dx * dx) + (dy * dy))
                    -- check if closer then previous
                    if (h < minHypSlot) then
                        minHypSlot  = h
                        nearestSlot = slot
                    end
                    if (slot ~= pieceSelected.slot and h < minHypNeighbor) then
						minHypNeighbor	 = h
						nearestNeighbor	 = slot
						-- set path props
						nearestPath.sx   = math.min(pieceSelected.slot.sprite:globalX(0, 0), slotx)
			            nearestPath.sy   = math.min(pieceSelected.slot.sprite:globalY(0, 0), sloty)
						nearestPath.ex   = math.max(pieceSelected.slot.sprite:globalX(0, 0), slotx)
						nearestPath.ey   = math.max(pieceSelected.slot.sprite:globalY(0, 0), sloty)
                    end
                end
            end
            -- move to nearest
            if (nearestSlot ~= nil and nearestSlot ~= pieceSelected.slot) then
                self:setPiece(self.mPieceSelected, nearestSlot)
                playAudioTrack("SLIDE_BLOCK")
            end
            -- move piece to path
            if (nearestNeighbor ~= nil) then
                local px = math.min(nearestPath.ex, math.max(x, nearestPath.sx))
                local py = math.min(nearestPath.ey, math.max(y, nearestPath.sy))
                if (self.mPieceSelected.slot.idx == 7) then
                    local br = true
                end                
				self.mPieceSelected:setX(self.mPieceSelected.slot.sprite:localX(px, py))
				self.mPieceSelected:setY(self.mPieceSelected.slot.sprite:localY(px, py)) 
			end
        end
    end
    return result    
end
-- ----------------------------------------------------------------------------
function LayoutPennant:onMouseUp(x, y)
    local result = Layout.onMouseUp(self, x, y)
    if (self:isPuzzling()) then
    	print("test")
        if (self.mPieceSelected ~= nil) then
		    self:setPiece(self.mPieceSelected, self.mPieceSelected.slot)
		end
        self:deselectPiece()
		if (self:checkSolved()) then
			self:solve()
		end
    end
    return result
end
-- ----------------------------------------------------------------------------
function LayoutPennant:onClickIO(object, x, y)
	return Layout.onClickIO(self, object, x, y)	
end
-- ----------------------------------------------------------------------------
function LayoutPennant:onClickButton(button)
    local result = Layout.onClickButton(self, button)
    if (self:isPuzzling()) then
	    if (button == self.mReset) then
    		-- return to initial state
		    self:resetPuzzle()
		    -- useful to test end animation
		    -- self:solve()
		    return true
	    end
	end
	return result
end
-- ----------------------------------------------------------------------------
function LayoutPennant:onSolve()
	Layout.onSolve(self)
	-- solve
    if (not self:checkSolved()) then
        self:resetPieces()
        for piece, settings in pairs(self.mPieces) do
            local slot = self:getSlot(settings.solution.col, settings.solution.row)
            self:setPiece(piece, slot)
        end
    end
end
-- ----------------------------------------------------------------------------
function LayoutPennant:onSolveStart()
	Layout.onSolveStart(self)
    -- anim
    self:animSolved()
end
-- ----------------------------------------------------------------------------
function LayoutPennant:onAnimationFinish(animationName)
	Layout.onAnimationFinish(self, animationName)
end
-- ----------------------------------------------------------------------------
function LayoutPennant:initSlots()
	local settings = self.mSlots.settings
	local index    = 1
    for row=1, settings.numRows do
        for col=1, settings.numCols do
            local sx     = settings.topLeftX + ((col - 1) * settings.width)
            local sy     = settings.topLeftY + ((row - 1) * settings.height)
            local slot   = {}
            slot.sprite  = settings.parent:newChildSprite( { name = "slot" .. sx .. sy, x=sx, y=sy } )
            slot.sprite.globalX = self.globalX
            slot.sprite.globalY = self.globalY
            slot.sprite.localX  = self.localX
            slot.sprite.localY  = self.localY            
            slot.piece   = nil
            slot.row     = row
            slot.col     = col
            slot.blocked = false
            table.insert(self.mSlots, index, slot)
            index 		 = index + 1
        end
    end
    local blocked = self.mSlots.blocked
    if (blocked ~= nil) then
    	for i, position in ipairs(blocked) do
	    	local slot = self:getSlot(position.col, position.row)
    		slot.blocked = true
    	end
    end
end
-- ----------------------------------------------------------------------------
function LayoutPennant:initPieces()
    for piece, settings in pairs(self.mPieces) do
        piece.slot    = nil
        local slot 	  = self:getSlot(settings.start.col, settings.start.row)
        self:setPiece(piece, slot)
    end
end
-- ----------------------------------------------------------------------------
function LayoutPennant:checkSolved()
	print("checkSolved")
    for piece, settings in pairs(self.mPieces) do
        if (settings.solution.required) then
            if (piece.slot.col ~= settings.solution.col or piece.slot.row ~= settings.solution.row) then
            	print("piece " .. piece:name() .. " is in col " .. piece.slot.col .. " row " .. piece.slot.row)
            	print("piece " .. piece:name() .. " should be in col " .. settings.solution.col .. " row " .. settings.solution.row)
                return false
            end
        end
    end
    print("checkSolved is true")
    return true
end
-- ----------------------------------------------------------------------------
function LayoutPennant:setPiece(piece, slot)
	local settings = self.mPieces[piece]
    if (piece.slot ~= nil) then
        for row = piece.slot.row, piece.slot.row + settings.dimensions.rows-1 do
            for col = piece.slot.col, piece.slot.col + settings.dimensions.cols-1 do
                self:getSlot(col, row).piece = nil
            end
        end
    end
    piece.slot = slot
    piece:becomeChildOf(slot.sprite)
    piece:setX(0)
    piece:setY(0)
    for row = slot.row, slot.row + settings.dimensions.rows-1 do
        for col = slot.col, slot.col + settings.dimensions.cols-1 do
            -- slot should be free, clear all slots before reset
            local s = self:getSlot(col, row)
            assert(s.piece == nil)
            s.piece = piece
        end
    end
end
-- ----------------------------------------------------------------------------
function LayoutPennant:resetPieces()
    for i, slot in ipairs(self.mSlots) do
        if (slot.piece ~= nil) then
            slot.piece.slot = nil
            slot.piece      = nil
        end
    end
end
-- ----------------------------------------------------------------------------
function LayoutPennant:resetPuzzle()
    self:resetPieces()
    for piece, settings in pairs(self.mPieces) do
        local slot = self:getSlot(settings.start.col, settings.start.row)
        self:setPiece(piece, slot)
	end
end
-- ----------------------------------------------------------------------------
function LayoutPennant:getSlot(col, row)
    local settings = self.mSlots.settings
    if (col > settings.numCols) then
        return nil
    end
    if (row > settings.numRows) then
        return nil
    end
    local idx      = (row - 1) * settings.numCols + col
    assert(idx <= #self.mSlots)
    return self.mSlots[idx]
end
-- ----------------------------------------------------------------------------
function LayoutPennant:hasPieceSelected()
    return self.mPieceSelected ~= nil
end
-- ----------------------------------------------------------------------------
function LayoutPennant:selectPiece(piece)
    self.mPieceSelected = piece
end
-- ----------------------------------------------------------------------------
function LayoutPennant:deselectPiece()
    self.mPieceSelected = nil
end
-- ----------------------------------------------------------------------------
function LayoutPennant:isValidMove(piece, slot)
	if (slot.blocked) then
		return false
	end
	local settings = self.mPieces[piece]
    for row = slot.row, slot.row + settings.dimensions.rows -1 do
        for col = slot.col, slot.col + settings.dimensions.cols-1 do
            local s = self:getSlot(col, row)
            if (s==nil or (s.piece ~= nil and s.piece ~= piece)) then
                return false
            end
        end
    end
    return true
end
-- ----------------------------------------------------------------------------
function LayoutPennant:isNeighbor(slot, neighbor)
    -- vertical neighbor check
    if (slot.row == neighbor.row) then
        if (neighbor.col == slot.col - 1 or neighbor.col == slot.col + 1) then
            return true
        end
    end
    -- horizontal neighbor check
    if (slot.col == neighbor.col) then
        if (neighbor.row == slot.row - 1 or neighbor.row == slot.row + 1) then
            return true
        end
    end
    return false
end
-- ----------------------------------------------------------------------------
function LayoutPennant:animSolved()
    self:animate(
        { call=LayoutPennant.solveFinish }
    )
end