///|
fn latex_parens(s : String) -> String {
  "\\left(\{s}\\right)"
}

///|
fn latex_matrix_contents(rows : Array[Array[String]]) -> String {
  let lines = rows.map(row => row.join(" & "))
  let body = lines.join("\\\\")
  "\\begin{matrix}\{body}\\end{matrix}"
}

///|
fn latex_wrap_matrix(rows : Array[Array[String]]) -> String {
  "\\left[\{latex_matrix_contents(rows)}\\right]"
}

///|
fn latex_expr_cell(
  expr : @symcore.Expr,
  settings : @symprint.LatexSettings,
) -> String {
  @symprint.latex_with_settings(expr, settings)
}

///|
pub fn Matrix::latex(
  self : Matrix,
  settings? : @symprint.LatexSettings = @symprint.latex_settings(),
) -> String {
  let rows = self.data.map(row => {
    row.map(expr => latex_expr_cell(expr, settings))
  })
  latex_wrap_matrix(rows)
}

///|
pub fn SparseMatrix::latex(
  self : SparseMatrix,
  settings? : @symprint.LatexSettings = @symprint.latex_settings(),
) -> String {
  self.to_dense().latex(settings~)
}

///|
fn matrix_expr_prec(expr : MatrixExpr) -> Int {
  match expr {
    MatrixExpr::Add(_) => 1
    MatrixExpr::Mul(_) => 2
    _ => 3
  }
}

///|
fn matrix_expr_needs_parens(expr : MatrixExpr) -> Bool {
  match expr {
    MatrixExpr::Add(_) | MatrixExpr::Mul(_) => true
    _ => false
  }
}

///|
fn matrix_symbol_latex(
  name : String,
  settings : @symprint.LatexSettings,
) -> String {
  @symprint.latex_with_settings(@symcore.Expr::Symbol(name), settings)
}

///|
fn latex_matrix_expr_block_diag(
  items : Array[MatrixExpr],
  settings : @symprint.LatexSettings,
) -> String {
  let rows : Array[Array[String]] = []
  for i in 0.. String {
  let rendered = rows.map(row => row.map(item => item.latex(settings~)))
  latex_wrap_matrix(rendered)
}

///|
fn latex_matrix_expr_format(
  expr : MatrixExpr,
  parent_prec : Int,
  settings : @symprint.LatexSettings,
) -> String {
  let prec = matrix_expr_prec(expr)
  let body = match expr {
    MatrixExpr::Concrete(m) => m.latex(settings~)
    MatrixExpr::Symbol(name, _, _) => matrix_symbol_latex(name, settings)
    MatrixExpr::Identity(_) => "\\mathbb{I}"
    MatrixExpr::Zero(_, _) => "0"
    MatrixExpr::Add(items) =>
      items
      .map(item => latex_matrix_expr_format(item, prec, settings))
      .join(" + ")
    MatrixExpr::Mul(items) => {
      let rendered : Array[String] = []
      for item in items {
        let tex = latex_matrix_expr_format(item, prec, settings)
        rendered.push(
          if matrix_expr_needs_parens(item) {
            latex_parens(tex)
          } else {
            tex
          },
        )
      }
      rendered.join(" ")
    }
    MatrixExpr::BlockDiag(items) =>
      latex_matrix_expr_block_diag(items, settings)
    MatrixExpr::Block(rows) => latex_matrix_expr_block(rows, settings)
    MatrixExpr::Transpose(inner) => {
      let inner_tex = inner.latex(settings~)
      match inner {
        MatrixExpr::Symbol(_, _, _) => "\{inner_tex}^{T}"
        _ => "\\left(\{inner_tex}\\right)^{T}"
      }
    }
    MatrixExpr::Inverse(inner) => {
      let inner_tex = inner.latex(settings~)
      match inner {
        MatrixExpr::Symbol(_, _, _) => "\{inner_tex}^{-1}"
        _ => "\\left(\{inner_tex}\\right)^{-1}"
      }
    }
    MatrixExpr::Pow(inner, exp) => {
      let inner_tex = inner.latex(settings~)
      match inner {
        MatrixExpr::Symbol(_, _, _) => "\{inner_tex}^{\{exp.to_string()}}"
        _ => "\\left(\{inner_tex}\\right)^{\{exp.to_string()}}"
      }
    }
  }
  if prec < parent_prec {
    latex_parens(body)
  } else {
    body
  }
}

///|
pub fn MatrixExpr::latex(
  self : MatrixExpr,
  settings? : @symprint.LatexSettings = @symprint.latex_settings(),
) -> String {
  latex_matrix_expr_format(self, 0, settings)
}