-- Settings
originNoVeinRadius = 2
originNoAttackRadius = 6

spaceBetweenMinorTunnels = 2
minorTunnelLengthPerSide = 30
spaceBetweenVerticalLevels = 3
maxMinorTunnelsPerHome = 12

enderChestMode = true
refuelFromInvIfBelowFuelLevel = 10000
minFuelForTrip = 100

whitelistMode = false
twoHighTunnels = false

chunkLoaderMode = true
-- Ie, for a 3x3 chunk loader, the range is 24
chunkLoaderRange = 24

-- Code

if chunkLoaderMode and minorTunnelLengthPerSide > chunkLoaderRange - 3 then
	minorTunnelLengthPerSide = chunkLoaderRange - 3
end

digSlotsStart = 0
digSlotsEnd = 0
digSlotsFull = false

function getFreeDigSlots()
	if digSlotsFull then return 0 end
	return aturtle.getSlotRangeSpace(digSlotsStart, digSlotsEnd)
end

function startCheck()
	local i
	local ok = true
	local gotItems = false
	local gotBlank = false
	local checkEnd = 15
	if chunkLoaderMode then checkEnd = 14 end
	for i = 1,checkEnd do
		if turtle.getItemCount(i) > 0 then
			if gotBlank then
				ok = false
				break
			end
			gotItems = true
		else
			if i == 1 then
				ok = false
				break
			end
			if not gotBlank then
				digSlotsStart = i
				itemlistSlotEnd = i - 1
				gotBlank = true
			end
		end
	end
	if ok then
		if turtle.getItemCount(16) == 0 then
			ok = false
		end
	end
	if not ok then
		print("Please set up turtle inventory.  Place blacklist items at beginning of inventory, and chests in slot 16.")
		if chunkLoaderMode then
			print("Place 2 chunk loaders in slot 15.")
		end
		io.read()
		startCheck()
	end
	digSlotsEnd = 15
	if chunkLoaderMode then
		digSlotsEnd = 14
	end
	itemlistSlotStart = 1
	if whitelistMode == true then
		digSlotsStart = 1
	end
end

function setupGeneralSettings()
	aturtle.setOriginNoAttackRadius(originNoAttackRadius)
end

-- Returns number of free slots after pruning
function checkRefuelFromInventory()
	local i
	if turtle.getFuelLevel() < refuelFromInvIfBelowFuelLevel then
		for i = digSlotsStart,digSlotsEnd do
			if turtle.getItemCount(i) > 0 and i > itemlistSlotEnd then
				turtle.select(i)
				turtle.refuel()
			end
		end
	end
end

function isSelectedSlotInItemlist()
	local i
	for i = itemlistSlotStart,itemlistSlotEnd do
		if turtle.getItemCount(i) > 0 and turtle.compareTo(i) then
			return true
		end
	end
	return false
end

function pruneInventory()
	if digSlotsFull then return 0 end
	checkRefuelFromInventory()
	local i
	local freeSlots = 0
	if not whitelistMode then
		for i = digSlotsStart,digSlotsEnd do
			if turtle.getItemCount(i) > 0 then
				turtle.select(i)
				if isSelectedSlotInItemlist() then
					turtle.dropUp()
					freeSlots = freeSlots + 1
				end
			else
				freeSlots = freeSlots + 1
			end
		end
		for i = itemlistSlotStart,itemlistSlotEnd do
			if turtle.getItemCount(i) > 1 then
				turtle.select(i)
				if isSelectedSlotInItemlist() then
					turtle.dropUp(turtle.getItemCount(i) - 1)
				end
			end
		end
	else
		for i = digSlotsStart,digSlotsEnd do
			if turtle.getItemCount(i) > 0 and i > itemlistSlotEnd then
				turtle.select(i)
				if not isSelectedSlotInItemlist() then
					turtle.dropUp()
					freeSlots = freeSlots + 1
				end
			end
		end
	end
	if freeSlots == 0 then digSlotsFull = true end
	return freeSlots
end

-- Returns false if did not successfully drop all items
function dropDigSlots()
	local i
	digSlotsFull = false
	for i = digSlotsStart,digSlotsEnd do
		while turtle.getItemCount(i) > 0 do
			turtle.select(i)
			if whitelistMode and i <= itemlistSlotEnd then
				if turtle.getItemCount(i) > 1 and not turtle.drop(turtle.getItemCount(i) - 1) then
					return false
				end
				break
			else
				if not turtle.drop() then
					return false
				end
			end
		end
	end
	return true
end

function doHomeDrop()
	pruneInventory()
	return dropDigSlots()
end

outOfChests = false

function setupHomeArea()
	-- print("Setting up home area ...")
	turtle.select(digSlotsStart)
	aturtle.resetPosition(0, 0, 0, 0)
	aturtle.forceForward()
	aturtle.forceUp()
	aturtle.forceDown()
	aturtle.forceBack()
	aturtle.forceUp()
	aturtle.forceDown()
	aturtle.changeDirection(0)
	turtle.select(16)
	while turtle.getItemCount(16) == 0 or outOfChests == true do
		print("Out of chests in slot 16.  Give chests and press enter.")
		io.read()
		outOfChests = false
	end
	turtle.place()
	if turtle.getItemCount(16) == 0 and not enderChestMode then
		outOfChests = true
	end
	doHomeDrop()
	--print("Done setting up home area.")
end

function getSurroundingBlocks(ox, oy, oz)
	return {
		{ x = ox, y = oy, z = oz - 1 },
		{ x = ox - 1, y = oy, z = oz },
		{ x = ox, y = oy, z = oz + 1 },
		{ x = ox, y = oy - 1, z = oz },
		{ x = ox, y = oy + 1, z = oz },
		{ x = ox + 1, y = oy, z = oz }
	}
end

-- dir is "up", "down", or "forward".  Returns "empty", "blacklist", or "good"
-- TODO: check if within origin safe zone
function compareBlacklist(dir)
	if dir == "up" then
		if not turtle.detectUp() then
			return "empty"
		end
	elseif dir == "down" then
		if not turtle.detectDown() then
			return "empty"
		end
	elseif dir == "forward" then
		if not turtle.detect() then
			return "empty"
		end
	end
	local inListStr = "blacklist"
	local notInListStr = "good"
	if whitelistMode then
		inListStr = "good"
		notInListStr = "blacklist"
	end
	local i
	for i = itemlistSlotStart,itemlistSlotEnd do
		if turtle.getItemCount(i) > 0 then
			turtle.select(i)
			if dir == "up" and turtle.compareUp() then
				return inListStr
			elseif dir == "down" and turtle.compareDown() then
				return inListStr
			elseif dir == "forward" and turtle.compare() then
				return inListStr
			end
		end
	end
	return notInListStr
end

checked = {}

function addToChecked(x, y, z)
	checked[x .. " " .. y .. " " .. z] = true
end

function checkMineVein()
	-- Each entry is a block that has either been checked for ores, or has been mined
	-- Each entry has a value of true and a key of: x .. " " .. y .. " " .. z
	-- local checked = {}
	-- Used as a stack to keep a record of how we got here, with blocks to check at each step
	-- Each entry is in the form: { x = .., y = .., z = .., toCheck = {..} }
	-- toCheck is a list of entries in the form { x = .., y = .., z = .. }
	local path = {}

	local checkBlock
	local entry
	local checkedKey
	local compareDirection
	local compareResult
	local r

	table.insert(path, { x = aturtle.getX(), y = aturtle.getY(), z = aturtle.getZ(), toCheck = getSurroundingBlocks( aturtle.getX(), aturtle.getY(), aturtle.getZ() ) } )
	-- checked[aturtle.getX() .. " " .. aturtle.getY() .. " " .. aturtle.getZ()] = true
	addToChecked(aturtle.getX(), aturtle.getY(), aturtle.getZ())

	while #path > 0 do
		entry = path[#path]
		r = aturtle.moveTo(entry.x, entry.y, entry.z, nil, true)
		if r == false or #(entry.toCheck) == 0 then
			table.remove(path)
		else
			checkBlock = entry.toCheck[#(entry.toCheck)]
			table.remove(entry.toCheck)
			checkedKey = checkBlock.x .. " " .. checkBlock.y .. " " .. checkBlock.z
			if checked[checkedKey] == nil then
				checked[checkedKey] = true
				local outOfRange = false
				if chunkLoaderMode and (math.abs(checkBlock.x) > chunkLoaderRange - 3 or math.abs(checkBlock.z) > chunkLoaderRange - 3) then
					outOfRange = true
				end
				if not outOfRange and (math.abs(checkBlock.x) > originNoVeinRadius or math.abs(checkBlock.y) > originNoVeinRadius or math.abs(checkBlock.z) > originNoVeinRadius) then
					aturtle.turnFacing(checkBlock.x, checkBlock.y, checkBlock.z)
					compareDirection = "forward"
					if checkBlock.y > aturtle.getY() then compareDirection = "up" end
					if checkBlock.y < aturtle.getY() then compareDirection = "down" end
					compareResult = compareBlacklist(compareDirection)
					if compareResult == "good" then
						if getFreeDigSlots() == 0 then
							pruneInventory()
							turtle.select(digSlotsStart)
						end
						r = aturtle.moveTo(checkBlock.x, checkBlock.y, checkBlock.z, nil, true)
						if r ~= false then
							table.insert(path, { x = checkBlock.x, y = checkBlock.y, z = checkBlock.z, toCheck = getSurroundingBlocks( checkBlock.x, checkBlock.y, checkBlock.z ) } )
						end
					end
				end
			end
		end
	end
end

function estFuelToHome()
	return math.abs(aturtle.getX()) + math.abs(aturtle.getY()) + math.abs(aturtle.getZ()) + 50
end

-- Analog of aturtle.moveTo() but using the mining procedure.  Returns true if we get to the destination.  Returns false if need to make a trip home.
-- If the noStop parameter is set to true, the turtle will not stop when full (but will skip mining veins)
function mineTo(x, y, z, noStop, twoHigh)
	turtle.select(digSlotsStart)
	local isFull = false
	addToChecked(aturtle.getX(), aturtle.getY(), aturtle.getZ())
	return aturtle.moveTo(x, y, z, nil, true, nil, function()
		addToChecked(aturtle.getX(), aturtle.getY(), aturtle.getZ())
		if (twoHigh ~= nil and twoHigh == true) or (twoHighTunnels ~= nil and twoHighTunnels == true) then
			turtle.digUp()
		end
		if getFreeDigSlots() <= 1 then
			if pruneInventory() <= 1 then
				isFull = true
			end
		end
		if isFull and (noStop == nil or noStop == false) then
			return false
		end
		if turtle.getFuelLevel() <= estFuelToHome() + 30 and (noStop == nil or noStop == false) then
			checkRefuelFromInventory()
			if turtle.getFuelLevel() <= estFuelToHome() + 30 then
				return false
			end
		end
		if turtle.getFuelLevel() > estFuelToHome() + 30 then
			checkMineVein()
		end
		turtle.select(digSlotsStart)
	end )
end

-- Starting at an offset in the main tunnel, digs 1 minor tunnel loop.  Returns true if it was fully dug without
-- needing to return home.  Returns false if stops in the middle and needs to return home.
function digMinorTunnel()
	local minorTunnelZ = aturtle.getZ()
	local centerX = aturtle.getX()
	local bottomY = aturtle.getY()
	checked = {}
	if not mineTo(centerX + minorTunnelLengthPerSide, bottomY, minorTunnelZ) then return false end
	if not mineTo(centerX + minorTunnelLengthPerSide, bottomY + spaceBetweenVerticalLevels + 1, minorTunnelZ) then return false end
	if not mineTo(centerX - minorTunnelLengthPerSide, bottomY + spaceBetweenVerticalLevels + 1, minorTunnelZ) then return false end
	if not mineTo(centerX - minorTunnelLengthPerSide, bottomY, minorTunnelZ) then return false end
	if not mineTo(centerX, bottomY, minorTunnelZ) then return false end
	return true
end

-- Starting at home position, digs minor tunnels until need to go home.  Returns the NEXT minor tunnel to dig.
function digUntilHome(startMinorTunnel)
	local minorTunnel = startMinorTunnel
	local minorTunnelZ
	while true do
		-- print("Mining to minor tunnel " .. minorTunnel .. " ...")
		-- print("Current pos " .. aturtle.getX() .. " " .. aturtle.getY() .. " " .. aturtle.getZ() .. " " .. aturtle.getDirection())
		minorTunnelZ = minorTunnel * (spaceBetweenMinorTunnels + 1) + 2
		-- print("Mining to 0 0 " .. minorTunnelZ)
		-- io.read()
		if not mineTo(0, 0, minorTunnelZ, false, true) then return minorTunnel end
		-- print("Digging minor tunnel ...")
		-- print("Current pos " .. aturtle.getX() .. " " .. aturtle.getY() .. " " .. aturtle.getZ() .. " " .. aturtle.getDirection())
		-- io.read()
		if not digMinorTunnel() then return minorTunnel + 1 end
		minorTunnel = minorTunnel + 1
		pruneInventory()
		if getFreeDigSlots() < math.floor((digSlotsEnd - digSlotsStart + 1) / 2) then
			return minorTunnel
		end
	end
end

-- Starting from a home location, sets up the home, reset coords, digs until it's time to start a new home.  Returns the Z coordinate where the new home should be.
function homeDigIteration()
	setupHomeArea()
	local minorTunnel = 0
	while true do
		aturtle.checkFuel(minFuelForTrip)
		print("Current fuel: " .. turtle.getFuelLevel())
		-- print("Doing dig iteration ...")
		minorTunnel = digUntilHome(minorTunnel)
		mineTo(aturtle.getX(), 0, aturtle.getZ(), true, false)
		mineTo(0, 0, aturtle.getZ(), true, false)
		mineTo(0, 0, 0, true, false)
		aturtle.changeDirection(0)
		local minorTunnelZ = minorTunnel * (spaceBetweenMinorTunnels + 1) + 2
		local nextHomeZ = minorTunnelZ
		if chunkLoaderMode then
			nextHomeZ = chunkLoaderRange + 8
		end
		if not doHomeDrop() or minorTunnel >= maxMinorTunnelsPerHome or (chunkLoaderMode and minorTunnelZ >= chunkLoaderRange - 4) then
			return nextHomeZ
		end
	end
end

function mineToNextHome(nextHomeZ)
	if chunkLoaderMode then
		mineTo(0, 0, nextHomeZ / 2, true, true)
		aturtle.forceDown()
		aturtle.forceUp()
		turtle.select(15)
		turtle.placeDown()
		turtle.select(digSlotsStart)
		aturtle.moveTo(0, 0, 0, nil, true)
		turtle.select(15)
		turtle.dropUp()
		turtle.digDown()
		mineTo(0, 0, nextHomeZ, true, true)
		aturtle.forceDown()
		aturtle.forceUp()
		turtle.select(15)
		turtle.placeDown()
		aturtle.moveTo(0, 0, nextHomeZ / 2, nil, true)
		turtle.select(15)
		turtle.dropUp()
		turtle.digDown()
		turtle.select(digSlotsStart)
		aturtle.moveTo(0, 0, nextHomeZ, nul, true)
	else
		mineTo(0, 0, nextHomeZ, true, true)
	end
end

function mineMain()
	local nextHomeZ
	if chunkLoaderMode then
		turtle.select(digSlotsStart)
		aturtle.forceDown()
		aturtle.forceUp()
		turtle.select(15)
		turtle.placeDown()
	end
	while true do
		nextHomeZ = homeDigIteration()
		if enderChestMode == true then
			turtle.select(16)
			if turtle.getItemCount(16) ~= 0 then
				turtle.dropUp()
			end
			turtle.dig()
		end
		-- print("Mining to next home ...")
		mineToNextHome(nextHomeZ)
		aturtle.changeDirection(0)
	end
end

aturtle.turnLeft()
setupGeneralSettings()
startCheck()
mineMain()

