(defun start-server ()
	"Start the TCP server."
  (ccl:with-open-socket (socket :connect :passive
                                :format :text
                                :local-port *port-number*
                                :reuse-address t) 
	(loop
		(with-open-stream (stream (ccl:accept-connection socket))
				(let ((message (read-line stream :eof nil)))
					(if (string-equal (get-action-from-network-message message) "STOP")
						(return-from start-server)
					(process-run-function "TCP Server Process" 'process-network-message message))))))
)

(defun send-network-message (server port message)
  "Send a message via TCP socket."
	;; e.g. (send-network-message "192.168.0.3" 6666 "Message content")
	(if *trace-socket-activity* (format t "~&Sending message: ~A~%" message))
	(let ((err t))
		(ignore-errors
			(let ((socket (make-socket :remote-host server :remote-port port)))
				(unwind-protect
					(progn
						(send-line socket (format nil message))
						(force-output socket)
						(close socket)
						(setf err nil)))))
		(if err
			(if *trace-socket-activity* (format t "~&An error occurred while sending the message: ~A~%" message))))
)

(defun send-line (network-stream line)
  "Send a line of text to the network stream."
  (princ line network-stream)
)

(defun send-stop-message-to-clients ()
	(if (is-monitor-connected)
		(send-network-message (get-monitor-ipaddress) (get-monitor-port-number)
				(create-network-message "SYSTEM" "STOP" "" "" "")))
)

(defun process-network-message (message)
	"Processes a message that has been received from the network."
	(if *trace-socket-activity* (format t "~&Received message: ~A~%" message))
	(cond
		((string-equal (get-action-from-network-message message) "CONNECT")
			(process-connect-network-message message))
		((string-equal (get-action-from-network-message message) "READY")
			(process-ready-network-message message))
		((string-equal (get-action-from-network-message message) "FINISH-INITIALIZATION")
			(process-finish-initialization-network-message message))
		((string-equal (get-action-from-network-message message) "GETVISICON")
			(process-get-visicon-network-message message))
		((string-equal (get-action-from-network-message message) "UPDATEDISPLAY")
			(process-update-display-network-message message))
		((string-equal (get-action-from-network-message message) "EVAL")
			(eval (read-from-string (get-text-from-network-message message))))
	)
)

(defun process-update-display-network-message (message)
	"Updates the device display."
	(declare (ignore message))
	(update-game-elements-db (where :current t) :current nil)
	(with-model agent-1
		(schedule-event-relative 0 (lambda nil (proc-display :clear t)) :maintenance t))
)

(defun get-position-x-from-network-message (message)
	(first (split-by-comma (get-text-from-network-message message))))

(defun get-position-y-from-network-message (message)
	(second (split-by-comma (get-text-from-network-message message))))

(defun get-position-z-from-network-message (message)
	(third (split-by-comma (get-text-from-network-message message))))

(defun get-height-from-network-message (message)
	(fourth (split-by-comma (get-text-from-network-message message))))

(defun get-width-from-network-message (message)
	(fifth (split-by-comma (get-text-from-network-message message))))

(defun get-distance-from-network-message (message)
	(sixth (split-by-comma (get-text-from-network-message message))))

(defun get-screen-x-from-network-message (message)
	(seventh (split-by-comma (get-text-from-network-message message))))

(defun get-screen-y-from-network-message (message)
	(eighth (split-by-comma (get-text-from-network-message message))))

(defun get-screen-width-from-network-message (message)
	(ninth (split-by-comma (get-text-from-network-message message))))

(defun get-screen-height-from-network-message (message)
	(nth 9 (split-by-comma (get-text-from-network-message message)))
)
	
(defun get-game-element-type-from-network-message (message)
	(nth 10 (split-by-comma (get-text-from-network-message message)))
)

(defun get-game-element-color-from-network-message (message)
	(nth 11 (split-by-comma (get-text-from-network-message message)))
)

(defun process-get-visicon-network-message (message)
	"Processes the GETVISICON message."
	(let* ((world-position-x (read-from-string (get-position-x-from-network-message message)))
			(world-position-y (read-from-string (get-position-y-from-network-message message)))
			(world-position-z (read-from-string (get-position-z-from-network-message message)))
			(height (read-from-string (get-height-from-network-message message)))
			(width (read-from-string (get-width-from-network-message message)))
			(world-distance (read-from-string (get-distance-from-network-message message)))
			(screen-x (read-from-string (get-screen-x-from-network-message message)))
			(screen-y (read-from-string (get-screen-y-from-network-message message)))
			(screen-width (read-from-string (get-screen-width-from-network-message message)))
			(screen-height (read-from-string (get-screen-height-from-network-message message)))
			(element-type (intern (string-upcase (get-game-element-type-from-network-message message))))
			(element-color (intern (string-upcase (get-game-element-color-from-network-message message)))))
	(add-game-element world-position-x world-position-y world-position-z height width 
			world-distance screen-x screen-y screen-width screen-height element-type element-color))
)


(defun process-connect-network-message (message)
	"Processes the CONNECT message. Clients use a CONNECT message to connect to the external environment. 
The message includes details about the clients network location and port number."
	(let ((port-number (parse-integer (get-text-from-network-message message)))
			(source (intern (get-source-from-network-message message)))
			(network-location (get-target-from-network-message message)))
		(add-triple source 'has-port-number port-number)
		(add-triple source 'has-network-location network-location)
		(send-network-message network-location port-number 
				(create-network-message "SYSTEM" "CONNECT" "OK" "" "")))
)


(defun process-ready-network-message (message)
	"Processes the READY message. This message signals that the client is ready to begin the task."
	(let ((source-val (intern (get-source-from-network-message message))))
		(add-triple source-val 'is-ready 'yes)
		(send-network-message (get-ip-address-from-message message) (get-port-number-from-message message) 
				(create-network-message "SYSTEM" "READY" "OK" "" "")))
)

(defun process-finish-initialization-network-message (message)
	"Processes the FINISH-INITIALIZATION message. This message indicates that the client has finished their initialization procedures."
	(let ((source-val (intern (get-source-from-network-message message))))
		(add-triple source-val 'is-initialized 'yes)
		(send-network-message (get-ip-address-from-message message) (get-port-number-from-message message) 
				(create-network-message "SYSTEM" "FINISH-INITIALIZATION" "OK" "" "")))
)

(defun get-ip-address-from-message (message)
	"Returns the IP address of the source of the message."
	(let ((source (intern (get-source-from-network-message message))))
		(string (get-object-from-triple source 'has-network-location)))
)

(defun get-port-number-from-message (message)
	"Returns the port number that the source of the message is listening on."
	(let ((source (intern (get-source-from-network-message message))))
		(get-object-from-triple source 'has-port-number))
)

(defun send-list-as-network-message (message l)
	"Combines the elements of l (a list) into a comma separated string and posts the string to the source of the message."
	(if (> (length l) 0)
			(send-network-message (get-ip-address-from-message message) (get-port-number-from-message message) 
				(create-network-message "SYSTEM" (get-action-from-network-message message) (format nil "~{~a~^,~}" l) (get-source-from-network-message message) ""))
			(send-network-message (get-ip-address-from-message message) (get-port-number-from-message message) 
				(create-network-message "SYSTEM" (get-action-from-network-message message) "" (get-source-from-network-message message) ""))) 
)

(defun create-network-message (arg1 arg2 arg3 arg4 arg5)
	(concatenate 'string (string arg1) ":" (string arg2) ":" (string arg3) ":" (string arg4) ":" (string arg5))
)

(defun get-source-from-network-message (message)
	(first (split-by-colon message)))

(defun get-action-from-network-message (message)
	(second (split-by-colon message)))

(defun get-text-from-network-message (message)
	(third (split-by-colon message)))

(defun get-target-from-network-message (message)
	(fourth (split-by-colon message)))

(defun split-string-by-character (string chr)
	 "Returns a list of substrings of the input string based on the separator character."
    (loop for i = 0 then (1+ j)
          as j = (position chr string :start i)
          collect (subseq string i j)
          while j)
)

(defun split-by-colon (string)
	 (split-string-by-character string #\:)
)

(defun split-by-comma (string)
	(split-string-by-character string #\,)
)

(defun kill-server-process (server-process)
	(send-network-message (get-monitor-ipaddress) (get-monitor-port-number)
				(create-network-message "SYSTEM" "STOP" "" "" ""))
	(process-kill server-process)
	(setf *server-process* nil)
)
