
local component = require("component")
local MineOSCore = require("MineOSCore")
local computer = require("computer")
local event = require("event")
local filesystemComponent = require("component").proxy(computer.getBootAddress())
local filesystemLibrary = require("filesystem")

-- Ебучие херолизы, с каких залупнинских хуев я должен учитывать их говнокод и синтаксические ошибки?
-- GGWP
if not filesystemLibrary.unmount and filesystemLibrary.umount then
	filesystemLibrary.unmount = filesystemLibrary.umount
end

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

local MineOSNetwork = {}

MineOSNetwork.modemPort = 1488
MineOSNetwork.modemProxy = nil
MineOSNetwork.modemPacketReserve = 128
MineOSNetwork.timeout = 2
MineOSNetwork.filesystemHandles = {}
MineOSNetwork.mountPath = "/ftp/"

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

function MineOSNetwork.getProxyName(proxy)
	return proxy.name and proxy.name .. " (" .. proxy.address .. ")" or proxy.address
end

function MineOSNetwork.getProxy(address)
	for proxy, path in filesystemLibrary.mounts() do
		if proxy.network and proxy.address == address then
			return proxy
		end
	end
end

function MineOSNetwork.getProxyCount()
	local count = 0
	for proxy, path in filesystemLibrary.mounts() do
		if proxy.network then
			count = count + 1
		end
	end

	return count
end

function MineOSNetwork.unmountAll()
	for proxy in filesystemLibrary.mounts() do
		if proxy.network then
			filesystemLibrary.unmount(proxy)
		end
	end
end

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

function MineOSNetwork.sendMessage(address, ...)
	if MineOSNetwork.modemProxy then
		return MineOSNetwork.modemProxy.send(address, MineOSNetwork.modemPort, ...)
	else
		MineOSNetwork.modemProxy = nil
		return false, "Modem component is not available"
	end
end

function MineOSNetwork.broadcastMessage(...)
	if MineOSNetwork.modemProxy then
		return MineOSNetwork.modemProxy.broadcast(MineOSNetwork.modemPort, ...)
	else
		MineOSNetwork.modemProxy = nil
		return false, "Modem component is not available"
	end
end

function MineOSNetwork.setSignalStrength(strength)
	if MineOSNetwork.modemProxy then
		if MineOSNetwork.modemProxy.isWireless() then
			return MineOSNetwork.modemProxy.setStrength(strength)
		else
			return false, "Modem component is not wireless"
		end
	else
		MineOSNetwork.modemProxy = nil
		return false, "Modem component is not available"
	end
end

function MineOSNetwork.updateModemState()
	if component.isAvailable("modem") then
		MineOSNetwork.modemProxy = component.proxy(component.list("modem")())
		MineOSNetwork.modemProxy.open(MineOSNetwork.modemPort)
		
		return true
	else
		MineOSNetwork.modemProxy = nil
		MineOSNetwork.unmountAll()

		return false, "Modem component is not available"
	end
end

function MineOSNetwork.broadcastComputerState(state)
	return MineOSNetwork.broadcastMessage("MineOSNetwork", state and "computerAvailable" or "computerNotAvailable", MineOSCore.properties.network.name)
end

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

local function newFilesystemProxy(address)
	local function request(method, returnOnFailure, ...)
		MineOSNetwork.sendMessage(address, "MineOSNetwork", "request", method, ...)
		
		while true do
			local eventData = { event.pull(MineOSNetwork.timeout, "modem_message") }
			
			if eventData[3] == address and eventData[6] == "MineOSNetwork" then
				if eventData[7] == "response" and eventData[8] == method then
					return table.unpack(eventData, 9)
				elseif eventData[7] == "accessDenied" then
					computer.pushSignal("MineOSNetwork", "accessDenied", address)
					return returnOnFailure, "Access denied"
				end
			elseif not eventData[1] then
				local proxy = MineOSNetwork.getProxy(address)
				if proxy then
					filesystemLibrary.unmount(proxy)
				end

				computer.pushSignal("MineOSNetwork", "timeout")

				return returnOnFailure, "Network filesystem timeout"
			end
		end
	end

	return {
		type = "filesystem",
		address = address,
		slot = 0,
		network = true,
		
		getLabel = function()
			return request("getLabel", "N/A")
		end,

		isReadOnly = function()
			return request("isReadOnly", "N/A")
		end,

		spaceUsed = function()
			return request("spaceUsed", "N/A")
		end,

		spaceTotal = function()
			return request("spaceTotal", "N/A")
		end,

		exists = function(path)
			return request("exists", false, path)
		end,

		isDirectory = function(path)
			return request("isDirectory", false, path)
		end,

		makeDirectory = function(path)
			return request("makeDirectory", false, path)
		end,

		setLabel = function(name)
			return request("setLabel", false, name)
		end,

		remove = function(path)
			return request("remove", false, path)
		end,

		lastModified = function(path)
			return request("lastModified", 0, path)
		end,

		size = function(path)
			return request("size", 0, path)
		end,

		list = function(path)
			return table.fromString(request("list", "{}", path))
		end,

		seek = function(handle, whence, offset)
			return request("seek", 0, handle, whence, offset)
		end,

		open = function(path, mode)
			return request("open", false, path, mode)
		end,

		close = function(handle)
			return request("close", false, handle)
		end,

		read = function(handle, count)
			return request("read", "", handle, count)
		end,

		write = function(handle, data)
			local maxPacketSize = MineOSNetwork.modemProxy.maxPacketSize() - MineOSNetwork.modemPacketReserve
			repeat
				if not request("write", false, handle, data:sub(1, maxPacketSize)) then
					return false
				end
				data = data:sub(maxPacketSize + 1)
			until #data == 0

			return true
		end,

		rename = function(from, to)
			local proxyFrom = filesystemLibrary.get(from)
			local proxyTo = filesystemLibrary.get(to)

			if proxyFrom.network or proxyTo.network then
				local success, handleFrom, handleTo, data, reason = true
				
				handleFrom, reason = proxyFrom.open(from, "rb")
				if handleFrom then
					handleTo, reason = proxyTo.open(to, "wb")
					if handleTo then
						while true do
							data, readReason = proxyFrom.read(handleFrom, 1024)
							if data then
								success, reason = proxyTo.write(handleTo, data)
								if not success then
									break
								end
							else
								success = false
								break
							end
						end

						proxyFrom.close(handleTo)
					else
						success = false
					end

					proxyFrom.close(handleFrom)
				else
					success = false
				end

				if success then
					success, reason = proxyFrom.remove(from)
				end

				return success, reason
			else
				return request("rename", false, from, to)
			end
		end,
	}
end

local exceptionMethods = {
	getLabel = function()
		return MineOSCore.properties.network.name or MineOSNetwork.modemProxy.address
	end,

	list = function(path)
		return table.toString(filesystemComponent.list(path))
	end,

	open = function(path, mode)
		local ID
		while not ID do
			ID = math.random(1, 0x7FFFFFFF)
			for handleID in pairs(MineOSNetwork.filesystemHandles) do
				if handleID == ID then
					ID = nil
				end
			end
		end

		MineOSNetwork.filesystemHandles[ID] = filesystemComponent.open(path, mode)
		
		return ID
	end,

	close = function(ID)
		local data, reason = filesystemComponent.close(MineOSNetwork.filesystemHandles[ID])
		MineOSNetwork.filesystemHandles[ID] = nil
		return data, reason
	end,

	read = function(ID, ...)
		return filesystemComponent.read(MineOSNetwork.filesystemHandles[ID], ...)
	end,

	write = function(ID, ...)
		return filesystemComponent.write(MineOSNetwork.filesystemHandles[ID], ...)
	end,

	seek = function(ID, ...)
		return filesystemComponent.seek(MineOSNetwork.filesystemHandles[ID], ...)
	end,
}

local function handleRequest(eventData)
	-- print("REQ", table.unpack(eventData, 6))
	
	if MineOSCore.properties.network.users[eventData[3]].allowReadAndWrite then
		local result = { pcall(exceptionMethods[eventData[8]] or filesystemComponent[eventData[8]], table.unpack(eventData, 9)) }
		if result[1] then
			MineOSNetwork.sendMessage(eventData[3], "MineOSNetwork", "response", eventData[8], table.unpack(result, 2))
		else
			MineOSNetwork.sendMessage(eventData[3], "MineOSNetwork", "response", eventData[8], result[1], result[2])
		end
	else
		MineOSNetwork.sendMessage(eventData[3], "MineOSNetwork", "accessDenied")
	end
end

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

function MineOSNetwork.disable()
	if MineOSNetwork.eventHandler then
		event.cancel(MineOSNetwork.eventHandler.ID)
	end
	MineOSNetwork.unmountAll()
end

function MineOSNetwork.enable()
	MineOSNetwork.disable()

	MineOSNetwork.eventHandler = event.register(function(...)
		local eventData = {...}
		if eventData[1] == "component_added" or eventData[1] == "component_removed" then
			MineOSNetwork.updateModemState()
		elseif eventData[1] == "modem_message" and MineOSCore.properties.network.enabled and eventData[6] == "MineOSNetwork" then
			if eventData[7] == "request" then
				handleRequest(eventData)
			elseif eventData[7] == "computerAvailable" or eventData[7] == "computerAvailableRedirect" then
				for proxy in filesystemLibrary.mounts() do
					if proxy.network and proxy.address == eventData[3] then
						filesystemLibrary.unmount(proxy)
					end
				end

				proxy = newFilesystemProxy(eventData[3])
				proxy.name = eventData[8]
				filesystemLibrary.mount(proxy, MineOSNetwork.mountPath .. eventData[3]:sub(1, 3) .. "/")

				if eventData[7] == "computerAvailable" then
					MineOSNetwork.sendMessage(eventData[3], "MineOSNetwork", "computerAvailableRedirect", MineOSCore.properties.network.name)
				end

				if not MineOSCore.properties.network.users[eventData[3]] then
					MineOSCore.properties.network.users[eventData[3]] = {}
					MineOSCore.saveProperties()
				end

				computer.pushSignal("MineOSNetwork", "updateProxyList")
			elseif eventData[7] == "computerNotAvailable" then
				local proxy = MineOSNetwork.getProxy(eventData[3])
				if proxy then
					filesystemLibrary.unmount(proxy)
				end

				computer.pushSignal("MineOSNetwork", "updateProxyList")
			elseif eventData[7] == "message" and MineOSCore.properties.network.users[eventData[3]].allowMessages then
				computer.pushSignal("MineOSNetwork", "message", eventData[3], eventData[8])
			end
		end
	end)
end

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

if not MineOSCore.properties.network then
	MineOSCore.properties.network = {
		users = {},
		enabled = true,
		signalStrength = 512,
	}
	MineOSCore.saveProperties()
end

MineOSNetwork.updateModemState()

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

return MineOSNetwork





