local Config = require('opus.config')
local Event  = require('opus.event')
local Socket = require('opus.socket')
local UI     = require('opus.ui')
local Util   = require('opus.util')

local device     = _G.device
local network    = _G.network
local os         = _G.os
local shell      = _ENV.shell

UI:configure('Network', ...)

local gridColumns = {
	{ heading = 'Label',  key = 'label'    },
	{ heading = 'Dist',   key = 'distance', align = 'right' },
	{ heading = 'Status', key = 'status'   },
}

local config = Config.load('network', { })

if UI.term.width >= 30 then
	table.insert(gridColumns, { heading = 'Fuel',   key = 'fuel', width = 5, align = 'right' })
end
if UI.term.width >= 40 then
	table.insert(gridColumns, { heading = 'Uptime', key = 'uptime', align = 'right' })
end

local page = UI.Page {
	menuBar = UI.MenuBar {
		buttons = {
			{ text = 'Connect', dropdown = {
				{ text = 'Telnet      t', event = 'telnet' },
				{ text = 'VNC         v', event = 'vnc'    },
				{ spacer = true },
				{ text = 'Reboot      r', event = 'reboot' },
			} },
			{ text = 'Trust', dropdown = {
				{ text = 'Establish', event = 'trust'   },
			} },
			{
				text = '\187',
				x = -3,
				dropdown = {
					{ text = 'Port Status', event = 'ports', modem = true },
					{ spacer = true },
					{ text = 'Help', event = 'help', noCheck = true },
				},
			},
		},
	},
	grid = UI.ScrollingGrid {
		y = 2,
		values = network,
		columns = gridColumns,
		sortColumn = 'label',
		autospace = true,
		getRowTextColor = function(self, row, selected)
			if not row.active then
				return 'lightGray'
			end
			return UI.Grid.getRowTextColor(self, row, selected)
		end,
		getDisplayValues = function(_, row)
			row = Util.shallowCopy(row)
			if row.uptime then
				if row.uptime < 60 then
					row.uptime = string.format("%ds", math.floor(row.uptime))
				elseif row.uptime < 3600 then
					row.uptime = string.format("%sm", math.floor(row.uptime / 60))
				else
					row.uptime = string.format("%sh", math.floor(row.uptime / 3600))
				end
			end
			if row.fuel then
				row.fuel = row.fuel > 0 and Util.toBytes(row.fuel) or ''
			end
			if row.distance then
				row.distance = Util.toBytes(Util.round(row.distance, 1))
			end
			return row
		end,
	},
	ports = UI.SlideOut {
		titleBar = UI.TitleBar {
			title = 'Ports',
			event = 'ports_hide',
		},
		grid = UI.ScrollingGrid {
			y = 2,
			columns = {
				{ heading = 'Port',       key = 'port'       },
				{ heading = 'State',      key = 'state'      },
				{ heading = 'Connection', key = 'connection' },
			},
			sortColumn = 'port',
			autospace = true,
		},
		eventHandler = function(self, event)
			if event.type == 'grid_select' then
				shell.openForegroundTab('Sniff ' .. event.selected.port)
			end
			return UI.SlideOut.eventHandler(self, event)
		end,
	},
	help = UI.SlideOut {
		x = 5, ex = -5, height = 8, y = -8,
		titleBar = UI.TitleBar {
			title = 'Network Help',
			event = 'slide_hide',
		},
		text = UI.TextArea {
			x = 1, y = 2,
			marginLeft = 1,
			value = [[

In order to connect to another computer:

1. The target computer must have a password set (run 'password' from the shell prompt).

2. From this computer, click trust and enter the password for that computer.

This only needs to be done once.
			]],
		},
		accelerators = {
			q = 'slide_hide',
		}
	},
	notification = UI.Notification { },
	accelerators = {
		t = 'telnet',
		v = 'vnc',
		r = 'reboot',
		[ 'control-q' ] = 'quit',
		c = 'clear',
	},
}

local function sendCommand(host, command)
	if not device.wireless_modem then
		page.notification:error('Wireless modem not present')
		return
	end

	page.notification:info('Connecting')
	page:sync()

	local socket = Socket.connect(host, 161)
	if socket then
		socket:write({ type = command })
		socket:close()
		page.notification:success('Command sent')
	else
		page.notification:error('Failed to connect')
	end
end

function page.ports.grid:update()
	local transport = network:getTransport()

	local function findConnection(port)
		if transport then
			for _,socket in pairs(transport.sockets) do
				if socket.sport == port then
					return socket
				end
			end
		end
	end

	local connections = { }

	pcall(function() -- guard against modem removal
		if device.wireless_modem then
			for i = 0, 65535 do
				if device.wireless_modem.isOpen(i) then
					local conn = {
						port = i
					}
					local socket = findConnection(i)
					if socket then
						conn.state = 'CONNECTED'
						local host = socket.dhost
						if network[host] then
							host = network[host].label
						end
						conn.connection = host .. ':' .. socket.dport
					else
						conn.state = 'LISTEN'
					end
					table.insert(connections, conn)
				end
			end
		end
	end)

	self.values = connections
	UI.Grid.update(self)
end

function page:eventHandler(event)
	local t = self.grid:getSelected()
	if t then
		if event.type == 'telnet' then
			shell.openForegroundTab('telnet ' .. t.id)

		elseif event.type == 'vnc' then
			shell.openForegroundTab('vnc.lua ' .. t.id)
			os.queueEvent('overview_shortcut', {
				title = t.label,
				category = "VNC",
				icon = "\010\030 \009\009\031e\\\031   \031e/\031dn\010\030 \009\009 \031e\\/\031  \031bc",
				run = "vnc.lua " .. t.id,
			})

		elseif event.type == 'clear' then
			Util.clear(network)
			page.grid:update()
			page.grid:draw()

		elseif event.type == 'trust' then
			shell.openForegroundTab('trust ' .. t.id)

		elseif event.type == 'reboot' then
			sendCommand(t.id, 'reboot')

		elseif event.type == 'shutdown' then
			sendCommand(t.id, 'shutdown')
		end
	end

	if event.type == 'help' then
		self.help:show()

	elseif event.type == 'ports' then
		self.ports.grid:update()
		self.ports:show()

		self.portsHandler = Event.onInterval(3, function()
			self.ports.grid:update()
			self.ports.grid:draw()
			self:sync()
		end)

	elseif event.type == 'ports_hide' then
		Event.off(self.portsHandler)
		self.ports:hide()

	elseif event.type == 'show_trusted' then
		config.showTrusted = true
		Config.update('network', config)

	elseif event.type == 'quit' then
		UI:quit()
	end
	UI.Page.eventHandler(self, event)
end

function page.menuBar:getActive(menuItem)
	local t = page.grid:getSelected()
	if menuItem.modem then
		return not not device.wireless_modem
	end
	return menuItem.noCheck or not not t
end

Event.onInterval(1, function()
	page.grid:update()
	page.grid:draw()
	page:sync()
end)

Event.on('device_attach', function(_, deviceName)
	if deviceName == 'wireless_modem' then
		page.notification:success('Modem connected')
		page:sync()
	end
end)

Event.on('device_detach', function(_, deviceName)
	if deviceName == 'wireless_modem' then
		page.notification:error('Wireless modem not attached')
		page:sync()
	end
end)

if not device.wireless_modem then
	page.notification:error('Wireless modem not attached')
end

UI:setPage(page)
UI:start()