------------------------------------------------------------------------
------------------------------------------------------------------------
-- FAR -----------------------------------------------------------------
------------------------------------------------------------------------
------------------------------------------------------------------------
local global_timeout = 1
local config_file_path = "/.settings"
local primary_color = colors.yellow

local connection
local server_running = false
local send_screen_next = false
local last_sent_messages = 0
local far_version = "0.9.4"

------------------------------------------------------------------------
-- SHELL COMPLETIONS ---------------------------------------------------
------------------------------------------------------------------------

local function tabCompletionFunction( shell, parNumber, curText )
	local ops = { "server", "client", "password", "version" }
	local results = {}
	if parNumber == 1 then
		for i=1, #ops do
			local word = ops[i]
			if word:sub(1, #curText) == curText then
				results[#results + 1] = word:sub(#curText + 1)
			end
		end
	end
	return results
end

shell.setCompletionFunction("far", tabCompletionFunction)

------------------------------------------------------------------------
-- UTILITY FUNCTIONS ---------------------------------------------------
------------------------------------------------------------------------

local function wrap(f, ...)
	local args = { ... }
	return function() f(unpack(args)) end
end

local function shallowCompare( t1, t2 )
	for k, v in pairs( t1 ) do
		if t2[k] ~= t2[v] then
			return false
		end
	end
	return true
end

local function printPrimary( ... )
	if term.isColor() then
		local oldColor = term.getTextColor()
		term.setTextColor( primary_color )
		print( ... )
		term.setTextColor( oldColor )
	else
		print( ... )
	end
end

local function printCentered( text, line )
	local w, h = term.getSize()
	term.setCursorPos( math.floor( ( w - text:len() ) / 2 ) + 1, line )
	print( text )
end

-- Like parallel.waitForAny() except it only forwards terminate events to function1
local function waitForAny( eventPassthrough, ... )
	local functions = { ... }

	local coroutines = {}
	
	for i = 1, #functions do
		if type( functions[i] ) ~= "function" then
			error( "Expected function, got "..type( functions[i] ), 3 )
		end
		table.insert( coroutines, coroutine.create( functions[i] ) )
	end

	local eventData = {}
	local filters = {}

	while true do
		for i = 1, #coroutines do
			local r = coroutines[i]
			if coroutine.status( r ) == "dead" then
				return
			end
			local ok, err
			local ePass
			if type( eventPassthrough[i] ) == "boolean" then
				ePass = eventPassthrough[i]
			elseif type( eventPassthrough[i] ) == "function" then
				ePass = eventPassthrough[i]()
			end
			if ePass or eventData[1] ~= "terminate" then
				ok, err = coroutine.resume( r, unpack( eventData ) )
			else
				ok, err = coroutine.resume( r )
			end
			if not ok then
				error( param, 0 )
			end
		end
		eventData = { os.pullEventRaw() }
	end
end

------------------------------------------------------------------------
-- PASSWORD HASHING ----------------------------------------------------
------------------------------------------------------------------------

-- A HUGE thanks to GravityScore for this!

--  
--  Adaptation of the Secure Hashing Algorithm (SHA-244/256)
--  Found Here: http://lua-users.org/wiki/SecureHashAlgorithm
--  
--  Using an adapted version of the bit library
--  Found Here: https://bitbucket.org/Boolsheet/bslf/src/1ee664885805/bit.lua
--  
 
local MOD = 2^32
local MODM = MOD-1
 
local function memoize(f)
		local mt = {}
		local t = setmetatable({}, mt)
		function mt:__index(k)
				local v = f(k)
				t[k] = v
				return v
		end
		return t
end
 
local function make_bitop_uncached(t, m)
		local function bitop(a, b)
				local res,p = 0,1
				while a ~= 0 and b ~= 0 do
						local am, bm = a % m, b % m
						res = res + t[am][bm] * p
						a = (a - am) / m
						b = (b - bm) / m
						p = p*m
				end
				res = res + (a + b) * p
				return res
		end
		return bitop
end
 
local function make_bitop(t)
		local op1 = make_bitop_uncached(t,2^1)
		local op2 = memoize(function(a) return memoize(function(b) return op1(a, b) end) end)
		return make_bitop_uncached(op2, 2 ^ (t.n or 1))
end
 
local bxor1 = make_bitop({[0] = {[0] = 0,[1] = 1}, [1] = {[0] = 1, [1] = 0}, n = 4})
 
local function bxor(a, b, c, ...)
		local z = nil
		if b then
				a = a % MOD
				b = b % MOD
				z = bxor1(a, b)
				if c then z = bxor(z, c, ...) end
				return z
		elseif a then return a % MOD
		else return 0 end
end
 
local function band(a, b, c, ...)
		local z
		if b then
				a = a % MOD
				b = b % MOD
				z = ((a + b) - bxor1(a,b)) / 2
				if c then z = bit32_band(z, c, ...) end
				return z
		elseif a then return a % MOD
		else return MODM end
end
 
local function bnot(x) return (-1 - x) % MOD end
 
local function rshift1(a, disp)
		if disp < 0 then return lshift(a,-disp) end
		return math.floor(a % 2 ^ 32 / 2 ^ disp)
end
 
local function rshift(x, disp)
		if disp > 31 or disp < -31 then return 0 end
		return rshift1(x % MOD, disp)
end
 
local function lshift(a, disp)
		if disp < 0 then return rshift(a,-disp) end
		return (a * 2 ^ disp) % 2 ^ 32
end
 
local function rrotate(x, disp)
	x = x % MOD
	disp = disp % 32
	local low = band(x, 2 ^ disp - 1)
	return rshift(x, disp) + lshift(low, 32 - disp)
end
 
local k = {
		0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
		0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
		0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
		0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
		0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
		0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
		0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
		0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
		0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
		0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
		0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
		0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
		0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
		0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
		0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
		0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
}
 
local function str2hexa(s)
		return (string.gsub(s, ".", function(c) return string.format("%02x", string.byte(c)) end))
end
 
local function num2s(l, n)
		local s = ""
		for i = 1, n do
				local rem = l % 256
				s = string.char(rem) .. s
				l = (l - rem) / 256
		end
		return s
end
 
local function s232num(s, i)
		local n = 0
		for i = i, i + 3 do n = n*256 + string.byte(s, i) end
		return n
end
 
local function preproc(msg, len)
		local extra = 64 - ((len + 9) % 64)
		len = num2s(8 * len, 8)
		msg = msg .. "\128" .. string.rep("\0", extra) .. len
		assert(#msg % 64 == 0)
		return msg
end
 
local function initH256(H)
		H[1] = 0x6a09e667
		H[2] = 0xbb67ae85
		H[3] = 0x3c6ef372
		H[4] = 0xa54ff53a
		H[5] = 0x510e527f
		H[6] = 0x9b05688c
		H[7] = 0x1f83d9ab
		H[8] = 0x5be0cd19
		return H
end
 
local function digestblock(msg, i, H)
		local w = {}
		for j = 1, 16 do w[j] = s232num(msg, i + (j - 1)*4) end
		for j = 17, 64 do
				local v = w[j - 15]
				local s0 = bxor(rrotate(v, 7), rrotate(v, 18), rshift(v, 3))
				v = w[j - 2]
				w[j] = w[j - 16] + s0 + w[j - 7] + bxor(rrotate(v, 17), rrotate(v, 19), rshift(v, 10))
		end
 
		local a, b, c, d, e, f, g, h = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8]
		for i = 1, 64 do
				local s0 = bxor(rrotate(a, 2), rrotate(a, 13), rrotate(a, 22))
				local maj = bxor(band(a, b), band(a, c), band(b, c))
				local t2 = s0 + maj
				local s1 = bxor(rrotate(e, 6), rrotate(e, 11), rrotate(e, 25))
				local ch = bxor (band(e, f), band(bnot(e), g))
				local t1 = h + s1 + ch + k[i] + w[i]
				h, g, f, e, d, c, b, a = g, f, e, d + t1, c, b, a, t1 + t2
		end
 
		H[1] = band(H[1] + a)
		H[2] = band(H[2] + b)
		H[3] = band(H[3] + c)
		H[4] = band(H[4] + d)
		H[5] = band(H[5] + e)
		H[6] = band(H[6] + f)
		H[7] = band(H[7] + g)
		H[8] = band(H[8] + h)
end
 
local function sha256(msg)
		msg = preproc(msg, #msg)
		local H = initH256({})
		for i = 1, #msg, 64 do digestblock(msg, i, H) end
		return str2hexa(num2s(H[1], 4) .. num2s(H[2], 4) .. num2s(H[3], 4) .. num2s(H[4], 4) ..
				num2s(H[5], 4) .. num2s(H[6], 4) .. num2s(H[7], 4) .. num2s(H[8], 4))
end

------------------------------------------------------------------------
-- PASSWORD CHANGING ---------------------------------------------------
------------------------------------------------------------------------

local function changePassword()
	write("Enter a password: ")
	local passwd1 = read("*")
	write("Confirm password: ")
	local passwd2 = read("*")
	if passwd2 ~= passwd1 then
		printError("Passwords do not match!")
	else
		settings.set( "far.password", sha256( passwd2 ) )
		settings.save( config_file_path )
		printPrimary("Password changed.")
	end
end

------------------------------------------------------------------------
-- PERIPHERAL FUNCTIONS ------------------------------------------------
------------------------------------------------------------------------

local function findAndOpenPeripheral()
	local sides = {
		"front",
		"back",
		"top",
		"bottom",
		"left",
		"right"
	}
	for i = 1, #sides do
		local side = sides[i]
		if peripheral.isPresent( side ) and peripheral.getType( side ) == "modem" then
			rednet.open( side )
			return true
		end
	end
	printError( "Error: Could not find a modem." )
	return false
end

------------------------------------------------------------------------
-- SERVER FUNCTIONS ----------------------------------------------------
------------------------------------------------------------------------

local function waitForClientMessage()
	local timer = os.startTimer( global_timeout )
	while true do
		local event, senderId, message, protocol = os.pullEvent()
		if event == "rednet_message" then
			if senderId == connection.client_id and protocol == "far" then
				return message
			else
				rednet.send(
					senderId,
					{ message = "err", error_message = "Server busy: Another client is currently connected." },
					"far"
				)
			end
		elseif event == "timer" and senderId == timer then
			return
		end
	end
end

local function sendClientMessage( type, message )
	message.a = type
	rednet.send(
		connection.client_id,
		message,
		"far"
	)
end

local function handleNewConnection( senderId, message )
	connection = {}
	connection.client_id = senderId
	--Check the password with the one in settings
	settings.load( config_file_path )
	if settings.get( "far.password" ) == message.passwd then
		--Check if the client is compatible
		if term.current().isColor() and not message.use_color then
			rednet.send(
				senderId,
				{ message = "err", error_message = "This computer uses color, you need a color monitor to connect." },
				"far"
			)
		else
			connection.auth = true
			connection.use_color = term.current().isColor()
			connection.screen_updates = {}
			local w, h = term.getSize()
			rednet.send(
				senderId,
				{ message = "ok:welcome", screen_w = w, screen_h = h },
				"far"
			)
			return true --Otherwise the session will be discarded
		end
	else
		rednet.send(
			senderId,
			{ message = "err", error_message = "Incorrect password." },
			"far"
		)
	end
end

local function addScreenUpdate( contents )
	if send_screen_next and not connection.screen_updates[1] then
		send_screen_next = false
		sendClientMessage( 1, { m = { contents } } )
	else
		table.insert( connection.screen_updates, contents )
	end
end

local function runSession( rterm )
	local oldterm = term.current()
	term.redirect( rterm )
	term.clear()
	term.setCursorPos( 1, 1 )
	local function handleMessages()
		local timed_out = 0
		while server_running do
			local message = waitForClientMessage()
			if message and type( message ) == "table" and message.a then
				timed_out = 0
				if message.a == 1 then
					if message.t == 0 then
						if last_sent_messages > 0 then
							for i = 1, last_sent_messages do
								table.remove( connection.screen_updates, 1 )
							end
							last_sent_messages = 0
						else
							sendClientMessage( 3, { t = 8 } )
						end
						send_screen_next = true
					end
				elseif message.a == 2 then
					os.queueEvent( unpack( message ) )
				elseif message.a == 3 then
					if message.t == 1 then
						break
					end
				end
			else
				if timed_out > 3 then
					sendClientMessage( 3, { t = 2 } )
					break
				else
					timed_out = timed_out + 1
				end
			end
		end
	end
	local function runScreenBuffer()
		if send_screen_next and #connection.screen_updates ~= 0 then
			last_sent_messages = #connection.screen_updates
			sendClientMessage( 1, { m = connection.screen_updates } )
			send_screen_next = false
		end
		coroutine.yield()
	end
	local function handleScreenBuffer()
		while server_running do
			runScreenBuffer()
		end
	end
	
	local ok, err = pcall( waitForAny, { true }, wrap( shell.run, "/rom/programs/shell" ), handleMessages, handleScreenBuffer )
	sendClientMessage( 3, { t = 1 } )
	connection = nil
	term.redirect( oldterm )
	if not ok then
		printError( "An error occurred, and the connection was lost.")
		printError(": " .. err)
	end
end

local function runServer( getRTerm )
	server_running = true
	local function postClear()
		local w, h = term.getSize()
		if term.isColor() then term.setBackgroundColor( colors.blue ) else term.setBackgroundColor( colors.black ) end
		term.clear()
		term.setTextColor( colors.white )
		term.setCursorBlink( false )
		local lines
		if w >= 45 and h >=9 then
			local xline = "computer and run \"far client "..os.getComputerID().."\""
			local pnum = math.ceil((43 - xline:len()) / 2)
			lines = {
				"+---------------- [ F A R ] ----------------+",
				"|           No client is connected.         |",
				"|     To connect, download far on another   |",
				"|"..string.rep(" ", math.ceil((43 - xline:len()) / 2) + 1)..xline..string.rep(" ", 45 - (math.ceil((43 - xline:len()) / 2) + 3) - xline:len()).."|",
				"+-------------------------------------------+",
			}
			for i = 1, #lines - 1 do
				table.insert( lines, i*2, "|                                           |" )
			end
		else
			lines = {
				"\187 FAR \171",
				"No client is connected"
			}
		end
		local startLine = math.floor( ( h - #lines ) / 2 )
		for i = 1, #lines do
			printCentered( lines[i], startLine + i )
		end
		term.setBackgroundColor( colors.black )
	end
	postClear()
	while server_running do
		--Wait for a new connection
		local senderId, message, protocol = rednet.receive( "far", global_timeout )
		if senderId then
			if type( message ) == "table" and message.message == "init_connection" then
				--Handle the new connection. If it fails, reset.
				if not handleNewConnection( senderId, message ) then
					connection = nil
				else
					runSession( getRTerm() )
					postClear()
				end
			end
		end
	end
end
------------------------------------------------------------------------
-- CLIENT FUNCTIONS ----------------------------------------------------
------------------------------------------------------------------------

local hScroll
local vScroll
local old_term
local parentWindow

local function setupWindow()
	local w, h = term.getSize()
	if connection.screen_w < w then
		hScroll = math.ceil(( connection.screen_w - w ) / 2 )
	else
		hScroll = 0
	end
	if connection.screen_h < h then
		vScroll = math.ceil(( connection.screen_h - h ) / 2 )
	else
		vScroll = 0
	end
	old_term = term.current()
	parentWindow = window.create( old_term, 1 - hScroll, 1 - vScroll, connection.screen_w, connection.screen_h, true )
	term.redirect( parentWindow )
end

local function rectifyBoundaries()
	local w, h = old_term.getSize()
	if hScroll > connection.screen_w - w then
		hScroll = connection.screen_w - w
	elseif hScroll < 0 then
		hScroll = 0
	end
	if vScroll > connection.screen_h - h then
		vScroll = connection.screen_h - h
	elseif vScroll < 0 then
		vScroll = 0
	end

end

local function scrollWindow( x, y )
	if hScroll < 0 then x = 0 end
	if vScroll < 0 then y = 0 end
	if x == 0 and y == 0 then return end
	hScroll = hScroll + x
	vScroll = vScroll + y
	rectifyBoundaries()
	parentWindow.reposition( 1 - hScroll, 1 - vScroll )
end

local function waitForServerMessage()
	local timer = os.startTimer( global_timeout )
	while true do
		local event, senderId, message, protocol = os.pullEvent()
		if event == "rednet_message" then
			if senderId == connection.server_id and protocol == "far" then
				return message
			end
		elseif event == "timer" and senderId == timer then
			return
		end
	end
end

local function sendServerMessage( type, message )
	message.a = type
	rednet.send(
		connection.server_id,
		message,
		"far"
	)
end

local function disconnected( message )
	connection = nil
	term.setCursorBlink( false )
	term.redirect( old_term )
	tDisp = nil
	term.clear()
	term.setCursorPos( 1, 1 )
	printPrimary( message )
end

local function disconnect( message )
	sendServerMessage( 3, { t = 1 } )
	disconnected( message )
end

local function runMessages()
	local color = term.isColor()
	local timed_out = 0
	while connection do
		local message = waitForServerMessage()
		if message then
			timed_out = 0
			if message.a == 1 then -- Term update
				for i = 1, #message.m do
					local message = message.m[i]
					if message.t == 1 then
						if color then term.setBackgroundColor( message.c ) end
					elseif message.t == 2 then
						if color then term.setTextColor( message.c ) end
					elseif message.t == 3 then
						term.setCursorBlink( message.b )
					elseif message.t == 4 then
						term.setCursorPos( message.x, message.y )
					elseif message.t == 5 then
						term.blit( message.tx, message.f, message.b )
					elseif message.t == 6 then
						term.write( message.s )
					elseif message.t == 7 then
						term.clearLine()
					elseif message.t == 8 then
						term.scroll( message.n )
					elseif message.t == 9 then
						term.clear()
					end
				end
				sendServerMessage( 1, { t = 0 } )
			elseif message.a == 3 then
				if message.t == 1 then
					return disconnected( "Disconnected from server." )
				elseif message.t == 2 then
					return disconnected( "Connection closed. (Server Error)" )
				end
			end
		else
			if timed_out < 3 then
				timed_out = timed_out + 1
			else
				sendServerMessage( 3, { t = 1 } )
				return disconnected( "Connection to server lost." )
			end
			sendServerMessage( 1, { t = 0 } )
		end
	end
end

local function runEvents()
	local lAltPressed = false
	local lShiftPressed = false
	local rAltPressed = false
	local rShiftPressed = false

	local forwardEvents = {
		key = true,
		char = true,
		key_up = true,
		paste = true,
		mouse_click = true,
		mouse_up = true,
		mouse_scroll = true,
		mouse_drag = true,
		monitor_touch = true,
		monitor_resize = true,

		terminate = true
	}

	local translateEvents = {
		mouse_click = true,
		mouse_up = true,
		mouse_scroll = true,
		mouse_drag = true,
		monitor_touch = true
	}
	while connection do
		local eventData = { os.pullEventRaw() }
		local ignoreEvent = false
		if forwardEvents[eventData[1]] then
			if translateEvents[eventData[1]] then
				eventData[3] = eventData[3] + hScroll
				eventData[4] = eventData[4] + vScroll
			elseif eventData[1] == "key" then
				if eventData[2] == 42 then lShiftPressed = true end
				if eventData[2] == 54 then rShiftPressed = true end
				if eventData[2] == 56 then lAltPressed = true end
				if eventData[2] == 184 then rAltPressed = true end
				if (lShiftPressed or rShiftPressed) and (lAltPressed or rAltPressed) then
					local x = 0
					local y = 0
					if eventData[2] == 203 then
						x = x - 1
					end
					if eventData[2] == 205 then
						x = x + 1
					end
					if eventData[2] == 208 then
						y = y + 1
					end
					if eventData[2] == 200 then
						y = y - 1
					end
					scrollWindow( x, y )
					if not (x == 0 and y == 0) then
						ignoreEvent = true
					end
				end
			elseif eventData[1] == "key_up" then
				if eventData[2] == 42 then lShiftPressed = false end
				if eventData[2] == 54 then rShiftPressed = false end
				if eventData[2] == 29 then lAltPressed = false end
				if eventData[2] == 157 then rAltPressed = false end
			end
			if not ignoreEvent then
				sendServerMessage( 2, eventData )
			end
		end
	end
end

local function connectToServer( id )
	--Sanity checks
	local id = tonumber( id )
	if not id then
		printError( "Invalid host ID!" )
		return false
	end
	if id == os.getComputerID() then
		printError( "Server ID cannot be the same as the client ID!" )
		return false
	end
	--Ask for a password
	write("Password: ")
	local password = read("*")
	local password_hashed = sha256( password )
	--Initiate connection
	connection = {}
	connection.server_id = id
	rednet.send(
		id,
		{
			message = "init_connection",
			passwd = password_hashed,
			use_color = term.isColor(),
			far_version = far_version
		},
		"far"
	)
	local message
	local function loadingSpinner()
		local x, y = term.getCursorPos()
		local steps = { "-", "\\", "|", "/" }
		local i = 1
		while true do
			if i > #steps then
				i = 1
			end
			write("Connecting "..steps[i])
			term.setCursorPos( 1, y )
			sleep(0.1)
			term.clearLine()
			i = i + 1
		end
	end
	local function getResponse()
		message = waitForServerMessage()
	end
	parallel.waitForAny( getResponse, loadingSpinner )
	print("Connecting...")
	if (not message) or message.message == "err" then
		printError("Connection failed: "..((message and message.error_message) or "Timed out."))
		return false
	elseif message.message ~= "ok:welcome" then
		printError("Connection failed: Server sent unexpected message")
		return false
	end
	print( "Connected to server!" )
	connection.auth = true
	term.clear()
	term.setCursorPos( 1, 1 )
	connection.screen_w = message.screen_w
	connection.screen_h = message.screen_h
	setupWindow()
	sendServerMessage( 1, { t = 0 } )
	waitForAny(
		{ true }, 
		runEvents,
		runMessages
	)
end

------------------------------------------------------------------------
-- REDIRECTED TERMINAL -------------------------------------------------
------------------------------------------------------------------------
local cterm = term.current()

local function getRTerm()
	local term = term.current()
	local rterm = {}

	rterm.setBackgroundColor = function( color )
		term.setBackgroundColor( color )
		addScreenUpdate({
			t = 1,
			c = color
		})
	end

	rterm.setBackgroundColour = function( colour )
		rterm.setBackgroundColor( colour )
	end

	rterm.setTextColor = function( color )
		term.setTextColor( color )
		addScreenUpdate({
			t = 2,
			c = color
		})
	end

	rterm.setTextColour = function( colour )
		rterm.setTextColor( colour )
	end

	rterm.setCursorBlink = function( blink )
		term.setCursorBlink( blink )
		addScreenUpdate({
			t = 3,
			b = blink
		})
	end

	rterm.setCursorPos = function( x, y )
		term.setCursorPos( x, y )
		addScreenUpdate({
			t = 4,
			x = x,
			y = y
		})
	end

	rterm.blit = function( text, textColor, backgroundColor )
		term.blit( text, textColor, backgroundColor )
		addScreenUpdate({
			t = 5,
			tx = text,
			f = textColor,
			b = backgroundColor
		})
	end

	rterm.write = function( string )
		term.write( string )
		addScreenUpdate({
			t = 6,
			s = string
		})
	end

	rterm.clearLine = function()
		term.clearLine()
		addScreenUpdate({
			t = 7
		})
	end

	rterm.scroll = function( n )
		term.scroll( n )
		addScreenUpdate({
			t = 8,
			n = n
		})
	end

	rterm.clear = function()
		term.clear()
		addScreenUpdate({
			t = 9
		})
	end

	rterm.isColor = function()
		if connection then
			return connection.use_color
		else
			return term.isColor()
		end
	end

	rterm.isColour = function()
		return rterm.isColor()
	end

	for name, func in pairs( term ) do
		if type(func) == "function" then
			if not rterm[name] then
				rterm[name] = func
			end
		end
	end
	return rterm
end

------------------------------------------------------------------------
-- HELP / USAGE --------------------------------------------------------
------------------------------------------------------------------------

local function showVersionInfo()
	local w, h = term.getSize()
	if term.isColor() then term.setBackgroundColor( colors.blue ) else term.setBackgroundColor( colors.black ) end
	term.clear()
	term.setTextColor( colors.white )
	term.setCursorBlink( false )
	local lines
	if w >= 37 and h >=14 then
		lines = {
			"+-----------------------------------+",
			"|         FAR version "..far_version.."         |",
			"+-----------------------------------+",
			"| Credits / Thanks to:              |",
			"| \007 GravityScore for his sha256 lib |",
			"| \007 Viluon for LuaExtended and LECC |",
			"| \007 The people of oeed/Silica and \172 |",
			"|   oeed/CCJam-2016 for awesomeness |",
			"+-----------------------------------+",
			"| Made with \003 by InternetUnexplorer |",
			"|      for CCJam 2016 - \169 2016      |",
			"+-----------------------------------+",
			"Press a key to continue"
		}
		local startLine = math.floor( ( h - #lines ) / 2 )
		for i = 1, #lines do
			printCentered( lines[i], startLine + i )
		end
		os.pullEvent("key")
		os.pullEvent("char")
	else
		lines = {
			{
				"\187 FAR v"..far_version.." \171",
				"",
				"Made with \003 by",
				"InternetUnexplorer",
				"for CCJam 2016",
				"\169 2016"
			},
			{
				"\007 Thanks To \007",
				"",
				"GravityScore for",
				"his sha256 lib"
			},
			{
				"\007 Thanks To \007",
				"",
				"Viluon for",
				"LuaExtended & LECC"
			},
			{
				"\007 Thanks To \007",
				"",
				"The people of",
				"oeed/Silica and",
				"oeed/CCJam-2016",
				"for awesomeness"
			}
		}
		for i=1, #lines do
			local clines = lines[i]
			table.insert( clines, "" )
			table.insert( clines, "Press a key" )
			local startLine = math.floor( ( h - #clines ) / 2 )
			for i = 1, #clines do
				printCentered( clines[i], startLine + i )
			end
			os.pullEvent("key")
			os.pullEvent("char")
			term.clear()
		end
	end
	term.setBackgroundColor( colors.black )
	term.setCursorPos( 1, 1 )
	term.clear()
end

local function printUsage()
	local help = {
		["client_<ID>"] = "connects to server at <ID>",
		["server"] = "starts the server so other computers can connect",
		["password"] = "changes the password to connect to this computer",
		["version"] = "displays credits and version info",
	}
	local function pc()
		if term.isColor() then
			term.setTextColor( primary_color )
		else
			term.setTextColor( colors.gray )
		end
	end
	local function rc()
		term.setTextColor( colors.white )
	end
	print("Usage: ")
	for k, v in pairs( help ) do
		pc()
		term.write( "> " )
		rc()
		print( "far "..k:gsub("_", " ") )
		pc()
		term.write( " * " )
		rc()
		print( v )
	end
end

------------------------------------------------------------------------
-- PUBLIC STATIC VOID MAIN (Just Kidding) ------------------------------
------------------------------------------------------------------------

local args = {...}

if #args < 1 then
	printUsage()
else
	if args[1] == "client" then
		if #args < 2 then
			printError( "Missing Server ID" )
			printUsage()
		else
			if findAndOpenPeripheral() then
				connectToServer( args[2] )
			end
		end
	elseif args[1] == "server" then
		if findAndOpenPeripheral() then
			runServer( getRTerm )
		end
	elseif args[1] == "password" then
		changePassword()
	elseif args[1] == "version" then
		showVersionInfo()
	elseif args[1] ~= "help" then
		print( "Unknown argument '"..args[1].."'" )
		printUsage()
	end
end