(defun init-all-agent-properties ()
	(create-agent-property-with-description 'agent-name "Specifies the name of the agent.")
	(create-agent-property-with-description 'role-number "Specifies the number of the agent.")
	(create-agent-property-with-description  'ready-interval-delay 
		"The interval of time between receipt of the instructions URL and when the agent clicks Ready in milliseconds. 
		Default is 10000 (10 seconds). ")
)

(defun create-agent-property-with-description (property description)
	"Creates an agent property with a description."
	(add-triple property 'isa 'agent-property)
	(add-triple property 'description description))

(defun configure-act-r-agents (num-agents agent-prefix model-code)
	(let ((agent-num 0)
			(m nil))
		(dotimes (x num-agents)
			(setf agent-num (+ x 1)) ;;1-based counting
			(setf m (format nil "~A-~D" agent-prefix agent-num))
			(configure-act-r-agent m model-code agent-num)))
)

(defun configure-act-r-agent (model-name model-code role-number) 
	(let* ((m (intern (format nil (string-upcase model-name)))))
				(add-triple m 'isa 'agent)
				(add-triple m 'isa 'act-r-agent)
				(add-model-code m model-code)
				(add-triple m 'role-number role-number))
)

(defun configure-system-agent (system-agent-name)
	(add-triple system-agent-name 'isa 'agent)
	(add-triple system-agent-name 'isa 'system-agent)
)

(defun configure-monitor-agent ()
	(add-triple 'xna-monitor 'isa 'monitor)
)

(defun create-act-r-models ()
	(dolist (model (get-act-r-agents))
		(define-model-fct model (get-model-code-for-agent model))
		(with-model-eval model
			(install-device (make-task-environment)))) ;;create the device instance to represent the task environment
)

(defun get-act-r-agents ()
	"Returns a list of ACT-R agents."
	(get-subjects 'isa 'act-r-agent)
)

(defun configure-all-agent-properties ()
	(dolist (m (get-act-r-agents))
		(add-triple m 'friendly-name (get-agent-friendly-name m))
		(add-triple m 'ready-interval-delay '10000)
		(add-triple m 'agent-name m))
)

(defun is-monitor-connected ()
	(triple-exists 'xna-monitor 'is-initialized 'yes)
)

(defun is-non-zero-length-string (string &optional (exclude-blank-spaces nil))
	(if (or (not (stringp string))
				(= (string-length string exclude-blank-spaces) 0))
		nil
	  t)
)

(defun get-object-from-triple (subject predicate)
	(let ((triple(select *triples-db* (where :subject subject :predicate predicate))))
		(if (eq triple nil)
			nil
			(getf (first triple) :object))))

(defun get-subjects (predicate object)
	(let ((result nil))
		(dolist (row (select *triples-db* (where :predicate predicate :object object)) result)
			(push (getf row :subject) result)))
)

(defun get-monitor-ipaddress ()
	(string (get-object-from-triple 'xna-monitor 'has-network-location))
)

(defun get-monitor-port-number ()
	(get-object-from-triple 'xna-monitor 'has-port-number)
)

(defun string-length (string &optional (exclude-blank-spaces nil))
	(let* ((s (if exclude-blank-spaces
					(string-trim " " string)
				  string))
			(c-string (loop for char across s
								collect char)))
		(length c-string))
)


(defun triple-exists (subject predicate object)
	(if (eq (select *triples-db* (where :subject subject :predicate predicate :object object)) 'nil)
		nil
	t))

(defun get-agent-friendly-name (name)
	(substitute-characters #\  #\-  (string name))
)

(defun get-model-code-for-agent (agent)
	(let ((row (select *model-code-db* (where :agent agent))))
		(if row
			(getf (first row) :model-code)
		  nil))
)

(defun get-slot-value-from-chunk-spec (spec slot-name chunk-type-name)
 (let* ((type (chunk-spec-chunk-type spec))
         (value? (slot-in-chunk-spec-p spec slot-name))
         (v1 (when value? (chunk-spec-slot-spec spec slot-name))))
     (if (eq type chunk-type-name) ;; we only want to handle chunks of a particular type
         (if value?
             (if (= (length v1) 1)
                 (if (eq (caar v1) '=)
                     (caddar v1)
                   (model-warning "Invalid slot modifier ~s in buffer request" (caar v1)))
               (model-warning "Value slot specified multiple times in buffer request"))
           (model-warning "Value slot missing in buffer request: ~s" slot-name))
       (model-warning "Bad chunk-type in request to buffer"))))

(defun substitute-characters (targetChar substituteChar string)
	(substitute targetChar substituteChar string)
)

(defun get-mp-time ()
	"Returns the current simulation time."
		(mp-time)
)

(defun calculate-simulation-duration ()
	"Returns the duration of the simulation."
	*cycle-count*
)

;; update cycle is called periodically, and will schedule conflict resolution event
(defun update-cycle ()
	(incf *cycle-count*)
	(print-model-output (mp-time) "SYSTEM" "REPORTING" (concatenate 'string "Running update cycle " (write-to-string *cycle-count*)))
	(if (check-simulation-status) ;; all agents are actively performing the task
		(progn
			(incf *simulation-cycle-count*)
			(dolist (m (get-act-r-agents))
					(with-model-eval m ;;make m the current model
						;; update the cycle-count slot of the goal module buffer
						(schedule-event-relative 0 'update-goal-simulation-cycle-count-slot :module 'goal)
						;; call the update cycle method for custom modules
						(schedule-event-relative 0 'self-module-update-cycle :params (list (get-module-fct 'self-module)) :module 'self-module)))))
	(dolist (m (mp-models))
			(unless (eq m 'base-model)
				(with-model-eval m ;;make m the current model
					(schedule-event-relative 0 'update-goal-cycle-count-slot :module 'goal))))
)

(defun update-goal-simulation-cycle-count-slot ()
	(mod-focus-fct `(simulation-cycle-count ,*simulation-cycle-count*))
)

(defun check-simulation-status ()
	(if (every #'(lambda (x) (triple-exists x 'is-ready 'yes)) (get-act-r-agents)) ;all the agents are ready
		(if (eq (triple-exists 'simulation 'status 'started) nil) ;but the simulation has not started
			(progn
				(add-triple 'simulation 'status 'started) ;start the simulation
				(dolist (m (mp-models))
					(unless (eq m 'base-model)
						(with-model-eval m ;;make m the current model
							(print-model-output (mp-time) "SYSTEM" "REPORTING" (concatenate 'string (string m) " is ready to begin the task."))
							(schedule-event-relative 3 (lambda ()(mod-focus-fct '(task-status start))) :module 'goal)))) ;start the models
			t)
		t) 
	nil)		
)

;; updates the 'cycle-count' slot of the goal chunk if a chunk with that slot exists.
(defun update-goal-cycle-count-slot ()
	(mod-focus-fct `(cycle-count ,*cycle-count*))
)