
local computer = require("computer")
local component = require("component")
local robot = require("robot")
local event = require("event")
local sides = require("sides")
local geo = component.geolyzer
local inventoryController = component.inventory_controller

-------------------------------------------------------------------------------------------------------------------

local fuels = {
	"minecraft:coal",
	"minecraft:lava_bucket",
	"minecraft:coal_block",
}

local shittyResources = {
	"minecraft:cobblestone",
	"minecraft:grass",
	"minecraft:dirt",
	"minecraft:gravel",
	"minecraft:sand",
	"minecraft:sandstone",
	"minecraft:torch",
	"minecraft:planks",
	"minecraft:fence",
	"minecraft:chest",
	"minecraft:monster_egg",
	"minecraft:stonebrick",
}

local oreSearchRadius = 24
local searchPassesCount = 5
local minimumOreHardness = 2.5
local maximumOreHardness = 9
local replaceToolDurabilityTrigger = 0.05
local replaceToolRegex = "(minecraft:).+(_pickaxe)"
local rechargeTrigger = 0.1
local dropShittyResourcesOnEmptySlots = 5

-------------------------------------------------------------------------------------------------------------------

local program = {}
local robotPosition = {x = 0, y = 0, z = 0, rotation = 0}
local energyStatusCheckEnabled = true
local toolStatusCheckEnabled = true
local generatorIsAvailable = component.isAvailable("generator")

function program.scan(radius, passes, minHardness, maxHardness)
	local ores = {}

	-- Заносим все руды в массивыч руд
	for pass = 1, passes do
		print("Scan pass " .. pass .. " started...")
		for x = -radius, radius do
			for z = -radius, radius do
				local stack = geo.scan(x, z, true)
				for y = 1, #stack do
					if stack[y] >= minHardness and stack[y] <= maxHardness then
						-- Заполняем координатную сетку, если массивов еще не существует
						ores[x] = ores[x] or {}
						ores[x][y] = ores[x][y] or {}

						-- Если мы уже сканировали этот блок, то получаем среднюю плотность из двух значений
						-- Если нет, то банально ставим полученное значение
						if ores[x][y][z] then
							ores[x][y][z] = (ores[x][y][z] + stack[y]) / 2
						else
							ores[x][y][z] = stack[y]
						end

						-- print("x=" .. x .. ", y=" .. y .. ", z=" .. z .. ", hardness=" .. stack[y])
					end
				end
			end

			-- Проверяем энергосостояние, а то уже один раз вылетело с ошибкой нот еаугх ЕНЕРГИ, БЛЯДЬ!!11
			program.checkEnergyStatus()
		end
	end

	-- Переебошиваем массив руд для более удобной работы с ним в линейном формате
	-- Не забываем подчищать говнище за собой, а то роботы не резиновые
	local newOres = {}
	for x in pairs(ores) do
		for y in pairs(ores[x]) do
			for z in pairs(ores[x][y]) do
				table.insert(newOres, {x = robotPosition.x + x, y = robotPosition.y + y - 33, z = robotPosition.z + z})
				ores[x][y][z] = nil
			end
			ores[x][y] = nil
		end
		ores[x]= nil
	end

	return newOres
end

local function getHardness(x, z)
	local stack = geo.scan(x, z)
	for i = 1, #stack do
		print("i=" .. i .. ", val=" .. stack[i])
		event.pull("key_down")
	end
end

function program.move(direction)
	while true do
		local swingSuccess, swingReason = component.robot.swing(direction)
		if swingSuccess or swingReason == "air" then
			local moveSuccess, moveReason = component.robot.move(direction)
			if moveSuccess then
				break
			end
		else
			if swingReason == "block" then
				print("Unbreakable block detected, going to base")
				program.returnToBase()
				os.exit()
			end
		end
	end

	if direction == sides.front or direction == sides.back then
		local directionOffset = direction == sides.front and 1 or -1

		if robotPosition.rotation == 0 then
			robotPosition.x = robotPosition.x + directionOffset
		elseif robotPosition.rotation == 1 then
			robotPosition.z = robotPosition.z + directionOffset
		elseif robotPosition.rotation == 2 then
			robotPosition.x = robotPosition.x - directionOffset
		elseif robotPosition.rotation == 3 then
			robotPosition.z = robotPosition.z - directionOffset
		end
	elseif direction == sides.up or direction == sides.down then
		local directionOffset = direction == sides.up and 1 or -1
		robotPosition.y = robotPosition.y + directionOffset
	end
end


function program.returnToBase()
	program.gotoPoint(0, robotPosition.y, 0)
	program.turnToRequiredRotation(0)
	program.gotoPoint(0, -2, 0)
	program.tryToDropShittyResources()
	program.gotoPoint(0, 0, 0)
	program.dropAllResoucesIntoBaseChest()
end

function program.getSlotWithFuel()
	for slot = 1, component.robot.inventorySize() do
		local stack = inventoryController.getStackInInternalSlot(slot)
		if stack then
			for fuel = 1, #fuels do
				if stack.name == fuels[fuel] then
					return slot
				end
			end
		end
	end
end

function program.tryToRechargeByGenerator()
	if generatorIsAvailable then
		if component.generator.count() == 0 then
			print("Generator is empty, trying to find some fuel in inventory")
			local slot = program.getSlotWithFuel()
			if slot then
				print("Found slot with fuel: " .. slot)
				local oldSlot = robot.select(slot)
				component.generator.insert()
				robot.select(oldSlot)
				return
			else
				print("Slot with fuel not found")
			end
		end
	end
end

function program.checkEnergyStatus()
	if computer.energy() / computer.maxEnergy() < rechargeTrigger then
		print("Low energy level detected")
		energyStatusCheckEnabled = false
		-- Запоминаем старую позицию, шобы суда вернуться
		local oldPosition = {x = robotPosition.x, y = robotPosition.y, z = robotPosition.z, rotation = robotPosition.rotation}
		-- Пиздуем на базу за зарядкой
		program.returnToBase()
		-- Заряжаемся, пока энергия не достигнет более-менее максимума
		while computer.energy() / computer.maxEnergy() < 0.99 do
			print("Charging up: " .. math.floor(computer.energy() / computer.maxEnergy() * 100) .. "%")
			os.sleep(1)
		end
		-- Пиздуем обратно
		program.gotoPoint(oldPosition.x, oldPosition.y, oldPosition.z)
		program.turnToRequiredRotation(oldPosition.rotation)
		energyStatusCheckEnabled = true
	end
end

function program.turn(clockwise)
	component.robot.turn(clockwise)
	robotPosition.rotation = robotPosition.rotation + (clockwise and 1 or -1)
	if robotPosition.rotation > 3 then
		robotPosition.rotation = 0
	elseif robotPosition.rotation < 0 then
		robotPosition.rotation = 3
	end
end

function program.turnToRequiredRotation(requiredRotation)
	local difference = robotPosition.rotation - requiredRotation
	
	if difference ~= 0 then
		local fastestWay
		if difference > 0 then
			if difference > 2 then fastestWay = true else fastestWay = false end
		else
			if -difference > 2 then fastestWay = false else fastestWay = true end
		end

		while robotPosition.rotation ~= requiredRotation do
			program.turn(fastestWay)
		end
	end
end

function program.gotoPoint(xTarget, yTarget, zTarget)
	local xDistance = xTarget - robotPosition.x
	local yDistance = yTarget - robotPosition.y
	local zDistance = zTarget - robotPosition.z

	if yDistance ~= 0 then
		local direction = yDistance > 0 and sides.up or sides.down
		for i = 1, math.abs(yDistance) do program.move(direction) end
	end

	if xDistance ~= 0 then
		program.turnToRequiredRotation(xDistance > 0 and 0 or 2)
		for i = 1, math.abs(xDistance) do program.move(sides.front) end
	end

	if zDistance ~= 0 then
		program.turnToRequiredRotation(zDistance > 0 and 1 or 3)
		for i = 1, math.abs(zDistance) do program.move(sides.front) end
	end

	-- Если количество пустых слотов меньше, чем лимит пустых слотов,
	-- то выбрасываем весь дерьмовый шмот, указанный в массиве дерьмового шмота
	program.tryToDropShittyResources()

	-- Если включена проверка энергосостояния, то делаем ее и возвращаемся на базу
	-- для подзарядки, если требуется
	if energyStatusCheckEnabled then program.checkEnergyStatus() end

	-- Проверяем также состояние инструментов
	if toolStatusCheckEnabled then program.checkToolStatus() end

	-- А еще заправляем генератор
	program.tryToRechargeByGenerator()
end

function program.findNearestOre(ores)
	local nearest
	for i = 1, #ores do
		local distance = math.sqrt((ores[i].x - robotPosition.x) ^ 2 + (ores[i].y - robotPosition.y) ^ 2 + (ores[i].z - robotPosition.z) ^ 2)
		if not nearest or distance < nearest.distance then
			nearest = {x = ores[i].x, y = ores[i].y, z = ores[i].z, distance = distance, oreIndex = i}
		end
	end
	return nearest
end

function program.scanAndDig(radius, passes, minHardness, maxHardness, bedrockLocation)
	local ores = program.scan(radius, passes, minHardness, maxHardness)
	print("Scanning finished, count of resources to mine: " .. #ores)
	while #ores > 0 do
		local nearest = program.findNearestOre(ores)
		if nearest.y >= bedrockLocation and nearest.y < 0 then
			-- print("Found next nearest ore: (" .. nearest.x .. "; " .. nearest.y .. "; " .. nearest.z .. ")")
			program.gotoPoint(nearest.x, nearest.y, nearest.z)
		end
		table.remove(ores, nearest.oreIndex)
	end
end

function program.getBedrockLocation()
	while true do
		local success, reason = component.robot.swing(sides.down)
		if success or reason == "air" then
			program.move(sides.down)
		else
			if reason == "block" then
				print("Bedrock location is: " .. robotPosition.y)
				return robotPosition.y
			end
		end
	end
end

function program.getEmptySlotsCount()
	local count = 0
	for slot = 1, robot.inventorySize() do
		count = count + (robot.count(slot) == 0 and 1 or 0)
	end
	return count
end

function program.tryToDropShittyResources()
	if program.getEmptySlotsCount() < dropShittyResourcesOnEmptySlots then 
		print("Trying to drop all shitty resources to free some slots for mining")
		for slot = 1, robot.inventorySize() do
			local stack = inventoryController.getStackInInternalSlot(slot)
			if stack then
				for i = 1, #shittyResources do
					if stack.name == shittyResources[i] then
						robot.select(slot)
						robot.drop()
					end
				end
			end
		end

		if program.getEmptySlotsCount() < dropShittyResourcesOnEmptySlots - 2 then
			local oldPosition = {x = robotPosition.x, y = robotPosition.y, z = robotPosition.z, rotation = robotPosition.rotation}
			program.returnToBase()
			program.gotoPoint(oldPosition.x, oldPosition.y, oldPosition.z)
			program.turnToRequiredRotation(oldPosition.rotation)
		end

		robot.select(1)
	end
end

function program.dropAllResoucesIntoBaseChest()
	print("Dropping all mined resources to chest on base")
	for slot = 1, robot.inventorySize() do
		local stack = inventoryController.getStackInInternalSlot(slot)
		if stack then
			if not string.match(stack.name, replaceToolRegex) then
				robot.select(slot)
				robot.drop()
			end
		end
	end
	robot.select(1)
end

function program.checkToolStatus()
	if robot.durability() < replaceToolDurabilityTrigger then
		print("Equipped tool durability lesser then " .. replaceToolDurabilityTrigger)
		local success = false
		
		for slot = 1, robot.inventorySize() do
			local stack = inventoryController.getStackInInternalSlot(slot)
			if stack then
				if string.match(stack.name, replaceToolRegex) and stack.damage / stack.maxDamage < replaceToolDurabilityTrigger then
					local oldSlot = robot.select(slot)
					inventoryController.equip()
					robot.select(oldSlot)
					success = true
					break
				end
			end
		end

		if not success and toolStatusCheckEnabled then
			toolStatusCheckEnabled = false
			returnToBase()
			print("No one useable tool are found in inventory, going back to base")
			os.exit()
		else
			print("Successfullty switched tool to another from inventory")
		end
	end
end

-------------------------------------------------------------------------------------------------------------------

-- getHardness(1, 0)
-- program.checkToolStatus()
-- program.move(sides.front)

-- Выбираем сразу первый слотик по умолчанию
robot.select(1)
-- Определяем позицию говна
print("Going deeper to determine bedrock location...")
local bedrockLocation = program.getBedrockLocation() + 4
-- Ебошим стартовую точку после определения позиции говна, и если она слишком высоко, то робот начнет как раз от нее же
local startPoint = bedrockLocation + 32
if startPoint > 0 then startPoint = 0 end

-- Пиздуем на старт и вкалываем до посинения
program.gotoPoint(0, startPoint, 0)
program.scanAndDig(oreSearchRadius, searchPassesCount, minimumOreHardness, maximumOreHardness, bedrockLocation)

-- В конце возвращаемся на начало и ожидаем, че уж там
program.returnToBase()












