;;; The code below shows: (1) How to store infomation about people and
;;; their jobs as two types of Lisp data structures: structures and
;;; objects.  The generated data structures two types of objects are
;;; stored in global variables.  (2) How to (3) How to read
;;; information from a input data file to do the same thing.

;;; ---------------------------
;;;          defstruct
;;; ---------------------------

;;; A list of all employees, a global
(defvar *all-people* nil)

;;; Define 2 structures: people and job
(defstruct person
  (name nil)
  (age 0)
  (job nil))

(defstruct job
  (title 'peon)
  (hours-per-week 40)
  (salary 8.5))


;;; Creates a person Jane, and adds structure to global variable
(push (make-person :name 'Jane
		   :age 40
		   :job (make-job :title 'doctor
				  :salary 50))
      *all-people*)

;;; Creates a person Fred, and adds structure to global variable
(push (make-person :name 'Fred
		   :age 32
		   :job (make-job :title 'Burger-flipper
				  :hours-per-week 20))
      *all-people*)

;;; Assigns a salary to an person
(defun give-salary (p amount)
  (setf (job-salary (person-job p)) amount))

;;; Finds Fred, and assigns a salary to him.  Note the use of the
;;; function find with a keyword, which is very convenient to find a
;;; person
(give-salary 
 (find 'fred *all-people* :key #'(lambda (x) (person-name x)))
 9)

;;; The above can be re-written in an equivalent way as follows:
(give-salary 
 (find 'fred *all-people* :key #'person-name)
 9)

;;; ---------------------------
;;;          declass
;;; ---------------------------


;;; In Spanish, a person is "persona" and job is "trabajo".  We will
;;; use the Spanish words to avoid clash with the above defined
;;; structures.  Otherwise, the code does the same as above.

(defvar *todo-persona* nil)

(defclass persona ()
  ((name :accessor name 
	 :initform nil 
	 :initarg :name)
   (age :accessor age
	:initform nil
	:initarg :age)
   (job :accessor job
	:initform nil
	:initarg :job)))

(defclass trabajo ()
  ((title :accessor title
	  :initform 'peon
	  :initarg :title)
   (hours-per-week :accessor hours
		   :initform 40
		   :initarg :hours-per-week)
   (salary :accessor salary
	   :initform 8.5
	   :initarg :salary)))

(push (make-instance 'persona
	:name 'Jane
	:age 40
	:job (make-instance 'trabajo
	       :title 'doctor
	       :salary 50))
      *todo-persona*)

(push (make-instance 'persona
	:name 'Fred
	:age 32
	:job (make-instance 'trabajo
	       :title 'burger-flipper
	       :hours-per-week 20))
      *todo-persona*)

;;; Assigns a salary to an employee
(defmethod set-salary ((someone persona) amount)
  (setf (salary (job someone)) amount))

;; Assigs Fred a salary of $9/hour
(set-salary (find 'fred *todo-persona* :key #'name) 9)


;;; ---------------------------
;;; Reading data from a file
;;; ---------------------------

(defun load-people (filename)
  (with-open-file (in-stream filename :direction :input)
    (let ((n (read in-stream nil)))
      (dotimes (count n nil)
	(let* ((person (read in-stream nil))
	       (job (third person)))

	  ;; adds person to struct list
	  (push (make-person
		 :name (first person)
		 :age (second person)
		 :job (make-job
		       :title (first job)
		       :hours-per-week (second job)
		       :salary (third job)))
		*all-people*)
  
	  ;; adds person to class list
	  (push (make-instance 'persona
	          :name (first person)
		  :age (second person)
		  :job (make-instance 'trabajo
			 :title (first job)
			 :hours-per-week (second job)
			 :salary (third job)))
		*todo-persona*))))))

;;; Try loading this file and looking at the values of the global
;;; variables.  Then run (load-people "people-data.lisp") and check
;;; the values of the global variables.
