--
-- Matrices
--

M.* %s %t := withSymbols [i, j, k] s~i~j . t_j
M.*' %s %t := withSymbols [i, j, k] s~i~j .' t_j

M.power %t n := foldl M.* t (take (n - 1) (repeat1 t))
--M.power %m n := repeatedSquaring M.* m n

M.comm %m1 %m2 := withSymbols [i, j, k] m1~i~j . m2_j_k - m2~i~j . m1_j_k

M.inverse %m :=
  let d := M.det m
   in generateTensor
        2#(match m as matrix with
          | cons #%2 #%1 _ $A $B $C $D ->
            if isEven (%1 + %2)
              then M.det (M.join A B C D) / d
              else - (M.det (M.join A B C D) / d))
        (tensorShape m)

trace %t := withSymbols [i] sum (contract t~i_i)

matrix :=
  matcher
    | quadCons $ $ $ $ as (mathExpr, matrix, matrix, matrix) with
      | $tgt ->
        match tensorShape tgt as list integer with
          | $m :: $n :: _ ->
            [(tgt_1_1, tgt_1_(2, n), tgt_(2, m)_1, tgt_(2, m)_(2, n))]
          | _ -> []
    | cons #$i #$j $ $ $ $ $ as (mathExpr, matrix, matrix, matrix, matrix) with
      | $tgt ->
        let ns := tensorShape tgt
            m := nth 1 ns
            n := nth 2 ns
         in [ ( tgt_i_j
            , tgt_(1, i - 1)_(1, j - 1)
            , tgt_(1, i - 1)_(j + 1, n)
            , tgt_(i + 1, m)_(1, j - 1)
            , tgt_(i + 1, m)_(j + 1, n) ) ]
    | #$val as () with
      | $tgt -> if val = tgt then [()] else []
    | $ as (something) with
      | $tgt -> [tgt]

M.join %A %B %C %D :=
  let ashape := tensorShape A
      a1 := nth 1 ashape
      a2 := nth 2 ashape
      bshape := tensorShape B
      b1 := nth 1 bshape
      b2 := nth 2 bshape
      cshape := tensorShape C
      c1 := nth 1 cshape
      c2 := nth 2 cshape
      dshape := tensorShape D
      d1 := nth 1 dshape
      d2 := nth 2 dshape
      m1 := max a1 b1
      m2 := max a2 c2
      n1 := max c1 d1
      n2 := max b2 d2
   in generateTensor
        2#(match (%1, %2) as (integer, integer) with
          | (?(<= a1), ?(<= a2)) -> A_%1_%2
          | (?(<= m1), _) -> B_%1_(%2 - a2)
          | (_, ?(<= m2)) -> C_(%1 - a1)_%2
          | (_, _) -> D_(%1 - m1)_(%2 - m2))
        [m1 + n1, m2 + n2]

--
-- Determinant
--
evenAndOddPermutations n :=
  let (es, os) := evenAndOddPermutations' n
   in (map 1#(\i -> nth i %1) es, map 1#(\i -> nth i %1) os)

evenAndOddPermutations0 n :=
  let (es, os) := evenAndOddPermutations' n
   in ( map 1#(\i -> nth (i + 1) (map 1#(%1 - 1) %1)) es
      , map 1#(\i -> nth (i + 1) (map 1#(%1 - 1) %1)) os )

evenAndOddPermutations' n :=
  match n as integer with
    | #1 -> ([[1]], [])
    | #2 -> ([[1, 2]], [[2, 1]])
    | _ ->
      let (es, os) := evenAndOddPermutations' (n - 1)
          es' := map (++ [n]) es
          os' := map (++ [n]) os
       in ( es' ++ concat
                     (map
                        (\i -> map 1#(permutate i n %1) os')
                        (between 1 (n - 1)))
          , os' ++ concat
                     (map
                        (\i -> map 1#(permutate i n %1) es')
                        (between 1 (n - 1))) )

permutate x y xs :=
  match xs as list eq with
    | $hs ++ #x :: $ms ++ #y :: $ts -> hs ++ y :: ms ++ x :: ts
    | $hs ++ #y :: $ms ++ #x :: $ts -> hs ++ x :: ms ++ y :: ts

M.determinant %m :=
  match tensorShape m as list integer with
    | #0 :: #0 :: [] -> 1
    | $n :: #n :: [] ->
      let (es, os) := evenAndOddPermutations' n
       in sum
            (map
               (\e -> product (map2 (\i j -> m_i_j) (between 1 n) e))
               es) - sum
                       (map
                          (\o -> product (map2 (\i j -> m_i_j) (between 1 n) o))
                          os)
    | _ -> undefined

M.det := M.determinant

--
-- Eigenvalues and eigenvectors
--
M.eigenvalues %m :=
  match tensorShape m as list integer with
    | #2 :: #2 :: [] ->
      let (e1, e2) := qF (M.det (T.- m (scalarToTensor x [2, 2]))) x
       in [e1, e2]
    | _ -> undefined

M.eigenvectors %m :=
  match tensorShape m as list integer with
    | #2 :: #2 :: [] ->
      let (e1, e2) := qF (M.det (T.- m (scalarToTensor x [2, 2]))) x
       in [ (e1, clearIndex (T.- m (scalarToTensor e1 [2, 2]))_i_1)
          , (e2, clearIndex (T.- m (scalarToTensor e2 [2, 2]))_i_1) ]
    | _ -> undefined

--
-- LU decomposition
--
M.LU %x :=
  match tensorShape x as list integer with
    | #2 :: #2 :: [] ->
      let L := generateTensor
                 2#(match compare %1 %2 as ordering with
                   | less -> 0
                   | equal -> 1
                   | greater -> b_%1_%2)
                 [2, 2]
          U := generateTensor
                 2#(match compare %1 %2 as ordering with
                   | greater -> 0
                   | _ -> c_%1_%2)
                 [2, 2]
          m := M.* L U
          ret := solve
                   [ (m_1_1, x_1_1, c_1_1)
                   , (m_1_2, x_1_2, c_1_2)
                   , (m_2_1, x_2_1, b_2_1)
                   , (m_2_2, x_2_2, c_2_2) ]
       in (substitute ret L, substitute ret U)
    | #3 :: #3 :: [] ->
      let L := generateTensor
                 2#(match compare %1 %2 as ordering with
                   | less -> 0
                   | equal -> 1
                   | greater -> b_%1_%2)
                 [3, 3]
          U := generateTensor
                 2#(match compare %1 %2 as ordering with
                   | greater -> 0
                   | _ -> c_%1_%2)
                 [3, 3]
          m := M.* L U
          ret := solve
                   [ (m_1_1, x_1_1, c_1_1)
                   , (m_1_2, x_1_2, c_1_2)
                   , (m_1_3, x_1_3, c_1_3)
                   , (m_2_1, x_2_1, b_2_1)
                   , (m_2_2, x_2_2, c_2_2)
                   , (m_2_3, x_2_3, c_2_3)
                   , (m_3_1, x_3_1, b_3_1)
                   , (m_3_2, x_3_2, b_3_2)
                   , (m_3_3, x_3_3, c_3_3) ]
       in (substitute ret L, substitute ret U)
    | _ -> undefined

--
-- Utility
--
generateMatrixFromQuadraticExpr f xs :=
  generateTensor
    2#(coefficient2 f (nth %1 xs) (nth %2 xs))
    [length xs, length xs]
