--luacheck: ignore
--tetris by mewmew

--18x10 gb

local event = require 'utils.event'
local bricks = require 'maps.tetris.bricks'
local connect_belts = require 'utils.functions.connect_belts'

local playfield_left_top = { x = -17, y = -18 }
local playfield_width = 12
local playfield_height = 24

local playfield_area = {
    ['left_top'] = { x = playfield_left_top.x, y = playfield_left_top.y },
    ['right_bottom'] = { x = playfield_left_top.x + playfield_width, y = playfield_left_top.y + playfield_height }
}
local playfield_width = math.abs(playfield_area.left_top.x - playfield_area.right_bottom.x)
local playfield_height = math.abs(playfield_area.left_top.y - playfield_area.right_bottom.y)

local move_translations = {
    ['iron-plate'] = { -1, 0 },
    ['copper-plate'] = { 1, 0 }
}

local function coord_string(x, y)
    str = tostring(x) .. '_'
    str = str .. tostring(y)
    return str
end

local function draw_active_bricks(surface)
    if not storage.active_brick then
        return
    end
    if not storage.active_brick.entities then
        storage.active_brick.entities = {}
    end
    for k, e in pairs(storage.active_brick.entities) do
        if e.valid then
            storage.tetris_active_grid[coord_string(math.floor(e.position.x), math.floor(e.position.y))] = false
        end
        storage.active_brick.entities[k] = nil
        e.destroy()
    end

    for _, p in pairs(storage.active_brick.positions) do
        local e = surface.create_entity({ name = storage.active_brick.entity_name, position = p, create_build_effect_smoke = false })
        --if bricks[storage.active_brick.type].fluid then
        --	e.fluidbox[1] = {name = bricks[storage.active_brick.type].fluid, amount = 100}
        --end
        storage.tetris_active_grid[coord_string(math.floor(e.position.x), math.floor(e.position.y))] = true
        storage.active_brick.entities[#storage.active_brick.entities + 1] = e
    end

    if storage.active_brick.entity_type == 'transport-belt' then
        connect_belts(storage.active_brick.entities)
    end
end

local function set_collision_grid(surface)
    for x = 0, playfield_width - 1, 1 do
        for y = 0, playfield_height - 1, 1 do
            local position = { x = playfield_area.left_top.x + x, y = playfield_area.left_top.y + y }
            storage.tetris_grid[coord_string(math.floor(position.x), math.floor(position.y))] = true
        end
    end
    local entities = surface.find_entities_filtered({ area = playfield_area, force = 'enemy' })
    for _, e in pairs(entities) do
        if not storage.tetris_active_grid[coord_string(math.floor(e.position.x), math.floor(e.position.y))] then
            storage.tetris_grid[coord_string(math.floor(e.position.x), math.floor(e.position.y))] = false
        else
            --game.print(e.position)
        end
    end
end

local function rotate_brick(surface)
    if not storage.active_brick then
        return
    end
    local center_pos = storage.active_brick.positions[1]
    local vectors = bricks[storage.active_brick.type].vectors
    local new_direction = storage.active_brick.direction + 1
    if new_direction > 4 then
        new_direction = 1
    end
    local new_vectors = vectors[new_direction]
    for k, p in pairs(storage.active_brick.positions) do
        if not storage.tetris_grid[coord_string(math.floor(center_pos.x + new_vectors[k][1]), math.floor(center_pos.y + new_vectors[k][2]))] then
            return
        end
    end
    for k, p in pairs(storage.active_brick.positions) do
        storage.active_brick.positions[k] = { x = center_pos.x + new_vectors[k][1], y = center_pos.y + new_vectors[k][2] }
    end
    storage.active_brick.direction = new_direction
end

local function set_hotbar()
    for _, player in pairs(game.connected_players) do
        player.set_quick_bar_slot(1, 'iron-plate')
        player.set_quick_bar_slot(2, 'copper-plate')
        player.set_quick_bar_slot(9, 'iron-gear-wheel')
        player.set_quick_bar_slot(10, 'processing-unit')
    end
end

local function set_inventory()
    for _, player in pairs(game.connected_players) do
        for _, item in pairs({ 'iron-plate', 'copper-plate', 'iron-gear-wheel', 'processing-unit' }) do
            if player.get_main_inventory().get_item_count(item) == 0 then
                player.insert({ name = item, count = 1 })
            end
        end
    end
end

local function draw_playfield(surface)
    for x = -1, playfield_width, 1 do
        for y = -1, playfield_height, 1 do
            local position = { x = playfield_area.left_top.x + x, y = playfield_area.left_top.y + y }
            --surface.set_tiles({{name = "deepwater", position = position}})
        end
    end
    for x = 0, playfield_width - 1, 1 do
        for y = 0, playfield_height - 1, 1 do
            local position = { x = playfield_area.left_top.x + x, y = playfield_area.left_top.y + y }
            storage.tetris_grid[coord_string(math.floor(position.x), math.floor(position.y))] = true
            surface.set_tiles({ { name = 'tutorial-grid', position = position } })
        end
    end
end

local function draw_score(surface)
    local score = 'Score: ' .. storage.score
    local high_score = 'Highscore: ' .. storage.high_score
    local level = 'Level: ' .. storage.level
    local cleared_lines = 'Lines: ' .. storage.cleared_lines

    if not storage.tetris_score_rendering then
        storage.tetris_score_rendering = {}
        rendering.draw_text {
            text = '#######################',
            surface = surface,
            target = { playfield_area.right_bottom.x + 1, playfield_area.left_top.y + 0.5 },
            color = { r = 0.2, g = 0.0, b = 0.5 },
            scale = 1,
            font = 'heading-1',
            --alignment = "center",
            scale_with_zoom = false
        }
        storage.tetris_score_rendering.score =
            rendering.draw_text {
                text = score,
                surface = surface,
                target = { playfield_area.right_bottom.x + 1, playfield_area.left_top.y + 1 },
                color = { r = 0.98, g = 0.66, b = 0.22 },
                scale = 2,
                font = 'heading-1',
                --alignment = "center",
                scale_with_zoom = false
            }
        rendering.draw_text {
            text = '#######################',
            surface = surface,
            target = { playfield_area.right_bottom.x + 1, playfield_area.left_top.y + 2.5 },
            color = { r = 0.2, g = 0.0, b = 0.5 },
            scale = 1,
            font = 'heading-1',
            --alignment = "center",
            scale_with_zoom = false
        }
        storage.tetris_score_rendering.high_score =
            rendering.draw_text {
                text = high_score,
                surface = surface,
                target = { playfield_area.right_bottom.x + 1, playfield_area.left_top.y + 3.3 },
                color = { r = 0.98, g = 0.66, b = 0.22 },
                scale = 1.15,
                font = 'heading-1',
                --alignment = "center",
                scale_with_zoom = false
            }
        storage.tetris_score_rendering.level =
            rendering.draw_text {
                text = level,
                surface = surface,
                target = { playfield_area.right_bottom.x + 1, playfield_area.left_top.y + 4.3 },
                color = { r = 0.98, g = 0.66, b = 0.22 },
                scale = 1.15,
                font = 'heading-1',
                --alignment = "center",
                scale_with_zoom = false
            }
        storage.tetris_score_rendering.cleared_lines =
            rendering.draw_text {
                text = cleared_lines,
                surface = surface,
                target = { playfield_area.right_bottom.x + 1, playfield_area.left_top.y + 5.3 },
                color = { r = 0.98, g = 0.66, b = 0.22 },
                scale = 1.15,
                font = 'heading-1',
                --alignment = "center",
                scale_with_zoom = false
            }
    end
    rendering.set_text(storage.tetris_score_rendering.score, score)
    rendering.set_text(storage.tetris_score_rendering.high_score, high_score)
    rendering.set_text(storage.tetris_score_rendering.level, level)
    rendering.set_text(storage.tetris_score_rendering.cleared_lines, cleared_lines)
end

local function add_score_points(amount)
    storage.score = storage.score + amount
    if storage.score > storage.high_score then
        storage.high_score = storage.score
    end
end

local function move_lines_down(surface, y)
    local entities = surface.find_entities_filtered({ area = { { playfield_area.left_top.x, playfield_area.left_top.y }, { playfield_area.left_top.x + playfield_width + 1, playfield_area.left_top.y + y + 1 } }, force = 'enemy' })
    for _, e in pairs(entities) do
        if e.valid then
            e.clone { position = { e.position.x, e.position.y + 1 }, surface = surface, force = 'enemy' }
            e.destroy()
        end
    end
end

local function tetris(surface)
    local c = 0
    local entity_lines_to_kill = {}
    for y = 0, playfield_height, 1 do
        local entities =
            surface.find_entities_filtered(
                {
                    area = { { playfield_area.left_top.x, playfield_area.left_top.y + y }, { playfield_area.left_top.x + playfield_width + 1, playfield_area.left_top.y + y + 1 } },
                    force = 'enemy'
                }
            )
        if #entities == playfield_width then
            entity_lines_to_kill[#entity_lines_to_kill + 1] = { entities = entities, y = y }
            c = c + 1
        end
    end
    if c < 1 then
        return
    end

    storage.cleared_lines = storage.cleared_lines + c

    local name = 'explosion'
    if c > 2 then
        name = 'uranium-cannon-shell-explosion'
    end
    if c >= 4 then
        name = 'ground-explosion'
    end

    local score_y = false

    for _, line in pairs(entity_lines_to_kill) do
        for _, entity in pairs(line.entities) do
            if math.random(1, 2) == 1 then
                surface.create_entity({ name = name, position = entity.position, force = 'neutral' })
            end
            entity.destroy()
        end
        move_lines_down(surface, line.y)
        if not score_y then
            score_y = line.y
        end
    end
    set_collision_grid(surface)

    local score_gain = (c * 100 * c)
    add_score_points(score_gain)

    rendering.draw_text {
        text = '+' .. score_gain,
        surface = surface,
        target = { playfield_area.left_top.x + playfield_width * 0.5, playfield_area.left_top.y + score_y },
        color = { r = 0.22, g = 0.77, b = 0.22 },
        scale = c,
        font = 'heading-1',
        time_to_live = 180,
        alignment = 'center',
        scale_with_zoom = false
    }
end

local function reset_score()
    storage.level = 0
    storage.cleared_lines = 0
    storage.score = 0
end

local function reset_play_area(surface)
    local entities = surface.find_entities_filtered({ area = playfield_area, force = 'enemy' })
    for _, e in pairs(entities) do
        if math.random(1, 6) == 1 then
            e.surface.create_entity({ name = 'big-artillery-explosion', position = e.position, force = 'neutral' })
        end
        e.destroy()
    end
    set_collision_grid(surface)
    storage.last_reset = game.tick + 300
end

local function set_difficulty()
    storage.move_down_delay = game.tick + (64 - (storage.level * 2))
    local level = math.floor(storage.cleared_lines * 0.15) + 1
    if storage.level == level then
        return
    end
    storage.level = level
end

local function new_brick(surface)
    if storage.active_brick then
        return
    end

    local r = math.random(1, 7)
    if math.random(1, 16) == 1 then
        r = math.random(8, #bricks)
    end
    --local r = 12
    local brick = bricks[r]

    local x_modifier = -1 + math.random(0, 2)
    local spawn_position = { x = playfield_area.left_top.x + playfield_width * 0.5 + x_modifier, y = playfield_area.left_top.y + brick.spawn_y_modifier - 1 }

    if not storage.tetris_grid[coord_string(math.floor(spawn_position.x), math.floor(spawn_position.y))] then
        reset_play_area(surface)
        reset_score()
        set_difficulty()
        draw_score(surface)
    end

    if game.tick < storage.last_reset then
        rendering.draw_text {
            text = 'Round begins in.. ' .. math.abs(math.floor((game.tick - storage.last_reset) / 60)),
            surface = surface,
            target = { playfield_area.left_top.x + playfield_width * 0.5, playfield_area.left_top.y + playfield_height * 0.5 },
            color = { r = 0.98, g = 0.66, b = 0.22 },
            scale = 3,
            font = 'heading-1',
            time_to_live = 60,
            alignment = 'center',
            scale_with_zoom = false
        }
        return
    end

    storage.active_brick = {}
    storage.active_brick.direction = 1
    storage.active_brick.type = r
    storage.active_brick.positions = {}
    storage.active_brick.entity_name = brick.entity_name
    storage.active_brick.entity_type = brick.entity_type

    for k, v in pairs(brick.vectors[1]) do
        storage.active_brick.positions[k] = { x = spawn_position.x + v[1], y = spawn_position.y + v[2] }
    end
end

local function move_down(surface)
    if not storage.active_brick then
        return
    end
    if storage.move_down_delay > game.tick then
        return
    end
    for k, p in pairs(storage.active_brick.positions) do
        if not storage.tetris_grid[coord_string(math.floor(storage.active_brick.positions[k].x), math.floor(storage.active_brick.positions[k].y + 1))] then
            for k, p in pairs(storage.active_brick.positions) do
                storage.tetris_grid[coord_string(math.floor(p.x), math.floor(p.y))] = false
                storage.tetris_active_grid[coord_string(math.floor(p.x), math.floor(p.y))] = false
            end
            storage.active_brick = nil
            tetris(surface)
            add_score_points(5)
            draw_score(surface)
            new_brick(surface)
            return
        end
    end
    for k, p in pairs(storage.active_brick.positions) do
        storage.active_brick.positions[k] = { x = storage.active_brick.positions[k].x, y = storage.active_brick.positions[k].y + 1 }
    end
    set_difficulty()
    return true
end

local function move(surface, item)
    if not storage.active_brick then
        return
    end
    if item == 'iron-gear-wheel' then
        rotate_brick(surface)
        return
    end
    if item == 'processing-unit' then
        for c = 1, 4, 1 do
            storage.move_down_delay = 0
            local b = move_down(surface)
            draw_active_bricks(surface)
            if not b then
                return
            end
        end
    end
    if not move_translations[item] then
        return
    end
    for k, p in pairs(storage.active_brick.positions) do
        if not storage.tetris_grid[coord_string(math.floor(storage.active_brick.positions[k].x + move_translations[item][1]), math.floor(storage.active_brick.positions[k].y + move_translations[item][2]))] then
            return
        end
    end
    for k, p in pairs(storage.active_brick.positions) do
        storage.active_brick.positions[k] = {
            x = storage.active_brick.positions[k].x + move_translations[item][1],
            y = storage.active_brick.positions[k].y + move_translations[item][2]
        }
    end
end

local function on_player_cursor_stack_changed(event)
    local player = game.players[event.player_index]
    --game.print(game.tick)
    --	game.print(player.cursor_stack)
    if not player.cursor_stack then
        return
    end
    if not player.cursor_stack.valid_for_read then
        return
    end
    if not player.cursor_stack.name then
        return
    end
    local item = player.cursor_stack.name
    move(player.surface, item)
    player.cursor_stack.clear()
    player.surface.spill_item_stack(player.position, { name = item, count = 1 }, true)

    if item == 'iron-plate' then
        player.surface.create_entity({ name = 'flying-text', position = player.position, text = '<', color = player.color })
    end
    if item == 'copper-plate' then
        player.surface.create_entity({ name = 'flying-text', position = player.position, text = '>', color = player.color })
    end
    if item == 'processing-unit' then
        player.surface.create_entity({ name = 'flying-text', position = player.position, text = '▼', color = player.color })
    end
    if item == 'iron-gear-wheel' then
        player.surface.create_entity({ name = 'flying-text', position = player.position, text = '8', color = player.color })
    end
end

local function on_player_joined_game(event)
    local player = game.players[event.player_index]
    set_hotbar()
    set_inventory()
    player.surface.daytime = 0.22
    player.surface.freeze_daytime = 1
    player.print('Use 1,2, 9, 0 on your keyboard to control the bricks.', { r = 0.98, g = 0.66, b = 0.22 })
end

local function on_chunk_generated(event)
    local surface = event.surface
    for _, e in pairs(surface.find_entities_filtered({ area = event.area, force = { 'neutral', 'enemy' } })) do
        e.destroy()
    end
    for _, t in pairs(surface.find_tiles_filtered({ area = event.area })) do
        --surface.set_tiles({{name = "tutorial-grid", position = t.position}})
        if t.position.y < 4 and t.position.y > -4 and t.position.x < 4 and t.position.x > -4 then
            surface.set_tiles({ { name = 'sand-1', position = t.position } })
        else
            surface.set_tiles({ { name = 'out-of-map', position = t.position } })
        end
    end
    surface.destroy_decoratives { area = event.area }
    if event.area.left_top.x == 128 and event.area.left_top.y == 128 then
        draw_playfield(surface)
    end
end

local function on_init(event)
    storage.tetris_grid = {}
    storage.tetris_active_grid = {}
    storage.high_score = 0
    storage.last_reset = 120
    storage.cleared_lines = 0
    storage.level = 0
end

local function tick()
    local surface = game.surfaces[1]
    if game.tick % 4 == 0 then
        draw_active_bricks(surface)
        move_down(surface)
    end
    if game.tick % 60 == 0 then
        new_brick(surface)
    end
end

event.on_nth_tick(4, tick)
event.on_init(on_init)
event.add(defines.events.on_player_cursor_stack_changed, on_player_cursor_stack_changed)
event.add(defines.events.on_player_joined_game, on_player_joined_game)
event.add(defines.events.on_chunk_generated, on_chunk_generated)
