(in-package :clf)

(defclass conservative-variables ()
  ((var-vector :initform (make-array *number-of-conservative-variables*)
	       :accessor var-vector)
   (headers/conservative-variables :initform '(P rg phi ug ul)
				   :allocation :class)
   ))


(defmethod initialize-instance :after ((vars conservative-variables) &key P phi ug ul rg number var-vector)
  (cond (var-vector (setf (var-vector vars) var-vector))
	(number (setf (var-vector vars) (make-array *number-of-conservative-variables*
						    :initial-element number)))
	((or phi ug ul P rg)
	 ;(format t "Дано:~%P: ~d~%rg: ~d~%phi: ~d~%ug: ~d~%ul: ~d~%~%" P rg phi ug ul)
	 (let ((primitive-vars (make-instance 'primitive-variables
					      :P P :rg rg :phi phi :ug ug :ul ul)))
	   (setf (var-vector vars)
		 (var-vector (primitive->conservative primitive-vars)))))
	(t (error "не хватает флагов для определения ячейки"))))

(make-header-for-class conservative-variables)

(defun conservative-variables-p (obj)
    (typep obj 'conservative-variables))

(print-functions-for-class conservative-variables
			   ((inherit . conservative-variables)))

;; дополнительная возможность вывода в чистом виде, как они есть (для дебага)

(defun print-conservative (stream cv &key (with-headers t))
  (when with-headers
    (format stream "u1 u2 u3 u4~%"))
  (apply #'format stream "~d ~d ~d ~d" (vector->list (var-vector cv))))

;; вспомогательный макрос для консервативных переменных

(defmacro let-conservative (cv &body body)
  "Связывает символы U<Num>, где <Num> - число от 1 до (length cv)."
  (let* ((vector-sym (gensym))
	 (numbers (iota *number-of-conservative-variables*))
	 (symbols (mapcar (lambda (n)
			    (intern (format nil "U~d" (+ n 1))))
			  numbers)))
    `(let* ,(mapcar (lambda (sym num)
		      `(,sym (elt (var-vector ,cv) ,num)))
		    symbols numbers)
       (if (or ,@(mapcar (lambda (sym)
			   `(equal ,sym 'undef))
			 symbols))
	   'undef
	   (progn ,@body)))))

;; акцессоры консервативных переменных

(defmethod rg ((vars conservative-variables))
  (let-conservative vars
    (/ (* *rl* u1)
       (- *rl* u3))))

(defmethod P ((vars conservative-variables))
  (let-conservative vars ;; для проверки на undef
    (* (/ (* *Cg* *Cg*)
	  *kappa*)
       (rg vars))))

(defmethod phi ((vars conservative-variables))
  (let-conservative vars
    (/ (- *rl* u3)
       *rl*)))

(defmethod ug ((vars conservative-variables))
  (let-conservative vars
    (/ u2 u1)))

(defmethod ul ((vars conservative-variables))
  (let-conservative vars
    (/ u4 u3)))

;; мутаторы консервативных переменных
;; отсутствуют

;; преобразования между примитивными и консервативными переменными

(defun primitive->conservative (pv)
  (let ((phi (phi pv))
	(rg (rg pv))
	(ug (ug pv))
	(ul (ul pv)))
    (if (or (equal phi 'undef)
	    (equal rg 'undef)
	    (equal ug 'undef)
	    (equal ul 'undef))
	(make-instance 'conservative-variables
		     :var-vector (vector 'undef 'undef 'undef 'undef))
	(let* ((u1 (* phi rg))
	       (u2 (* u1 ug))
	       (u3 (* (- 1 phi) *rl*))
	       (u4 (* u3 ul)))
	  (make-instance 'conservative-variables
			 :var-vector (vector u1 u2 u3 u4))))))
  
(defun conservative->primitive (cv)
  (let-conservative cv
    (let* ((phi (- 1 (/ u3 *rl*)))
	   (rg (/ u1 phi))
	   (ug (/ u2 u1))
	   (ul (/ u4 u3)))
      (make-instance 'primitive-variables
		     :phi phi
		     :rg rg
		     :ug ug
		     :ul ul))))

;; операции, которые можно производить над консервативными переменными
(defun number->conservative (num)
  (make-instance 'conservative-variables :number num))

(defun any->conservative (obj)
  (cond ((numberp obj) (number->conservative obj))
	((typep obj 'conservative-variables) obj)
	(t (error "need num or conservative!"))))

(defmacro conservative-variables-operator (cv-list func)
  (let ((cv-list-sym (gensym)))
    `(progn
       (when (null ,cv-list) (error "conservative-variables-operator нельзя применять к пустому списку"))
       (let ((,cv-list-sym (mapcar #'any->conservative ,cv-list)))
	 (make-instance 'conservative-variables
			:var-vector (apply #'map 'vector ,func
					   (mapcar #'var-vector ,cv-list-sym)))))))

(defun cvar+ (&rest cvs&nums)
  (conservative-variables-operator cvs&nums #'+))

(defun cvar- (&rest cvs&nums)
  (conservative-variables-operator cvs&nums #'-))

(defun cvar/ (&rest cvs&nums)
  (conservative-variables-operator cvs&nums #'/))

(defun cvar* (&rest cvs&nums)
  (conservative-variables-operator cvs&nums #'*))

(defun cvar-abs (cv)
  (conservative-variables-operator (list cv) #'abs))

(defun cvar-min (&rest cvs&nums)
  (conservative-variables-operator cvs&nums #'min))

(defun cvar-signum (cv)
  (conservative-variables-operator (list cv) #'signum))

;; операция minmod - особый случай

(defmethod minmod ((type (eql 'number)) &rest params)
  (cond ((andmap #'plusp params) (apply #'min params))
	((andmap #'minusp params) (apply #'max params))
	(t 0)))

(defmethod minmod ((type (eql 'conservative-variables)) &rest params)
  (conservative-variables-operator params (curry #'minmod 'number)))

(defmethod minmod ((type (eql 'primitive-variables)) &rest params)
  (primitive-variables-operator params (curry #'minmod 'number)))