Wood = {isHome = false}

function Wood:readConfig(path)
    -- reading configFile into a table
    if (path == nil) then
        term.write("init must have a path for the config file as argument")
        return -1
    end
    if (type(path) ~= "string") then
        term.write("invalid argument")
        return -1
    end
    if (not fs.exists(path)) then
        term.write("could not find config file \n please make sure it exists and the path is correct")
        return -1
    end
    local file = fs.open(path, "r")
    local config = {}
    while true do
        local line = file.readLine()
        if not line then
            break
        end
        local numLine = tonumber(line)
        config[#config+1] = numLine
    end
    file.close()

    --ToDo: implement a sanity check for the config file

    self.treePos = {config[1] + config[5], config[2] + config[6], config[3] + config [7], config[8]}
    self.homePos = {config[1], config[2], config[3], config[4]}
end

function Wood:new(t)
    t = t or {}
    setmetatable(t, self)
    self.__index = self
    return t
end

function Wood:autoHome()
    self.dir = getDir()
    self.x, self.y, self.z = gps.locate()
    if (self.x == self.homePos[1] and self.y == self.homePos[2] and self.z == self.homePos[3] and self.dir == self.homePos[4]) then
        self.isHome = true
        self:log("turtle is already at home position")
        return true
    else
        if (self:isOnTree()) then
            self.isBroken = true
            self:log("broken tree detected")
        end
        self:log("going to home position")
        self:moveTo("home")
        

    end
end

function Wood:log(message)
    if self.verbose == true then
        print(message)
    end
end

function Wood:isOnTree()
    return false
end

function Wood:offset(position, dir, offset)
    self:assertPositionFormat(position)
    assert(type(dir) == "number", "dir must be a number")
    assert(type(offset) == "number", "offset must be a number")
    local newPosition = self:copyPositionTable(position)
    dir = dir % 4
    if (dir == 0) then newPosition[1] = newPosition[1] - offset
    elseif (dir == 1) then newPosition[3] = newPosition[3] - offset
    elseif (dir == 2) then newPosition[1] = newPosition[1] + offset
    else newPosition[3] = newPosition[3] + offset
    end
    return newPosition
end

function Wood:copyPositionTable(position)
    self:assertPositionFormat(position)
    local positionClone = {}
    for i = 1, 4 do
        positionClone[i] = position[i]
    end
    return positionClone
end

function Wood:assertPositionFormat(position)
    assert(type(position) == "table", "the target is not a table")
    assert(type(position[1]) == "number" and type(position[2]) == "number" and type(position[3]) == "number", "the table doesn't contain the position")
    assert(type(position[4]) == "number", "direction is missing")
end

function Wood:resolveTarget(target)
    if target == "home" then
        target = self.homePos
    elseif target == "tree" then
        target = self.treePos
    elseif target == "sapling" then
        target = self.treePos
        target = self:offset(target, target[4], 1)
        target[2] = target[2] + 1
    elseif target == "saplingChest" then
        target = self.homePos
        target = self:offset(target, target[4] - 1, 1)
    elseif target == "coalChest" then
        target = self.homePos
        target = self:offset(target, target[4] -1, 2)
    elseif target == "outputChest" then
        target = self.homePos
        target = self:offset(target, target[4] -1, -1)
    end

    self:assertPositionFormat(target)
    self:log("target resolved")
    return target
end

function Wood:moveTo(target)
    target = self:resolveTarget(target)
    self:log("moving to target")

    moveTo(target[1], target[2], target[3], target[4], self.dir)

    self.x, self.y, self.z, self.dir = target[1], target[2], target[3], target[4]

    self:log("target reached")

end

function Wood:hoverTo(target)
    target = self:resolveTarget(target)
    self:log("hovering to target")

    hoverTo(target[1], target[2], target[3], target[4], self.dir)
    self.x, self.y, self.z, self.dir = target[1], target[2], target[3], target[4]

    self:log("target reached")
end

function Wood:isPos(position)
    position = self:resolveTarget(position)
    return self.x == position[1] and self.y == position[2] and self.z == position[3] and self.dir == position[4]
end

function Wood:checkTree(goBack)
    if (goBack == nil) then goBack = true end
    if(goBack == true) then
        goBack = {self.x, self.y, self.z, self.dir}
    end

    self:log("moving to tree")
    self:moveTo("tree")

    self:log("inspecting tree...")
    local bd, data = turtle.inspect()
    local result
    if (bd == false) then result = "nothing" 
    elseif (data.tags["minecraft:saplings"] == true) then result = "sapling"
    elseif (data.tags["minecraft:logs"]) then result = "tree"
    else result = "other" end

    self:log(result)

    if (goBack ~= false) then
        self:log("moving to goBack")
        self:moveTo(goBack)
    end

    self:log("checkTree complete")
    self.treeState = result
    return result
end

function Wood:cutTree(goBack)
    if (goBack == nil) then goBack = true end
    if(goBack == true) then
        goBack = {self.x, self.y, self.z, self.dir}
    end
    
    self:moveTo("tree")

    self:log("cutting tree...")
    cut4()
    self:log("successful")

    if (goBack ~= false) then
        self:log("moving to goBack")
        self:moveTo(goBack)
    end

end

function Wood:plantTree(goBack)
    if (goBack == nil) then goBack = true end
    if(goBack == true) then
        goBack = {self.x, self.y, self.z, self.dir}
    end

    self:clearInv("saplingChest")
    turtle.select(1)
    turtle.suckDown(4)

    self:moveTo("sapling")
    plant4(1)

    if (goBack ~= false) then
        self:log("moving to goBack")
        self:hoverTo(goBack)
    end
end

function Wood:sort(goBack)
    if (goBack == nil) then goBack = true end
    if(goBack == true) then
        goBack = {self.x, self.y, self.z, self.dir}
    end

    self:moveTo("home")
    local hasMore = self:suckAllDown()
    self:clearInv(goBack)
    return hasMore
end

function Wood:getFuelLevel()
    return turtle.getFuelLevel()
end

function Wood:suckAllDown()
    for i = 1, 16 do
        turtle.select(i)
        if(turtle.getItemCount() == 0) then
            if(turtle.suckDown() == false) then
                return true
            end
        end
    end
    return false
end

function Wood:clearInv(goBack)
    if (goBack == nil) then goBack = true end
    if(goBack == true) then
        goBack = {self.x, self.y, self.z, self.dir}
    end
    if (self:isInInv("sapling", true)) then
        self:moveTo("saplingChest")
        self:dropItemsDown("sapling", false)
    end
    if (self:isInInv("coal", false)) then
        self:moveTo("coalChest")
        self:dropItemsDown("coal", false)
    end
    if (self:isInvEmpty(true) == false) then
        self:moveTo("outputChest")
        self:dropItemsDown("any", false)
    end

    if (goBack ~= false) then
        self:log("moving to goBack")
        self:moveTo(goBack)
    end
end

function Wood:isInInv(item, update)
    assert(type(item) == "string", "item must be a string")
    self:assertInvUpdate(update)

    local tag = self:resolveTag(item)
    if (tag == "") then
        for i = 1, 16 do
            if self:checkItemName(item, i) == true then return true end
        end
    else
        for i = 1, 16 do
            if self:checkItemTag(tag, i) == true then return true end
        end
    end
    return false
end

function Wood:dropItemsDown(item, update)
    assert(type(item) == "string", "item must be a string")
    self:assertInvUpdate(update)

    local tag = self:resolveTag(item)
    
    if(tag == "") then
        for i = 1, 16 do
            if self:checkItemName(item, i) == true then
                turtle.select(i)
                turtle.dropDown()
            end
        end
    else
        for i = 1, 16 do
            if self:checkItemTag(tag, i) == true then
                turtle.select(i)
                turtle.dropDown()
            end
        end
    end
    
end

function Wood:resolveTag(item)
    local tag = ""
    if (item == "sapling") then
        tag = "minecraft:saplings"
    elseif (item == "log" or item == "wood") then
        tag = "minecraft:logs"
    elseif (item == "coal") then
        tag = "minecraft:coals"
    end
    return tag
end

function Wood:checkItemTag(tag, slot)
    assert(type(tag) == "string", "tag must be a string")
    assert(type(slot) == "number" and slot > 0 and slot <= 16, "slot must be between 1 and 16")
    if self.inv[slot] == nil then
        self:log("self.inv is empty at slot "..tostring(slot))
        return false
    end
    if self.inv[slot].tags == nil then
        self:log("no tags found")
        return false
    end
    if self.inv[slot].tags[tag] == true then
        return true
    else
        return false
    end
end

function Wood:checkItemName(name, slot)
    assert(type(name) == "string", "tag must be a string")
    assert(type(slot) == "number" and slot > 0 and slot <= 16, "slot must be between 1 and 16")
    if self.inv[slot] == nil then
        self:log("self.inv is empty at slot "..tostring(slot))
        return false
    end
    if self.inv[slot].name == nil then
        self:log("no name found")
        return false
    end
    if name == "any" then
        return true
    end
    if self.inv[slot].name == name then
        return true
    else
        return false
    end
end

function Wood:updateInv()
    self.inv = {}
    for slot = 1, 16 do
        self.inv[slot] = turtle.getItemDetail(slot, true)
    end
end

function Wood:isInvEmpty(update)
    self:assertInvUpdate(update)
    for i = 1, 16 do
        if (self.inv[i] ~= nil) then
            return false
        end
    end
    return true
end

function Wood:assertInvUpdate(update)
    if (update ~= false) then update = true end

    if self.inv == nil then
        update = true
    end

    assert(type(update) == "boolean","update must be of the type boolean")
    if update == true then
        self:updateInv()
    end
end