 		message: @message
 		read: @read
 
+getMessageById = (id) ->
+	return msg for msg in messageRegistry when msg.id is id
+
+class Logger
+	Colors:
+		reset: `'\033[0m'`
+		red: `'\033[0;31m'`
+		green: `'\033[0;32m'`
+		yellow: `'\033[0;33m'`
+		blue: `'\033[0;34m'`
+
+	constructor: (@name, @includeTimeStamp=yes) -> @levels = []
+	log: (message, prefix='', suffix='', type='log') ->
+		console.log "#{prefix}#{if @includeTimeStamp then ("[" + new Date().toTimeString().substr 0,8 + "]") else ''}#{@name} #{type} -> #{level+" -> " for level in @levels}#{message}#{suffix}"
+	error: (message) -> @log message, @Colors.red, @Colors.reset, 'ERROR'
+	warn: (message) -> @log message, @Colors.yellow, @Colors.reset, 'WARN'
+	info: (message) -> @log message, @Colors.blue, @Colors.reset, 'info'
+	debug: (message) -> if GlobalDebug then @log message, @Colors.green, @Colors.reset, 'debug'
+	enter: (level) -> if not (level in @levels) then @levels.push level else @warn "logger was asked to enter existent level #{level}"
+	exit: (level) -> if (level in @levels) then @levels.pop level else @warn "logger was asked to exit nonexistent level #{level}"
 
 User.fromJSON = (jsonExpression) -> new User(jsonExpression.id, jsonExpression.powerLevel, jsonExpression.auth)
 Group.fromJSON = (jsonExpression) -> new Group(jsonExpression.id, getUserById userExp for userExp in jsonExpression.members, jsonExpression.passphrase)
 Message.fromJSON = (je) -> new Message je.type, je.sourceId, je.destId, je.message, je.read
 
 # Set up variables and functions that use the defined classes
-userRegistry.push User.fromJSON json for json in JSON.parse Modules.fs.readFileSync ResourceDir + "users.json"
-groupRegistry.push Group.fromJSON json for json in JSON.parse Modules.fs.readFileSync ResourceDir + "groups.json"
-messageRegistry.push Message.fromJSON json for json in JSON.parse Modules.fs.readFileSync ResourceDir + "messages.json"
+userRegistry.push User.fromJSON json for json in getFileJSON 'users'
+groupRegistry.push Group.fromJSON json for json in getFileJSON 'groups'
+messageRegistry.push Message.fromJSON json for json in getFileJSON 'messages'
+
+KcCoreLogger = new Logger "KentChat"
+KcMessageLogger = new Logger "MessageLogger"
 
 containsUser = (id) -> (getUserById id)?
 containsGroup = (id) -> (getGroupById id)?
 messagesFrom = (id) -> (msg for msg in messageRegistry when msg.sourceId is id)
-messagesToUser = (id) -> (msg for msg in messageRegistry when msg.type is Message.DIRECT_MESSAGE and msg.destId is id)
+messagesToUser = (id) -> (msg for msg in messageRegistry when msg.type is Message.DIRECT_MESSAGE and msg.destId is id).concat \
+	(msg for msg in messageRegistry when msg.type is Message.GROUP_MESSAGE and id in (getGroupById msg.destId).members)
 groupIDs = () -> (group.id for group in groupRegistry)
 userIDs = () -> (user.id for group in groupRegistry)
+connectionIsLoggedIn = (conn) -> if user.connID is conn.id for user in userRegistry then yes else no
+getUserBySocketID = (socketID) ->
+	return user for user in userRegistry when user.connID is socketID
 User::unreadMessages = () -> (msg for msg in messagesToUser @id when not msg.read)
 User::sentMessages = () -> messagesFrom @id
 User::onRead = (message) -> (message.read = yes) if message.type is Message.DIRECT_MESSAGE and message.destId is @id and not message.read
-User::sendMessage = (destId, message, type) -> messageRegistry.push new Message type, @id, destId, message
-User::tell = (data) -> @connection.send data
+User::sendMessage = (destId, message, type) ->
+	msg = new Message type, @id, destId, message
+	messageRegistry.push msg
+	@send {type: "newMessage", id: msg.id}
 Group::getMessages = () -> (msg for msg in messageRegistry when msg.type is Message.GROUP_MESSAGE and msg.destId is @id)
 
+# Event handler common functions
+fail = (reason) -> ['failure', reason]
+succeed = (info) -> if info? then ['success', info] else ['success']
+data = (raw) -> ['data', (if raw instanceof Array then raw.join ', ' else raw)]
 # Event handler modules
 eventHandlers = [
 	{
 		expectedType: "msg"
+		expectedPerm: UserPowerLevels.guest
 		expectedArgs: ["dest", "message", "toUser"]
 		onMessage: (msg, source) ->
-			return "failure: target user or group does not exist" if (msg.toUser and not containsUser msg.dest) or (not msg.toUser and not containsGroup msg.dest)
+			(return fail "target user or group does not exist") if (msg.toUser and not containsUser msg.dest) or (not msg.toUser and not containsGroup msg.dest)
 			source.sendMessage msg.dest, msg.message, if msg.toUser then Message.DIRECT_MESSAGE else Message.GROUP_MESSAGE
-			return true
+			KcMessageLogger.log "message sent from #{source.toString()} to #{msg.dest}: #{msg.message}"
+			return succeed()
 	}
 	{
 		expectedType: "listUsers"
+		expectedPerm: UserPowerLevels.guest
 		expectedArgs: []
-		onMessage: (msg, source) ->
-			return userIDs().join ", "
+		onMessage: (msg, source) -> data userIDs()
 	}
 	{
 		expectedType: "read"
+		expectedPerm: UserPowerLevels.guest
 		expectedArgs: ["id"]
-		# todo
+		onMessage: (msg, source) ->
+			message = getMessageById msg.id
+			if message? and message.destId is source.id
+				source.onRead message
+				return data
+					read: yes
+					sender: message.sourceId
+					message: message.message
+			else return data
+				read: no
+				why: 'message does not exist or is unaccessible'
+	}
+	{
+		expectedType: "getUnread"
+		expectedPerm: UserPowerLevels.guest
+		expectedArgs: []
+		onMessage: (msg, source) -> data(message.id for message in source.unreadMessages())
+	}
+	{
+		expectedType: "createGroup"
+		expectedPerm: UserPowerLevels.admin
+		expectedArgs: ["id"]
+		onMessage: (msg, source) ->
+			if msg.id in groupIDs()
+				return fail 'this group already exists'
+			else
+				groupRegistry.push new Group msg.id, [source.id]
+				return succeed "group #{msg.id} created"
+	}
+	{
+		expectedType: "listGroups"
+		expectedPerm: UserPowerLevels.guest
+		expectedArgs: []
+		onMessage: (msg, source) -> data groupIDs()
+	}
+	{
+		expectedType: "inviteToGroup"
+		expectedPerm: UserPowerLevels.guest
+		expectedArgs: ['target', 'group']
+		onMessage: (msg, source) ->
+			(return fail "group #{msg.group} does not exist") if not msg.group in groupIDs()
+			source.sendMessage msg.target, "I invite you to join the group named #{msg.group}. \
+				Use \"kc.join('#{msg.group}')\" to join. (generated by KentChat)", Message.DIRECT_MESSAGE
+			return succeed "invitation sent"
+	}
+	{
+		expectedType: "joinGroup"
+		expectedPerm: UserPowerLevels.guest
+		expectedArgs: ['target']
+		onMessage: (msg, source) ->
+			(return fail "group #{msg.group} does not exist") if not msg.target in groupIDs()
+			group = getGroupById msg.target
+			(return fail "you are already in this group") if source.id in group.members
+			group.addUser source
+			return succeed "you have joined #{group.id}"
+	}
+	{
+		expectedType: "register"
+		expectedPerm: UserPowerLevels.admin
+		expectedArgs: ['id', 'auth', 'powerLevel']
+		onMessage: (msg, source) ->
+			(return fail "#{msg.id} exists") if containsUser source.id
+			userRegistry.push new User msg.id, msg.powerLevel, Modules.sha msg.auth
+			return succeed "#{msg.id} created"
 	}
 ]
-setupHandlersForUser = (conn, id) ->
-
+KcCoreLogger.debug "Declared #{eventHandlers.length} event handlers"
 
 # Login procedure
-WSServer.on 'request', (req) ->
-	if req.host in hostBlacklist
-		req.reject 200, 'You are on the KentChat blacklist!'
-	else
-		conn = req.accept null, req.origin
-		conn.on 'message', (message) ->
-			if message.type is 'binary'
-				conn.drop 1007, 'expected login data to be utf8, received binary'
-			else if not (message.id? and message.auth?)
-				conn.drop 1007, 'expected msg.id and msg.auth, missing'
-			else
-				user = getUserById message.id
-				if not user?
-					conn.drop 1003, "unknown userID: #{message.id}"
+SIOServer.on 'connection', (socket) ->
+	socket.on 'login', (data) ->
+		if not data.id? or not data.auth?
+			socket.emit 'failure', 'expected data.id and data.auth, missing'
+			return
+		if connectionIsLoggedIn socket
+			socket.emit 'failure', 'already logged in'
+			return
+		user = getUserById data.id
+		if not user?
+			socket.emit 'failure', "unknown userID: #{data.id}"
+			return
+		if user.isConnected()
+			socket.emit 'failure', "user #{data.id} is already logged in"
+			return
+		if not user.authenticate data.auth
+			socket.emit 'failure', 'bad password'
+			return
+		user.onConnect socket
+		socket.emit 'success', {}
+		for handler in eventHandlers
+			socket.on handler.expectedType, (data) ->
+				if not connectionIsLoggedIn socket
+					socket.emit 'failure', 'You are not logged in'
+					socket.disconnect()
 					return
-				if user.isConnected()
-					conn.drop 1003, "user #{message.id} is already logged in"
+				source = getUserBySocketID socket.id
+				if not source.isQualifiedFor handler.expectedPerm
+					source.send 'failure', "You are not qualified to '#{handler.expectedType}', \
+						required power level is #{handler.expectedPerm}, you have #{source.powerLevel}"
 					return
-				if not user.authenticate message.auth
-					conn.drop 1003, 'bad password'
+				if not data[arg]? for arg in handler.expectedArgs
+					source.send 'failure', "Missing arguments; expected #{handler.expectedArgs.join ', '}"
 					return
-				user.onConnect conn
+				result = handler.onMessage data, source
+				if not (result instanceof Array) or result.length isnt 2
+					KcCoreLogger.error "Unexpected result from #{handler.expectedType} eventHandler: #{result}, expected 2-element array"
+					source.send 'failure', 'Server internal error, please report to the owners'
+					return
+				source.send result[0], result[1]
+	socket.on 'disconnect', ->
+		source = getUserBySocketID socket.id
+		source.onDisconnect() if source?

messageRegistry = []
loggedUsers = []
hostBlacklist = JSON.parse Modules.fs.readFileSync ResourceDir + "blacklist.json"

# Declare object types using classes
UserPowerLevels =

Modules =
	fs: require('fs')
	https: require('https')
	sio: require('socket.io')
	sha: require('sha1')
	aes: require('aes256')
	readline: require('readline')
