local Direction = require("lib.direction")
local TurtleController = require("controller.turtle_controller")
local AudioController = require("controller.audio_controller")

---@alias ExcavationControllerConfig {slice_length: number, slice_height: number, center_slice_width: number, slice_width: number, slice_padding: number, slices_left: number, slices_right: number}

---@class ExcavationController
---@field controller TurtleController
---@field audio AudioController
---@field config ExcavationControllerConfig
local ExcavationController = {}
ExcavationController.__index = ExcavationController


---@return ExcavationController
function ExcavationController:Create()
    local t = {}
    setmetatable(t, ExcavationController)


    -----------------------------------------------------------------------------------------------
    -- Fields
    -----------------------------------------------------------------------------------------------


    t.controller = TurtleController:Create()
    t.audio = AudioController:Create()
    t.config = {
        slice_length = 0,
        slice_height = 0,
        center_slice_width = 0,
        slice_width = 0,
        slice_padding = 0,
        slices_left = 0,
        slices_right = 0,
    }


    return t
end


-----------------------------------------------------------------------------------------------
-- Behavior Methods
-----------------------------------------------------------------------------------------------


---Excavates a single 1x1/1x2 tunnel of configured length.
---Will leave the turtle wherever it ends up.
---Only unstocks/refuels if required.
---@param mine_above boolean
---@param mine_below boolean
function ExcavationController:Excavate_1x1or2or3xL(mine_above, mine_below)
    self.controller:EnableMiningForward()
    if mine_above then
        self.controller:EnableMiningAbove()
    end
    if mine_below then
        self.controller:EnableMiningBelow()
    end

    self.controller:MoveForward(self.config.slice_length)

    self.controller:DisableMiningForward()
    self.controller:DisableMiningAbove()
    self.controller:DisableMiningBelow()
end


---Excavates a single 1xH partial slice of configured length.
---Will leave the turtle in its starting position.
---Only unstocks/refuels if required.
function ExcavationController:Excavate_1xHxL()
    self.controller:StorePosition()

    -- We can mine 2 blocks heigh in a single go
    local number_of_1x3xL = math.floor(self.config.slice_height / 3)

    -- Prepare for mining the blocks below the turtle
    if number_of_1x3xL > 0 then
        self.controller:EnableMiningAbove()
        self.controller:MoveVertical(1)
        self.controller:DisableMiningAbove()
    end

    -- Mine 1x3xL
    for i = 1,number_of_1x3xL do
        self:Excavate_1x1or2or3xL(true, true)

        self.controller:TurnRelative(2)

        -- Don't move upwards in the last iteration so we can decide
        -- how much to move depending on how much we still have to excavate
        if i == number_of_1x3xL then
            break
        end

        self.controller:EnableMiningAbove()
        self.controller:MoveVertical(4)
        self.controller:MoveVertical(-1)
        self.controller:DisableMiningAbove()
    end

    -- Mine remaining 1x2xL or 1x1xL
    if self.config.slice_height % 3 == 2 then
        self.controller:EnableMiningAbove()
        self.controller:MoveVertical(3)
        self.controller:MoveVertical(-1)
        self.controller:DisableMiningAbove()

        self:Excavate_1x1or2or3xL(true, false)
    elseif self.config.slice_height % 3 == 1 then
        self.controller:EnableMiningAbove()
        self.controller:MoveVertical(3)
        self.controller:MoveVertical(-1)
        self.controller:DisableMiningAbove()

        self:Excavate_1x1or2or3xL(false, false)
    end

    self.controller:EnableMiningBelow()
    self.controller:MoveBack()
    self.controller:DisableMiningBelow()
end


---Excavates a single WxH slice of configured length (EAST to WEST).
---Will leave the turtle in its starting position.
---Only unstocks/refuels if required.
---@param center_slice boolean | nil
function ExcavationController:Excavate_WxHxL(center_slice)
    self.controller:StorePosition()

    center_slice = center_slice or false
    local width = center_slice and self.config.center_slice_width or self.config.slice_width

    for i = 1,width do
        self:Excavate_1xHxL()

        if i == width then
            break
        end

        self.controller:TurnToDirection(Direction.WEST)
        self.controller:EnableMiningForward()
        self.controller:EnableMiningAbove()
        self.controller:MoveForward(1)
        self.controller:DisableMiningForward()
        self.controller:DisableMiningAbove()
        self.controller:TurnToDirection(Direction.NORTH)
    end

    self.controller:MoveBack()
end


---Excavates all slices.
---Will leave the turtle refueled and unstocked in its 0x0x0 position.
function ExcavationController:Excavate()
    -- Enter excavation area
    self.controller:EnableMiningForward()
    self.controller:EnableMiningAbove()
    self.controller:MoveForward(1)
    self.controller:DisableMiningForward()
    self.controller:DisableMiningAbove()

    -- Excavate center slice (start from right/back/bottom)
    -- Uneven center widths will be centered, even center widths will have a block more on the right
    print("Excavating center slice...")
    local center_width_right = math.floor(self.config.center_slice_width / 2)
    self.controller:StorePosition()
    self.controller:TurnToDirection(Direction.EAST)
    self.controller:EnableMiningForward()
    self.controller:EnableMiningAbove()
    self.controller:MoveForward(center_width_right)
    self.controller:DisableMiningForward()
    self.controller:DisableMiningAbove()
    self.controller:TurnToDirection(Direction.NORTH)
    self:Excavate_WxHxL(true)
    self.controller:MoveBack() -- (0, 0, 1)

    if self.config.slices_right > 0 then
        -- Move to right slices starting location
        local padded_width_right = self.config.slices_right * (self.config.slice_width + self.config.slice_padding)
        self.controller:StorePosition()
        self.controller:TurnToDirection(Direction.EAST)
        self.controller:EnableMiningForward()
        self.controller:EnableMiningAbove()
        self.controller:MoveForward(center_width_right + padded_width_right)
        self.controller:DisableMiningForward()
        self.controller:DisableMiningAbove()
        self.controller:TurnToDirection(Direction.NORTH)

        -- Excavate right slices
        print("Excavating right slices")
        for i = 1,self.config.slices_right do
            self:Excavate_WxHxL()

            if i == self.config.slices_right then
                break
            end

            -- Move to the next slice
            self.controller:TurnToDirection(Direction.WEST)
            self.controller:EnableMiningForward()
            self.controller:EnableMiningAbove()
            self.controller:MoveForward(self.config.slice_width + self.config.slice_padding)
            self.controller:DisableMiningForward()
            self.controller:DisableMiningAbove()
            self.controller:TurnToDirection(Direction.NORTH)
        end
        self.controller:MoveBack() -- (0, 0, 1)
    end

    if self.config.slices_left > 0 then
        -- Move to left slices starting location
        local center_width_left = self.config.center_slice_width - center_width_right
        self.controller:StorePosition()
        self.controller:TurnToDirection(Direction.WEST)
        self.controller:EnableMiningForward()
        self.controller:EnableMiningAbove()
        self.controller:MoveForward(center_width_left)
        self.controller:DisableMiningForward()
        self.controller:DisableMiningAbove()
        self.controller:TurnToDirection(Direction.NORTH)

        -- Excavate left slices
        print("Excavate left slices")
        for i = 1,self.config.slices_left do
            -- Move to the next slice
            self.controller:TurnToDirection(Direction.WEST)
            self.controller:EnableMiningForward()
            self.controller:EnableMiningAbove()
            self.controller:MoveForward(self.config.slice_padding)
            self.controller:DisableMiningForward()
            self.controller:DisableMiningAbove()
            self.controller:TurnToDirection(Direction.NORTH)

            self:Excavate_WxHxL()

            if i == self.config.slices_left then
                break
            end

            self.controller:TurnToDirection(Direction.WEST)
            self.controller:EnableMiningForward()
            self.controller:EnableMiningAbove()
            self.controller:MoveForward(self.config.slice_width)
            self.controller:DisableMiningForward()
            self.controller:DisableMiningAbove()
            self.controller:TurnToDirection(Direction.NORTH)
        end
        self.controller:MoveBack() -- (0, 0, 1)
    end

    -- Finish up
    self.controller:MoveToPosition(0, 0, 0, self.controller.config.storage_direction)
    self.controller:DropInventory()
    self.controller:TurnToDirection(Direction.NORTH)
    self.audio:StopPlaying()
end


-----------------------------------------------------------------------------------------------
-- Management Methods
-----------------------------------------------------------------------------------------------


function ExcavationController:Configure()
    local config_complete = false

    while not config_complete do
        print("How long should each slice be?")
        self.config.slice_length = tonumber(io.read()) or 3

        print("How high should each slice be?")
        self.config.slice_height = tonumber(io.read()) or 3

        print("How wide should the center slice be?")
        self.config.center_slice_width = tonumber(io.read()) or 1

        print("How wide should the other slices be?")
        self.config.slice_width = tonumber(io.read()) or 1

        print("How many blocks should remain between each slice?")
        self.config.slice_padding = tonumber(io.read()) or 0

        print("How many slices should the turtle mine to its right?")
        self.config.slices_right = tonumber(io.read()) or 1

        print("How many slices should the turtle mine to its left?")
        self.config.slices_left = tonumber(io.read()) or 1

        local padded_width_left = self.config.slices_left * (self.config.slice_width + self.config.slice_padding)
        local padded_width_right = self.config.slices_right * (self.config.slice_width + self.config.slice_padding)
        local padded_width = padded_width_left + padded_width_right + self.config.center_slice_width
        local mined_width_left = self.config.slices_left * self.config.slice_width
        local mined_width_right = self.config.slices_right * self.config.slice_width
        local mined_width = mined_width_left + mined_width_right + self.config.center_slice_width

        print("Configuration complete!")
        print(("Mining area spans %d x %d x %d (width x height x forward), totalling %d blocks."):format(
            padded_width, self.config.slice_height, self.config.slice_length, padded_width * self.config.slice_height * self.config.slice_length
        ))
        print(("Turtle will mine %d block wide slices (%d wide in the center) with %d blocks of padding, totalling %d mined blocks."):format(
            self.config.slice_width, self.config.center_slice_width, self.config.slice_padding, mined_width * self.config.slice_height * self.config.slice_length
        ))
        print("Do you want to accept the configuration (enter 1, otherwise 0)?")
        config_complete = tonumber(io.read()) == 1
    end
end


-----------------------------------------------------------------------------------------------
-- Main Method
-----------------------------------------------------------------------------------------------


function ExcavationController:Run()
    self.controller:Configure()
    self:Configure()

    -- Consume our starting fuel and refuel to the full amount
    turtle.select(1)
    turtle.refuel()
    self.controller:RefuelIfEmpty()

    parallel.waitForAll(function() self:Excavate() end, self.audio:PlayAudioFactory("bangarang"))
end


return ExcavationController