xRelPos = 0 -- "positive = forward"
yRelPos = 0 -- "positive = right"
zRelPos = 0 -- "positive = upwards"

facing = {
    x = 1,
    y = 0
}

-- slots for all the items needed
slot_bucket1 = 1
slot_bucket2 = 2
slot_lff = 3        -- 18 pieces + 1 for resupply
slot_lpb1 = 4       -- 1 stack
slot_lpb2 = 5       -- 10 pieces -> 64 + 10 = 74, 72 + 2 extra for 1 in each slot for resupply
slot_liquiduct = 6  -- 12 pieces + 1 for resupply
slot_aq_acc = 7
slot_steam_cons = 8
slot_energy_bridge = 9
slot_hv_prod = 10
slot_mfsu = 11
slot_glass_fiber = 12
slot_trash = 16

num_lff = 19
num_lpb1 = 64
num_lpb2 = 10
num_liquiduct = 13

num_aq_acc = 2
num_steam_cons = 2
num_energy_bridge = 2
num_hv_prod = 2
num_mfsu = 1
num_glass_fiber = 4

function printPos()
    print("At (", xRelPos, ", ", yRelPos, ", ", zRelPos, ") facing (", facing.x, ", ", facing.y, ")")
end

-- helper functions for movement
function forward()
    xRelPos = xRelPos + facing.x
    yRelPos = yRelPos + facing.y

    if turtle.forward() then
        return true
    end
    
    xRelPos = xRelPos - facing.x
    yRelPos = yRelPos - facing.y

    turtle.dig()
    turtle.attack()
    return false
end

function forceForward()
    -- try moving forward until it works
    while not forward() do end
end

function upwards()
    zRelPos = zRelPos + 1
    if turtle.up() then
        return true
    end
    zRelPos = zRelPos - 1
    turtle.digUp()
    turtle.attackUp()
    return false
end

function forceUpwards()
    -- try moving upwards until it works
    while not upwards() do end
end

function downwards()
    zRelPos = zRelPos - 1
    if turtle.down() then
        return true
    end
    zRelPos = zRelPos + 1
    turtle.digDown()
    turtle.attackDown()
    return false
end

function forceDownwards()
    -- try moving upwards until it works
    while not downwards() do end
end

function forcePlace()
    -- try placing until it works
    while not turtle.place() do 
        turtle.dig()
        turtle.attack()
    end
end

function forcePlaceDown()
    -- try placing until it works
    while not turtle.placeDown() do 
        turtle.digDown()
        turtle.attackDown()
    end
end

function forcePlaceUp()
    -- try placing until it works
    while not turtle.placeUp() do 
        turtle.digUp()
        turtle.attackUp()
    end
end

function turnRight()
    if facing.x == 1 and facing.y == 0 then -- facing forward
        facing.x = 0
        facing.y = 1
    elseif facing.x == 0 and facing.y == 1 then -- facing right
        facing.x = -1
        facing.y = 0
    elseif facing.x == -1 and facing.y == 0 then -- facing backward
        facing.x = 0
        facing.y = -1
    elseif facing.x == 0 and facing.y == -1 then -- facing left
        facing.x = 1
        facing.y = 0
    else 
        print("turnRight: This should never happen")
    end
    turtle.turnRight()
end

function turnLeft()
    if facing.x == 1 and facing.y == 0 then -- facing forward
        facing.x = 0
        facing.y = -1
    elseif facing.x == 0 and facing.y == 1 then -- facing right
        facing.x = 1
        facing.y = 0
    elseif facing.x == -1 and facing.y == 0 then -- facing backward
        facing.x = 0
        facing.y = 1
    elseif facing.x == 0 and facing.y == -1 then -- facing left
        facing.x = -1
        facing.y = 0
    else 
        print("turnLeft: This should never happen")
    end
    turtle.turnLeft()
end

function faceTo(x, y)
    if (x == facing.x) and (y == facing.y) then
        return
    end
    if (x == 1 and facing.x == -1) or (x == -1 and facing.x == 1) or (y == 1 and facing.y == -1) or (y == -1 and facing.y == 1) then
        turnRight()
        turnRight()
    elseif (facing.y == -1 and x == 1) or (facing.y == 1 and x == -1) or (facing.x == 1 and y == 1) or (facing.x == -1 and y == -1) then
        turnRight()
    elseif (facing.y == -1 and x == -1) or (facing.y == 1 and x == 1) or (facing.x == 1 and y == -1) or (facing.x == -1 and y == 1) then
        turnLeft()
    else
        print("faceTo: This should never happen")
    end
end

function moveTo(x, y)
    if (yRelPos > y) then
        faceTo(0, -1)
        while yRelPos > y do
            forward()
        end
    elseif (yRelPos < y) then
        faceTo(0, 1)
        while yRelPos < y do
            forward()
        end
    end

    if (xRelPos > x) then
        faceTo(-1, 0)
        while xRelPos > x do
            forward()
        end
    elseif (xRelPos < x) then
        faceTo(1, 0)
        while xRelPos < x do
            forward()
        end
    end
end

-- ask user to place <number> items with name <name> in slot <slot>
function initSlot(name, slot, number)
    turtle.select(slot)
    print("Please put ", number, " ", name, " in the selected slot.")
    while turtle.getItemCount(slot) < number do
        sleep(1)
    end
    print("Got all required ", name)
end

function initSlots(num)
    -- setup building slots
    initSlot("Water Bucket", slot_bucket1, 1)
    initSlot("Water Bucket", slot_bucket2, 1)
    initSlot("Liquid Fueled Firebox", slot_lff, 1)
    initSlot("Low Pressure Boiler", slot_lpb1, 1)
    initSlot("Low Pressure Boiler", slot_lpb2, 1)
    initSlot("Liquiduct", slot_liquiduct, 1)
    initSlot("Aqueous Accumulators", slot_aq_acc, num_aq_acc * num)
    initSlot("Steam Consumers", slot_steam_cons, num_steam_cons * num)
    initSlot("Energy Bridges", slot_energy_bridge, num_energy_bridge * num)
    initSlot("HV Producers", slot_hv_prod, num_hv_prod * num)
    initSlot("MFSUs", slot_mfsu, num_mfsu * num)
    initSlot("Glass Fiber", slot_glass_fiber, num_glass_fiber * num)
end

function placeLeftRight(slot)
    turtle.select(slot)
    -- figure out whether to place left or right first depending on current rotation
    if (facing.y ~= 0) then
        -- if already facing left or right, place, turn twice, place is enough
    else
        turnRight()
    end

    forcePlace()
    turnRight(); turnRight()
    forcePlace()
    -- don't care about end rotation
end

function placeDown(slot)
    -- doesn't care about turtle rotation at all
    turtle.select(slot_trash)
    turtle.digDown()
    turtle.select(slot)
    forcePlaceDown()
end

function toNextLayer()
    forceUpwards()
end

function placeLayer(slots_down, slots_left_right, forwardX, forwardY)
    localFacingX = forwardX
    localFacingY = forwardY

    for i=1, 7 do
        if i ~= 1 then
            -- placeLeftRight does not necessarily rotate back
            faceTo(localFacingX, localFacingY)
            forceForward()
        end

        if (slots_down[i] ~= nil) then
            placeDown(slots_down[i])
        end
        if (slots_left_right[i] ~= nil) then
            placeLeftRight(slots_left_right[i])
        end
    end
end

function placeLowestLayer(forwardX, forwardY)
    localFacingX = forwardX
    localFacingY = forwardY

    for i = 1, 2 do
        placeDown(slot_bucket1)
        placeLeftRight(slot_lff)
        
        faceTo(localFacingX, localFacingY)
        forceForward()
        turtle.select(slot_trash)
        turtle.digDown()

        forceForward()
        placeDown(slot_bucket2)
        placeLeftRight(slot_lff)

        faceTo(-localFacingX, -localFacingY)
        forceForward()

        turtle.select(slot_bucket1)
        forcePlaceDown()
        placeLeftRight(slot_lff)
        turtle.select(slot_bucket2)
        forcePlaceDown()
        placeDown(slot_aq_acc)
        faceTo(localFacingX, localFacingY)
        forceForward()
        if i == 1 then 
            forceForward()
            placeLeftRight(slot_liquiduct)
            faceTo(localFacingX, localFacingY)
            forceForward()
        end
    end
end

function buildUnit()
    -- build a single setup consisting of 2 boilers
    -- the initial positioning is like this, where b is the boiler, l is the liquiducts/glass fiber on top, and ^ is the turtle, looking north
    --  bbb
    --  bbb
    --  bbb
    --  llll
    --  bbb
    --  bbb
    --  bbb
    --   ^

    -- keep track of correct facing
    localFacingX = facing.x
    localFacingY = facing.y

    forceForward()

    -- turtle layer 1
    placeLowestLayer(localFacingX, localFacingY)
    localFacingX = -1 * localFacingX; localFacingY = -1 * localFacingY
    toNextLayer()

    -- turtle layer 2
    placeLayer(
        {slot_lff, slot_lff, slot_lff, slot_liquiduct, slot_lff, slot_lff, slot_lff},
        {slot_lpb2, slot_lpb2, slot_lpb2, nil, slot_lpb2, slot_lpb1, slot_lpb1},
        localFacingX, localFacingY
    )
    localFacingX = -1 * localFacingX; localFacingY = -1 * localFacingY
    toNextLayer()

    -- turtle layers 3-5
    for i = 3, 5 do
        placeLayer(
            {slot_lpb1, slot_lpb1, slot_lpb1, nil, slot_lpb1, slot_lpb1, slot_lpb1},
            {slot_lpb1, slot_lpb1, slot_lpb1, nil, slot_lpb1, slot_lpb1, slot_lpb1},
            localFacingX, localFacingY
        )
        localFacingX = -1 * localFacingX; localFacingY = -1 * localFacingY
        toNextLayer()
    end

    -- turtle layer 6
    placeLayer(
        {slot_lpb2, slot_lpb1, slot_lpb1, nil, slot_lpb1, slot_lpb1, slot_lpb1},
        {nil, slot_liquiduct, nil, slot_glass_fiber, nil, slot_liquiduct},
        localFacingX, localFacingY
    )
    localFacingX = -1 * localFacingX; localFacingY = -1 * localFacingY
    toNextLayer()

    -- turtle layer 7
    placeLayer(
        {slot_liquiduct, slot_steam_cons, slot_liquiduct, nil, slot_liquiduct, slot_steam_cons, slot_liquiduct},
        {nil, nil, nil, nil, nil, nil, nil},
        localFacingX, localFacingY
    )
    localFacingX = -1 * localFacingX; localFacingY = -1 * localFacingY
    toNextLayer()

    -- turtle layer 8
    placeLayer(
        {nil, slot_energy_bridge, slot_hv_prod, slot_mfsu, slot_hv_prod, slot_energy_bridge, nil},
        {nil, nil, nil, nil, nil, nil, nil},
        localFacingX, localFacingY
    )
    localFacingX = -1 * localFacingX; localFacingY = -1 * localFacingY

    faceTo(localFacingX, localFacingY)

    -- end
    forceForward(); forceForward(); forceForward()
    turnRight();
    forceForward()
    turnRight(); turnRight()
    forceDownwards()
    turtle.select(slot_glass_fiber)
    turtle.digDown()
    forceDownwards()
    forcePlace()
    turnRight(); turnRight(); forceForward(); turnRight(); turnRight()
    forcePlace() -- place mined glass fiber back
    forceDownwards()
    forcePlaceUp()

    forceDownwards(); forceDownwards(); forceDownwards()
    turtle.select(slot_liquiduct)
    forcePlaceDown()
end

function throwTrash()
    turtle.select(slot_trash)
    turtle.drop()
end

function resupplyItem(name, slot, min_num)
    module = peripheral.wrap("right")
    module.link()

    if (turtle.getItemCount(slot) < min_num) then
        module.resupply(slot)
    end

    if (turtle.getItemCount(slot) >= min_num) then
        return
    end

    -- start complaining
    print("Insufficient ", name, " in resupply station, waiting...")

    while (turtle.getItemCount(slot) < min_num) do
        sleep(1)
        module.resupply(slot)
    end

    print("Got Items, continuing...")
end

function resupplyAll()
    -- expected position of the resupply station
    moveTo(0, 0)
    faceTo(-1, 0)

    resupplyItem("Liquid Fueled Fireboxes", slot_lff, num_lff)
    resupplyItem("Low Pressure Boilers", slot_lpb1, num_lpb1)
    resupplyItem("Low Pressure Boilers", slot_lpb2, num_lpb2)
    resupplyItem("Liquiducts", slot_liquiduct, num_liquiduct)
end

print("-- This program can not restart after the turtle was unloaded  --")
print("-- Keep the turtle loaded the entire time while it is building --")
print("")
print("Boilers will be built infront of the turtle, stretching to the right.")
print("Please place a resupply station with liquiducts, liquid fueled fireboxes and low pressure boilers behind the turtle.")

num_boilers = 0
while num_boilers < 1 or num_boilers > 16 do
    print("How many pairs of boilers do you want to build?")
    write ("> ") input = io.read()
    num_boilers = tonumber(input)
    if (num_boilers < 1) then print("Enter at least 1") end
    if (num_boilers > 16) then print("Can build a maximum of 16 pairs at once") end
end

initSlots(num_boilers)
resupplyAll()

for i = 1, num_boilers do
    moveTo(1, i * 4 - 3)
    faceTo(1, 0)
    buildUnit()

    -- move back in x direction only
    faceTo(-1, 0)
    forceForward(); forceForward(); forceForward(); forceForward(); forceForward()
    forceDownwards()
    moveTo(0, 0)

    throwTrash()
    resupplyAll()
end

faceTo(1, 0)
print("Finished building ", num_boilers, " boilers.")