(in-package :clf)

;; координаты ячейки

(defclass cell-coordinates ()
  ((coord-vector :initarg :coord-vector
		 :accessor coord-vector)
   (dimensions :initarg :dimensions
	       :accessor dimensions)
   (headers/cell-coordinates :initform '(x-coord y-coord z-coord)
			     :allocation :class)))

(make-header-for-class/dimensions cell-coordinates)

(defmethod coord (&rest numbers)
  (make-instance 'cell-coordinates
		 :coord-vector (list->vector numbers)
		 :dimensions (length numbers)))

(defmethod initialize-instance :after ((obj cell-coordinates) &key coordinate)
  (cond ((cell-coordinates-p coordinate)
	 (setf (coord-vector obj) (coord-vector coordinate)))
	(t nil)))

(defun cell-coordinates-p (obj)
  (typep obj 'cell-coordinates))

;; акцессоры координат

(defmethod x-coord ((crnt cell-coordinates))
  (elt (coord-vector crnt) 0))

(defmethod y-coord ((crnt cell-coordinates))
  (elt (coord-vector crnt) 1))

(defmethod z-coord ((crnt cell-coordinates))
  (elt (coord-vector crnt) 2))

;; мутаторы координат

(defmethod (setf x-coord) (num (crnt cell-coordinates))
  (setf (elt (coord-vector crnt) 0) num))

(defmethod (setf y-coord) (num (crnt cell-coordinates))
  (setf (elt (coord-vector crnt) 1) num))

(defmethod (setf z-coord) (num (crnt cell-coordinates))
  (setf (elt (coord-vector crnt) 2) num))

;; операции, которые можно производить с координатами

(defmacro cell-coordinates-operator (coords-list func)
  `(progn
     (when (null ,coords-list) (error "cell-coordinates-operator нельзя применять к пустому списку"))
     (unless (andmap #'cell-coordinates-p ,coords-list)
       (error "Операция ~s требует, чтобы все аргунты были типа 'cell-coordinates" ,func))
     (let ((dim (dimensions (first ,coords-list))))
       (unless (andmap (lambda (x) (equal dim (dimensions x)))
		       ,coords-list)
	 (error "Все координаты должны быть одной размерности"))
       (make-instance 'cell-coordinates
		      :coord-vector (apply #'map 'vector ,func
					   (mapcar #'coord-vector ,coords-list))
		      :dimensions dim))))

(defun coord+ (&rest coords)
  (cell-coordinates-operator coords #'+))

(defun coord- (&rest coords)
  (cell-coordinates-operator coords #'-))

(defmethod distance ((coord1 cell-coordinates) (coord2 cell-coordinates))
  (let ((diffs (vector->list (coord-vector (coord- coord2 coord1)))))
    (sqrt (apply #'+ (mapcar #'(lambda (x)
				 (expt x 2))
			     diffs)))))