(in-package :clf)

;; надо заметить, что данный класс - чудовищная копипаста из grid2d-pv
;; возможно, надо бы сетку и используемую ячейку в ней как-то развязать между собой
;; по факту - различие-то в инициализации, и только-то.

(defclass grid2d-cv ()
  (;; количество ячеек
   (cells-amount-x :initarg :cells-amount-x
		   :initform (error "необходимо задать количество ячеек по x")
		   :reader cells-amount-x)
   (cells-amount-y :initarg :cells-amount-y
		   :initform (error "необходимо задать количество ячеек по y")
		   :reader cells-amount-y)
   ;; характеристики сетки
   (iteration :initarg :iteration
	      :initform 0
	      :accessor iteration)
   (time :initarg :time
	 :initform 0.0d0
	 :accessor grid-time)
   ;; массив ячеек
   (cells :initform 'undef
	  :accessor cells)
   ;; максимальная невязка на сетке
   (convergence :initform 0.0
		:initarg :convergence
		:accessor convergence)
   ;; граничные условия
   (left-boundary-condition :initarg :left-boundary-condition
			    :initform (error "не задано левое ГУ")
			    :reader left-boundary-condition)
   (right-boundary-condition :initarg :right-boundary-condition
			     :initform (error "не задано правое ГУ")
			     :reader right-boundary-condition)
   (top-boundary-condition :initarg :top-boundary-condition
			   :initform (error "не задано верхнее ГУ")
			   :reader top-boundary-condition)
   (bottom-boundary-condition :initarg :bottom-boundary-condition
			      :initform (error "не задано нижнее ГУ")
			      :reader bottom-boundary-condition)))

(defmethod initialize-instance :after ((grid grid2d-cv) &key &allow-other-keys)
  ;; создать сам массив
  (setf (cells grid)
	(make-array (list (2+ (cells-amount-x grid))
			  (2+ (cells-amount-y grid)))))
		    ;;:element-type 'cell2d-cv))
  ;; создать все его элементы и их градиенты
  (let ((xi-min -1)
	(yi-min -1)
	(xi-max (cells-amount-x grid))
	(yi-max (cells-amount-y grid)))
    (loop for i from (min-x-index/border grid) to (max-x-index/border grid) do
	 (loop for j from (min-y-index/border grid) to (max-y-index/border grid) do
	      (let ((index (index i j)))
		(setf (cell grid index)
		      (if (valid-index-p grid index)
			  (make-dummy-cell 'cell2d-cv)
			  'outgrid)))))))

;; проверка, находится ли индекс в допустимых диапазонах
(defun grid-index-type (grid index)
  (let ((xi (x-index index))
	(yi (y-index index))
	(xi-min -1)
	(yi-min -1)
	(xi-max (cells-amount-x grid))
	(yi-max (cells-amount-y grid))
	)
    (cond ((and (<= 0 xi (1- xi-max))
		(<= 0 yi (1- yi-max)))
	   'internal)
	  ((or (and (or (equal xi xi-min)
			(equal xi xi-max))
		    (<= 0 yi (1- yi-max)))
	       (and (or (equal yi yi-min)
			(equal yi yi-max))
		    (<= 0 xi (1- xi-max))))
	   'border)
	  (t 'outgrid))))

(defun border-index-p (grid index)
  (equal (grid-index-type grid index) 'border))

(defun internal-index-p (grid index)
  (equal (grid-index-type grid index) 'internal))

(defun outgrid-index-p (grid index)
  (equal (grid-index-type grid index) 'outgrid))

(defun valid-index-p (grid index)
  (not (outgrid-index-p grid index)))

;; выдаёт ошибку, если индекс вне допустимых диапазонов
(defun index-validation (grid index)
  (unless (valid-index-p grid index)
    (error "Индекс за пределами сетки: (index ~a ~a)~%" (x-index index) (y-index index))))

;; Акцессоры и мутаторы для элементов сетки
;; note: у меня такое чувство, будто мутаторами я пользоваться не буду

(defmethod cell ((grid grid2d-cv) (index cell-index))
  (index-validation grid index)
  (aref (cells grid) (1+ (x-index index)) (1+ (y-index index))))

(defmethod (setf cell) ((new cell2d-cv) (grid grid2d-cv) (index cell-index))
  (index-validation grid index)
  (setf (aref (cells grid) (1+ (x-index index)) (1+ (y-index index)))
	(clone new)))

(defmethod (setf cell) ((new conservative-variables) (grid grid2d-cv) (index cell-index))
  (index-validation grid index)
  (setf (var-vector (cell grid index))
	(var-vector new)))

(defmethod (setf cell) ((new cell-coordinates) (grid grid2d-cv) (index cell-index))
  (index-validation grid index)
  (setf (coord-vector (cell grid index))
	(coord-vector new)))

(defmethod (setf cell) ((new cell-size) (grid grid2d-cv) (index cell-index))
  (index-validation grid index)
  (setf (size-vector (cell grid index))
	(size-vector new)))

;; Этот метод используется для задания символов 'OUTGRID в угловых
;; ячейках сетки, поэтому валидация не нужна.
(defmethod (setf cell) ((new symbol) (grid grid2d-cv) (index cell-index))
  (setf (aref (cells grid) (1+ (x-index index)) (1+ (y-index index))) new))
	
  
 

;; дополнительные акцессоры для сетки

(defmethod min-x-index ((grid grid2d-cv))
  0)

(defmethod max-x-index ((grid grid2d-cv))
  (1- (cells-amount-x grid)))

(defmethod min-x-index/border ((grid grid2d-cv))
  (1- (min-x-index grid)))

(defmethod max-x-index/border ((grid grid2d-cv))
  (1+ (max-x-index grid)))

(defmethod min-y-index ((grid grid2d-cv))
  0)

(defmethod max-y-index ((grid grid2d-cv))
  (1- (cells-amount-y grid)))

(defmethod min-y-index/border ((grid grid2d-cv))
  (1- (min-y-index grid)))

(defmethod max-y-index/border ((grid grid2d-cv))
  (1+ (max-y-index grid)))

;; дополнительные акцессоры используются из grid2d-primitive.lisp
;; функции инициализации я подцепляю оттуда же

(defmethod init-grid ((grid grid2d-cv)
		      &key ((:left-border-coord x1)) ((:right-border-coord x2))
			top-border-line bottom-border-line
			thickening-coef-x thickening-coef-y)
  (unless (and x1 x2)
    (error "надо задать левую и правую границу по координате x: :left-border-coord :right-border-coord"))
  (unless (and top-border-line bottom-border-line)
    (error "надо определить функции, задающие вернюю и нижнюю линии: :top-border-line :bottom-border-line"))
  (unless (and thickening-coef-x thickening-coef-y)
    (error "надо задать коэффициенты поджатия по осям: :thickening-coef-x :thickening-coef-y"))

  (let ((x-coefs (loop for i from 0 to (nodes-max-x grid)
		    collect (thick i (cells-amount-x grid) thickening-coef-x)))
	(y-coefs (loop for i from 0 to (nodes-max-y grid)
		    collect (thick i (cells-amount-y grid) thickening-coef-y)))
	(nodes (make-hash-table :test #'equal)) ; хэш с координатами узлов
	(cell-centers (make-hash-table :test #'equal)) ; хэш с координатами центров ячеек
	)

    ;; находим координаты узлов при помощи коэффициентов поджатия x-coefs и y-coefs
    (loop for i from 0 to (nodes-max-x grid) do
	 (let* ((x (+ x1 (* (nth i x-coefs)
			    (- x2 x1))))
		(y1 (funcall bottom-border-line x))
		(y2 (funcall top-border-line x)))
	   (loop for j from 0 to (nodes-max-y grid) do
		(let ((y (+ y1 (* (nth j y-coefs)
				  (- y2 y1)))))
		  (setf (gethash (list i j) nodes)
			(coord x y))))))

    ;; находим центры ячеек
    (loop for i from 0 to (cells-max-x grid) do
	 (loop for j from 0 to (cells-max-y grid) do
	      (setf (gethash (list i j) cell-centers)
		    (determine-mass-center nodes i j))))

    ;; определяем центры заграничных ячеек (кстати не совсем верно)
    (loop for j from 0 to (cells-max-y grid) do
	 (progn
	   ;; слева
	   (let ((cell-coord (gethash (list 0 j) cell-centers)))
	     (setf (gethash (list -1 j) cell-centers)
		   (coord (- (* 2 x1)
			     (x-coord cell-coord))
			  (y-coord cell-coord))))
	   ;; справа
	   (let ((cell-coord (gethash (list (cells-max-x grid) j) cell-centers)))
	     (setf (gethash (list (1+ (cells-max-x grid)) j) cell-centers)
		   (coord (- (* 2 x2)
			     (x-coord cell-coord))
			  (y-coord cell-coord))))))
    
    (loop for i from 0 to (cells-max-x grid) do
	 (progn
	   ;; снизу
	   (let ((cell-coord (gethash (list i 0) cell-centers)))
	     (setf (gethash (list i -1) cell-centers)
		   (coord (x-coord cell-coord)
			  (- (* 2 (funcall bottom-border-line (x-coord cell-coord))) 
			     (y-coord cell-coord)))))
	   ;; сверху
	   (let ((cell-coord (gethash (list i (cells-max-y grid)) cell-centers)))
	     (setf (gethash (list i (1+ (cells-max-y grid))) cell-centers)
		   (coord (x-coord cell-coord)
			  (- (* 2 (funcall top-border-line (x-coord cell-coord)))
			     (y-coord cell-coord)))))))

    ;; задаём координаты и размеры ячеек сетки и заграничных ячеек
    ;; сначала устанавливаем в центре, а потом сносим из приграничных за границу
    (loop for stage in '(center borders) do
	 (loop for i from -1 to (1+ (cells-max-x grid)) do
	      (loop for j from -1 to (1+ (cells-max-y grid)) do
		 ;; везде, кроме углов
		   (unless (and (not (<= 0 i (cells-max-x grid)))
				(not (<= 0 j (cells-max-y grid))))
		     (setf (cell grid (index i j)) ;; устанавливаем центры
			   (gethash (list i j) cell-centers))
		     (cond ((= i -1) ;; левые заграничные
			    (when (equal stage 'borders)
			      (setf (x-size (cell grid (index i j)))
				    (x-size (cell grid (index (1+ i) j))))
			      (setf (y-size (cell grid (index i j)))
				    (y-size (cell grid (index (1+ i) j)))))
			    )
			   ((= i (1+ (cells-max-x grid))) ;; правые заграничные
			    (when (equal stage 'borders)
			      (setf (x-size (cell grid (index i j)))
				    (x-size (cell grid (index (1- i) j))))
			      (setf (y-size (cell grid (index i j)))
				    (y-size (cell grid (index (1- i) j)))))
			    )
			   ((= j -1) ;; нижние заграничные
			    (when (equal stage 'borders)
			      (setf (x-size (cell grid (index i j)))
				    (x-size (cell grid (index i (1+ j)))))
			      (setf (y-size (cell grid (index i j)))
				    (y-size (cell grid (index i (1+ j))))))
			    )
			   ((= j (1+ (cells-max-y grid))) ;; верхние заграничные
			    (when (equal stage 'borders)
			      (setf (x-size (cell grid (index i j)))
				    (x-size (cell grid (index i (1- j)))))
			      (setf (y-size (cell grid (index i j)))
				    (y-size (cell grid (index i (1- j))))))
			    )
			   (t ;; центральные
			    (when (equal stage 'center)
			      (setf (x-size (cell grid (index i j)))
				    (determine-x-size cell-centers i j))
			      (setf (y-size (cell grid (index i j)))
				    (determine-y-size cell-centers i j)))))))))
    ))

(defmethod print-grid ((grid grid2d-cv) &key (print-out-border nil))
  ;; информация о текущем состоянии
  (format t "# Iterations: ~a~%" (iteration grid))
  (format t "# Time: ~a~%" (grid-time grid))
  (format t "# Convergence: ~a~%" (convergence grid))
  ;; заголовки
  (format t "NX NY ")
  (print-headers t (cell grid (index 0 0)))
  (newline)
  ;; ячейки
  (loop for i from -1 to (1+ (cells-max-x grid)) do
       (loop for j from -1 to (1+ (cells-max-y grid)) do
	    (let ((index (index i j))
		  (print-flag nil))
	      (case (grid-index-type grid index)
		((border) (when print-out-border (setf print-flag t)))
		((internal) (setf print-flag t)))
	      (when print-flag
		(format t "~d ~d " i j)
	        (print-content t (cell grid (index i j)) :with-headers nil)
		(newline))))))

(defmethod clone ((grid grid2d-cv))
  ;; создадим новую сетку
  (let ((newgrid (make-instance 'grid2d-cv
				:cells-amount-x (cells-amount-x grid)
				:cells-amount-y (cells-amount-y grid)
				:left-boundary-condition (left-boundary-condition grid)
				:right-boundary-condition (right-boundary-condition grid)
				:bottom-boundary-condition (bottom-boundary-condition grid)
				:top-boundary-condition (top-boundary-condition grid)
				:iteration (iteration grid)
				:time (grid-time grid)
				:convergence (convergence grid))))
    ;; скопируем все ячейки
    (loop for i from -1 to (1+ (cells-max-x grid)) do
	 (loop for j from -1 to (1+ (cells-max-y grid)) do
	      (unless (and (not (<= 0 i (cells-max-x grid)))
			   (not (<= 0 j (cells-max-y grid))))
		(setf (cell newgrid (index i j))
		      (cell grid (index i j))))))
    ;; вернём новую сетку в качестве значения
    newgrid))