(defn scalar? [x] (number? x))

(defn v? [x]
  (and (vector? x) (every? number? x)))

(defn m? [x]
  (and (vector? x) (every? v? x)))

(defn- same-length? [& vs]
  (apply = (map count vs)))

(defn- same-shape? [& ms]
  (apply = (map (fn [m] (mapv count m)) ms)))

(defn- check [cond msg]
  (assert cond msg))

(defn- vec-op [op & vs]
  (check (every? v? vs) "Все аргументы должны быть векторами чисел")
  (check (apply same-length? vs)
         (str "Векторы должны быть одинаковой длины, получено: " (mapv count vs)))
  (apply mapv op vs))

(def v+ (partial vec-op +))
(def v- (partial vec-op -))
(def v* (partial vec-op *))
(def vd (partial vec-op /))

(defn scalar [& vs]
  (reduce + (apply v* vs)))

(defn vect
  "Векторное произведение произвольного числа 3-мерных векторов."
  [& vs]
  (check (every? v? vs) "Все аргументы должны быть векторами")
  (check (every? #(= 3 (count %)) vs)
         "Векторное произведение определено только для 3-мерных векторов")
  (reduce
   (fn [a b]
     (let [[a0 a1 a2] a
           [b0 b1 b2] b]
       [(- (* a1 b2) (* a2 b1))
        (- (* a2 b0) (* a0 b2))
        (- (* a0 b1) (* a1 b0))]))
   vs))

(defn v*s [v & scalars]
  (check (v? v) "Первый аргумент должен быть вектором")
  (check (every? scalar? scalars) "Остальные аргументы должны быть скалярами")
  (let [s (reduce * scalars)]
    (mapv #(* % s) v)))

(defn- mat-op [op & ms]
  (check (every? m? ms) "Все аргументы должны быть матрицами")
  (check (apply same-shape? ms)
         (str "Матрицы должны иметь одинаковую форму, получено: "
              (mapv #(vector (count %) (count (first %))) ms)))
  (apply mapv (fn [& rows] (apply vec-op op rows)) ms))

(def m+ (partial mat-op +))
(def m- (partial mat-op -))
(def m* (partial mat-op *))
(def md (partial mat-op /))

(defn m*s [m & scalars]
  (check (m? m) "Первый аргумент должен быть матрицей")
  (check (every? scalar? scalars) "Остальные аргументы должны быть скалярами")
  (let [s (reduce * scalars)]
    (mapv (fn [row] (mapv #(* % s) row)) m)))

(defn transpose [m]
  (check (m? m) "Аргумент должен быть матрицей")
  (apply mapv vector m))

(defn m*v [m v]
  (check (m? m) "Первый аргумент должен быть матрицей")
  (check (v? v) "Второй аргумент должен быть вектором")
  (check (= (count (first m)) (count v))
         (str "Число столбцов матрицы (" (count (first m))
              ") должно совпадать с длиной вектора (" (count v) ")"))
  (mapv #(scalar % v) m))

(defn m*m [& ms]
  (check (every? m? ms) "Все аргументы должны быть матрицами")
  (reduce
   (fn [a b]
     (check (= (count (first a)) (count b))
            (str "Число столбцов левой матрицы (" (count (first a))
                 ") должно совпадать с числом строк правой (" (count b) ")"))
     (let [bt (transpose b)]
       (mapv (fn [row] (mapv #(scalar row %) bt)) a)))
   ms))
