local Object = require 'libs.classic'
local Piece = require 'game.piece'

local Rotation = Object:extend()

Rotation.spawn_positions = {
    I = { x=3, y= -1 },
    J = { x=3, y= -1 },
    L = { x=3, y= -1 },
    O = { x=3, y= -1 },
    S = { x=3, y= -1 },
    T = { x=3, y= -1 },
    Z = { x=3, y= -1 },
}

Rotation.colourscheme = {
    I = "R",
    L = "O",
    J = "B",
    S = "M",
    Z = "G",
    O = "Y",
    T = "C",
}

Rotation.block_offsets = {
    I={
        { {x=0, y=1}, {x=1, y=1}, {x=2, y=1}, {x=3, y=1} },
        { {x=2, y=0}, {x=2, y=1}, {x=2, y=2}, {x=2, y=3} },
        { {x=0, y=1}, {x=1, y=1}, {x=2, y=1}, {x=3, y=1} },
        { {x=2, y=0}, {x=2, y=1}, {x=2, y=2}, {x=2, y=3} },
    },
    J={
        { {x=0, y=1}, {x=1, y=1}, {x=2, y=1}, {x=2, y=2} },
        { {x=1, y=0}, {x=1, y=1}, {x=1, y=2}, {x=0, y=2} },
        { {x=0, y=1}, {x=0, y=2}, {x=1, y=2}, {x=2, y=2} },
        { {x=1, y=0}, {x=2, y=0}, {x=1, y=1}, {x=1, y=2} },
    },
    L={
        { {x=0, y=1}, {x=0, y=2}, {x=1, y=1}, {x=2, y=1} },
        { {x=0, y=0}, {x=1, y=0}, {x=1, y=1}, {x=1, y=2} },
        { {x=0, y=2}, {x=1, y=2}, {x=2, y=1}, {x=2, y=2} },
        { {x=1, y=0}, {x=1, y=1}, {x=1, y=2}, {x=2, y=2} },
    },
    O={
        { {x=1, y=1}, {x=2, y=1}, {x=1, y=2}, {x=2, y=2} },
        { {x=1, y=1}, {x=2, y=1}, {x=1, y=2}, {x=2, y=2} },
        { {x=1, y=1}, {x=2, y=1}, {x=1, y=2}, {x=2, y=2} },
        { {x=1, y=1}, {x=2, y=1}, {x=1, y=2}, {x=2, y=2} },
    },
    S={
        { {x=1, y=1}, {x=2, y=1}, {x=0, y=2}, {x=1, y=2} },
        { {x=0, y=0}, {x=0, y=1}, {x=1, y=1}, {x=1, y=2} },
        { {x=1, y=1}, {x=2, y=1}, {x=0, y=2}, {x=1, y=2} },
        { {x=0, y=0}, {x=0, y=1}, {x=1, y=1}, {x=1, y=2} },
    },
    T={
        { {x=0, y=1}, {x=1, y=1}, {x=1, y=2}, {x=2, y=1} },
        { {x=0, y=1}, {x=1, y=0}, {x=1, y=1}, {x=1, y=2} },
        { {x=0, y=2}, {x=1, y=1}, {x=1, y=2}, {x=2, y=2} },
        { {x=1, y=0}, {x=1, y=1}, {x=2, y=1}, {x=1, y=2} },
    },
    Z={
        { {x=0, y=1}, {x=1, y=1}, {x=1, y=2}, {x=2, y=2} },
        { {x=2, y=0}, {x=1, y=1}, {x=2, y=1}, {x=1, y=2} },
        { {x=0, y=1}, {x=1, y=1}, {x=1, y=2}, {x=2, y=2} },
        { {x=2, y=0}, {x=1, y=1}, {x=2, y=1}, {x=1, y=2} },
    }
}

Rotation.pieces = 7

-- Component functions.

function Rotation:new(game_mode)
    self.game = require 'game.gamemode'
end

function Rotation:rotatePiece(inputs, piece, grid, prev_inputs, initial, lastdir)
    local new_inputs = {}

    for input, value in pairs(inputs) do
        if value and not prev_inputs[input] then
            new_inputs[input] = true
        end
    end
    local was_drop_blocked = piece:isDropBlocked(grid)

    if self:canPieceRotate(piece, grid) then
--		if not self.held_rotate then 
--			self:attemptRotate(inputs, piece, grid, initial)
--			self.held_rotate = true
--		else
			self:attemptRotate(new_inputs, piece, grid, initial, lastdir)
--		end
    end

    if not initial and not was_drop_blocked and piece:isDropBlocked(grid) then
        PlaySE("bottom")
    end

    -- prev_inputs becomes the previous inputs
    for input, value in pairs(inputs) do
        prev_inputs[input] = inputs[input]
    end
end

function Rotation:attemptRotate(new_inputs, piece, grid, initial, lastdir)
    local rot_dir = 0

    if (new_inputs["rotate_left"] or new_inputs["rotate_left2"]) then
        rot_dir = 3
        if lastdir == 0 then lastdir = -1 end
    elseif (new_inputs["rotate_right"] or new_inputs["rotate_right2"]) then
        rot_dir = 1
        if lastdir == 0 then lastdir = 1 end
	end
    if rot_dir == 0 then return end

    local new_piece = piece:withRelativeRotation(rot_dir)
	self:attemptWallkicks(piece, new_piece, rot_dir, grid, lastdir)
end

function Rotation:attemptWallkicks(piece, new_piece, rot_dir, grid, lastdir)
	-- wallkick routine designed to maximize flexibility while minimizing teleports
	
    -- O doesn't kick
    if (piece.shape == "O") then return end
	
	-- assess precisely what rows/columns would be blocked given the desired rotation
	local sides = {top=false,uright=false,lright=false,uleft=false,lleft=false,center=false,bottom=false}
	local left_exists = false
	local right_exists = false
	local kick = {x=0,y=0}
	for _,offset in pairs(new_piece:getBlockOffsets()) do
		local x = piece.position.x + offset.x
		local y = piece.position.y + offset.y
		if offset.x == 0 then left_exists = true end
		if offset.x == 2 or offset.x == 3 then right_exists = true end
		
		if grid:isOccupied(x,y) then 
			if offset.y == 0 then sides.top = true end
			if offset.y == 3 then sides.bottom = true end
			if offset.y == 1 and offset.x == 0 then sides.uleft = true end
			if offset.y == 2 and offset.x == 0 then sides.lleft = true end
			if offset.y == 1 and (offset.x == 2 or offset.x == 3) then sides.uright = true end
			if offset.y == 2 and (offset.x == 2 or offset.x == 3) then sides.lright = true end
			if offset.x == 1 then sides.center = true end
		end
	end
	
	if sides.top then kick = {x=0,y=1}
	elseif (sides.uleft and sides.lright) or (sides.uright and sides.lleft) or (sides.uright and sides.uleft) then kick = {x=0,y=1}
	elseif (sides.lleft and sides.lright) then kick = {x=0,y=-1}
	elseif (sides.lleft or sides.uleft) then kick = {x=1,y=0}
	elseif (sides.lright or sides.uright) then kick = {x=-1,y=0}
	elseif sides.center and left_exists then kick = {x=1,y=0}
	elseif sides.center and right_exists then kick = {x=-1,y=0}
	elseif sides.bottom then kick = {x=0,y=-1}
	end


	if grid:canPlacePiece(new_piece:withOffset({x=0,y=0})) then
		self:onPieceRotate(piece, grid)
		piece:setRelativeRotation(rot_dir):setOffset({x=0,y=0})
	elseif grid:canPlacePiece(new_piece:withOffset(kick)) then
		self:onPieceRotate(piece, grid)
		piece:setRelativeRotation(rot_dir):setOffset(kick)
	end
end

function Rotation:movePiece(piece, grid, move, instant)
    local was_drop_blocked = piece:isDropBlocked(grid)
    local offset = ({x=0, y=0})
    local moves = 0
    local y = piece.position.y
    if move == "left" then
        offset.x = -1
        moves = 1
    elseif move == "right" then
        offset.x = 1
        moves = 1
    elseif move == "speedleft" then
        offset.x = -1
        moves = grid.width
    elseif move == "speedright" then
        offset.x = 1
        moves = grid.width
    end
    if not self:canPieceMove(piece, grid) then return end
    for i = 1, moves do
        local x = piece.position.x
        if moves ~= 1 then
            piece:moveInGrid(offset, 1, grid, instant)
        else
            piece:moveInGrid(offset, 1, grid, false)
        end
        if piece.position.x ~= x then
            self:onPieceMove(piece, grid)
            if piece.locked then break end
        end
    end
    if not was_drop_blocked and piece:isDropBlocked(grid) then
        PlaySE("bottom")
    end
end

function Rotation:dropPiece(
    inputs, piece, grid, gravity, drop_speed, drop_locked, hard_drop_locked,
    hard_drop_enabled, additive_gravity, classic_lock
)
    local y = piece.position.y
    if inputs["down"] == true and drop_locked == false then
        piece:addGravity(math.max(gravity, drop_speed), grid, classic_lock)
    else
        piece:addGravity(gravity, grid, classic_lock)
    end
    if piece.position.y ~= y then
        self:onPieceDrop(piece, grid)
    end
end

function Rotation:lockPiece(piece, grid, lock_delay, classic_lock)
    if piece:isDropBlocked(grid) and piece.lock_delay >= lock_delay then
        piece.locked = true
    end
end

function Rotation:get180RotationValue() return 3 end
function Rotation:getDefaultOrientation() return 1 end
function Rotation:getDrawOffset(shape, orientation) return { x=0, y=0 } end
function Rotation:getAboveFieldOffset(shape, orientation)
    if shape == "I" then
        return 1
    else
        return 2
    end
end

function Rotation:initializePiece(
    inputs, data, grid, gravity, prev_inputs,
    move, lock_delay, drop_speed,
    drop_locked, hard_drop_locked, big, irs, lastdir
)
    local spawn_positions
    spawn_positions = self.spawn_positions

    local colours
    colours = self.colourscheme
    self.last_dir = 0
    self.held_rotate = false
	if inputs['left'] then self.last_dir = -1
	elseif inputs['right'] then self.last_dir = 1 end
    local spawn_x = math.floor(spawn_positions[data.shape].x / 10 * grid.width)

    local spawn_dy
    spawn_dy = 0

    local piece = Piece(data.shape, data.orientation - 1, {
        x = spawn_x or spawn_positions[data.shape].x,
        y = spawn_positions[data.shape].y - spawn_dy
    }, self.block_offsets, 0, 0, data.skin, colours[data.shape], big)

    self:onPieceCreate(piece)
    if irs then
        self:rotatePiece(inputs, piece, grid, {}, true, lastdir)
    end
    return piece
end

function Rotation:onPieceCreate(piece) end

function Rotation:processPiece(
    inputs, piece, grid, gravity, prev_inputs,
    move, lock_delay, drop_speed,
    drop_locked, hard_drop_locked,
    hard_drop_enabled, additive_gravity, classic_lock, lastdir
)
    self:rotatePiece(inputs, piece, grid, prev_inputs, false, lastdir)
    self:movePiece(piece, grid, move, gravity >= grid.height)
    self:dropPiece(
        inputs, piece, grid, gravity, drop_speed, drop_locked, hard_drop_locked,
        hard_drop_enabled, additive_gravity, classic_lock
    )
    self:lockPiece(piece, grid, lock_delay, classic_lock)
end

function Rotation:canPieceMove(piece, grid) return true end
function Rotation:canPieceRotate(piece, grid) return true end
function Rotation:onPieceMove(piece) end
function Rotation:onPieceRotate(piece) end
function Rotation:onPieceDrop(piece) 
	if piece.position.y > piece.lowest_point then
		piece.lock_delay = 0
		piece.lowest_point = piece.position.y
	end
end

return Rotation
