-- Constants
DIR_POS_X = 0
DIR_POS_Z = 1
DIR_NEG_X = 2
DIR_NEG_Z = 3

-- Global initializations
currentX = 0
currentY = 0
currentZ = 0
currentDirection = DIR_POS_X

originNoAttackRadius = 0
throwAwaySlot = nil
throwAwayForceDigBlocks = false

function setOriginNoAttackRadius(n)
	originNoAttackRadius = n
end

function setThrowAwaySlot(n)
	throwAwaySlot = n
end

function setThrowAwayForceDigBlocks(b)
	throwAwayForceDigBlocks = b
end

function findEmptySlot(rangeStart, rangeEnd)
	local i
	for i = rangeStart,rangeEnd do
		if turtle.getItemCount(i) == 0 then
			return i
		end
	end
	return nil
end

function selectEmptySlot(rangeStart, rangeEnd)
	local slot = findEmptySlot(rangeStart, rangeEnd)
	if slot ~= nil then
		turtle.select(slot)
		return true
	else
		return false
	end
end

function throwAwayDigHelper(detectFunc, compareFunc, dropFunc, digFunc)
	if not detectFunc() then
		return false
	end
	local slot
	local dumpWhen
	if throwAwaySlot ~= nil then
		slot = throwAwaySlot
		dumpWhen = "full"
	else
		slot = findEmptySlot(1, 16)
		if slot == nil then
			slot = 1
			dumpWhen = "never"
		else
			dumpWhen = "always"
		end
	end
	turtle.select(slot)
	if turtle.getItemCount(slot) > 0 then
--		if (turtle.getItemSpace(slot) < 1 or (not compareFunc())) and dumpWhen ~= "never" then
			dropFunc()
--		end
	end
	local result = digFunc()
	if result then
		if dumpWhen == "always" then
			dropFunc()
		end
	end
	return result
end

-- These 3 functions attempt to dig and throw away a block.
-- If a Throw Away Slot is configured, that slot is managed and used to to throw away any dug items.
-- Otherwise, it attempts to drop the item without polluting slots, but in some cases, the dug item might be added to an existing slot that contains the same item

function throwAwayDig()
	return throwAwayDigHelper(turtle.detect, turtle.compare, turtle.dropUp, turtle.dig)
end

function throwAwayDigUp()
	return throwAwayDigHelper(turtle.detectUp, turtle.compareUp, turtle.dropDown, turtle.digUp)
end

function throwAwayDigDown()
	return throwAwayDigHelper(turtle.detectDown, turtle.compareDown, turtle.dropUp, turtle.digDown)
end

function checkFuel(minFuel)
	if turtle.getFuelLevel() < minFuel then
		local needFuel = minFuel - turtle.getFuelLevel()
		if selectEmptySlot(1, 16) then
			print("Place at least " .. needFuel .. " units of fuel in selected slot")
			io.read()
			turtle.refuel()
			return checkFuel(minFuel)
		else
			turtle.select(16)
			print("Place at least " .. needFuel .. " units of fuel in selected slot and save items in there")
			io.read()
			turtle.refuel()
			print("Replace items that were in the slot")
			io.read()
			return checkFuel(minFuel)
		end
	else
		return true
	end
end

function forward()
	checkFuel(1)
	if turtle.forward() then
		if currentDirection == DIR_POS_X then
			currentX = currentX + 1
		elseif currentDirection == DIR_POS_Z then
			currentZ = currentZ + 1
		elseif currentDirection == DIR_NEG_X then
			currentX = currentX - 1
		elseif currentDirection == DIR_NEG_Z then
			currentZ = currentZ - 1
		end
		return true
	else
		return false
	end
end

function back()
	checkFuel(1)
	if turtle.back() then
		if currentDirection == DIR_POS_X then
			currentX = currentX - 1
		elseif currentDirection == DIR_POS_Z then
			currentZ = currentZ - 1
		elseif currentDirection == DIR_NEG_X then
			currentX = currentX + 1
		elseif currentDirection == DIR_NEG_Z then
			currentZ = currentZ + 1
		end
		return true
	else
		return false
	end
end

function up()
	if turtle.up() then
		currentY = currentY + 1
		return true
	else
		return false
	end
end

function down()
	if turtle.down() then
		currentY = currentY - 1
		return true
	else
		return false
	end
end

function turnRight()
	turtle.turnRight()
	if currentDirection == DIR_POS_X then
		currentDirection = DIR_POS_Z
	elseif currentDirection == DIR_POS_Z then
		currentDirection = DIR_NEG_X
	elseif currentDirection == DIR_NEG_X then
		currentDirection = DIR_NEG_Z
	elseif currentDirection == DIR_NEG_Z then
		currentDirection = DIR_POS_X
	end
end

function turnLeft()
	turtle.turnLeft()
	if currentDirection == DIR_POS_X then
		currentDirection = DIR_NEG_Z
	elseif currentDirection == DIR_NEG_Z then
		currentDirection = DIR_NEG_X
	elseif currentDirection == DIR_NEG_X then
		currentDirection = DIR_POS_Z
	elseif currentDirection == DIR_POS_Z then
		currentDirection = DIR_POS_X
	end
end


-- Select the first slot in a given range that has items in it, or prompt if none found
function selectUsedSlot(rangeStart, rangeEnd, itemString)
	local i
	for i = rangeStart,rangeEnd do
		if turtle.getItemCount(i) > 0 then
			turtle.select(i)
			return
		end
	end
	print("Place item " .. itemString .. " in slots " .. rangeStart .. " through " .. rangeEnd)
	io.read()
	selectUsedSlot(rangeStart, rangeEnd, itemString)
end

function getSlotRangeSpace(rangeStart, rangeEnd)
	local i
	local ctr = 0
	for i = rangeStart,rangeEnd do
		if turtle.getItemCount(i) == 0 then
			ctr = ctr + 1
		end
	end
	return ctr
end

function changeDirection(toDir)
	if
		(currentDirection == DIR_POS_X and toDir == DIR_NEG_X)
		or (currentDirection == DIR_NEG_X and toDir == DIR_POS_X)
		or (currentDirection == DIR_POS_Z and toDir == DIR_NEG_Z)
		or (currentDirection == DIR_NEG_Z and toDir == DIR_POS_Z)
	then
		turnLeft()
		turnLeft()
	elseif
		(currentDirection == DIR_POS_X and toDir == DIR_POS_Z)
		or (currentDirection == DIR_POS_Z and toDir == DIR_NEG_X)
		or (currentDirection == DIR_NEG_X and toDir == DIR_NEG_Z)
		or (currentDirection == DIR_NEG_Z and toDir == DIR_POS_X)
	then
		turnRight()
	elseif
		(currentDirection == DIR_POS_X and toDir == DIR_NEG_Z)
		or (currentDirection == DIR_NEG_Z and toDir == DIR_NEG_X)
		or (currentDirection == DIR_NEG_X and toDir == DIR_POS_Z)
		or (currentDirection == DIR_POS_Z and toDir == DIR_POS_X)
	then
		turnLeft()
	end
end

function getOriginDistance()
	return math.abs(currentX) + math.abs(currentY) + math.abs(currentZ)
end

function forceMove(moveFunc, digFunc, attackFunc, maxTries)
	if moveFunc() then
		return true, false, false
	end
	local dug = false
	local attacked = false
	local curTries = 0
	while true do
		if digFunc() then
			dug = true
			if moveFunc() then
				return true, dug, attacked
			end
		end
		attackFunc()
		attacked = true
		if moveFunc() then
			return true, dug, attacked
		end
		curTries = curTries + 1
		if maxTries ~= nil and curTries >= maxTries then
			return false, dug, attacked
		end
	end
end

-- These functions try to move, and if unsuccessful, try to dig the block and attack for maxTries
-- The return values are: success, dugABlock, attacked

function forceForward(maxTries)
	local dig = turtle.dig
	if throwAwayForceDigBlocks then dig = throwAwayDig end
	forceMove(forward, dig, turtle.attack, maxTries)
end

function forceUp(maxTries)
	local dig = turtle.digUp
	if throwAwayForceDigBlocks then dig = throwAwayDigUp end
	forceMove(up, dig, turtle.attackUp, maxTries)
end

function forceDown(maxTries)
	local dig = turtle.digDown
	if throwAwayForceDigBlocks then dig = throwAwayDigDown end
	forceMove(down, dig, turtle.attackDown, maxTries)
end

function forceBack(maxTries)
	if turtle.back() then
		return true, false, false
	end
	turnLeft()
	turnLeft()
	local res, dug, attacked = forceForward(maxTries)
	turnLeft()
	turnLeft()
	return res, dug, attacked
end

function moveTo(x, y, z, dir, force, maxTries, stepCallback)
	local oldDir = currentDirection
	checkFuel(math.abs(x - currentX) + math.abs(y - currentY) + math.abs(z - currentZ))
	while currentX < x do
		changeDirection(DIR_POS_X)
		if force ~= nil and force then forceForward(maxTries) else forward() end
		if stepCallback ~= nil then stepCallback() end
	end
	while currentX > x do
		changeDirection(DIR_NEG_X)
		if force ~= nil and force then forceForward(maxTries) else forward() end
		if stepCallback ~= nil then stepCallback() end
	end
	while currentZ < z do
		changeDirection(DIR_POS_Z)
		if force ~= nil and force then forceForward(maxTries) else forward() end
		if stepCallback ~= nil then stepCallback() end
	end
	while currentZ > z do
		changeDirection(DIR_NEG_Z)
		if force ~= nil and force then forceForward(maxTries) else forward() end
		if stepCallback ~= nil then stepCallback() end
	end
	while currentY < y do
		if force ~= nil and force then forceUp(maxTries) else up() end
		if stepCallback ~= nil then stepCallback() end
	end
	while currentY > y do
		if force ~= nil and force then forceDown(maxTries) else down() end
		if stepCallback ~= nil then stepCallback() end
	end
	if dir ~= nil then changeDirection(dir) end
end

function moveToX(x, force, maxTries, stepCallback)
	moveTo(x, currentY, currentZ, nil, force, maxTries, stepCallback)
end

function moveToY(y, force, maxTries, stepCallback)
	moveTo(currentX, y, currentZ, nil, force, maxTries, stepCallback)
end

function moveToZ(z, force, maxTries, stepCallback)
	moveTo(currentX, currentY, z, nil, force, maxTries, stepCallback)
end

function moveToXZ(x, z, force, maxTries, stepCallback)
	moveTo(x, currentY, z, nil, force, maxTries, stepCallback)
end

function getX()
	return currentX
end

function getY()
	return currentY
end

function getZ()
	return currentZ
end

function getDirection()
	return currentDirection
end

function getPosition()
	return getX(), getY(), getZ(), getDirection()
end

function resetPosition(x, y, z, dir)
	currentX = x
	currentY = y
	currentZ = z
	if dir ~= nil then currentDirection = dir end
end

