///|
pub struct ExpressionStats {
  atoms : Int
  and_operators : Int
  or_operators : Int
  depth : Int
  alternatives : Int
} derive(Eq, @debug.Debug)

///|
pub fn ExpressionStats::atoms(self : ExpressionStats) -> Int {
  self.atoms
}

///|
pub fn ExpressionStats::and_operators(self : ExpressionStats) -> Int {
  self.and_operators
}

///|
pub fn ExpressionStats::or_operators(self : ExpressionStats) -> Int {
  self.or_operators
}

///|
pub fn ExpressionStats::depth(self : ExpressionStats) -> Int {
  self.depth
}

///|
pub fn ExpressionStats::alternatives(self : ExpressionStats) -> Int {
  self.alternatives
}

///|
fn capped_product(left : Int, right : Int) -> Int {
  if left > 64 || right > 64 || left * right > 64 {
    65
  } else {
    left * right
  }
}

///|
pub fn Expression::stats(self : Expression) -> ExpressionStats {
  match self {
    Atom(_) =>
      { atoms: 1, and_operators: 0, or_operators: 0, depth: 1, alternatives: 1 }
    And(left, right) => {
      let a = left.stats()
      let b = right.stats()
      {
        atoms: a.atoms + b.atoms,
        and_operators: a.and_operators + b.and_operators + 1,
        or_operators: a.or_operators + b.or_operators,
        depth: 1 + max_int(a.depth, b.depth),
        alternatives: capped_product(a.alternatives, b.alternatives),
      }
    }
    Or(left, right) => {
      let a = left.stats()
      let b = right.stats()
      {
        atoms: a.atoms + b.atoms,
        and_operators: a.and_operators + b.and_operators,
        or_operators: a.or_operators + b.or_operators + 1,
        depth: 1 + max_int(a.depth, b.depth),
        alternatives: min_int(65, a.alternatives + b.alternatives),
      }
    }
  }
}

///|
fn combine_alternatives(
  left : Array[Array[LicenseAtom]],
  right : Array[Array[LicenseAtom]],
) -> Result[Array[Array[LicenseAtom]], Diagnostic] {
  let result = []
  for first in left {
    for second in right {
      if result.length() >= 64 {
        return Err(
          Diagnostic::new(
            "expression.alternatives.limit", "expression", "expanded alternatives exceed the safety limit",
            "at most 64 alternatives", "more than 64",
          ),
        )
      }
      let combined = first.copy()
      for atom in second {
        combined.push(atom)
      }
      result.push(combined)
    }
  }
  Ok(result)
}

///|
fn expand_expression(
  expression : Expression,
) -> Result[Array[Array[LicenseAtom]], Diagnostic] {
  match expression {
    Atom(atom) => Ok([[atom]])
    Or(left, right) => {
      let first = match expand_expression(left) {
        Ok(value) => value
        Err(error) => return Err(error)
      }
      let second = match expand_expression(right) {
        Ok(value) => value
        Err(error) => return Err(error)
      }
      if first.length() + second.length() > 64 {
        return Err(
          Diagnostic::new(
            "expression.alternatives.limit",
            "expression",
            "expanded alternatives exceed the safety limit",
            "at most 64 alternatives",
            (first.length() + second.length()).to_string(),
          ),
        )
      }
      Ok(first + second)
    }
    And(left, right) => {
      let first = match expand_expression(left) {
        Ok(value) => value
        Err(error) => return Err(error)
      }
      let second = match expand_expression(right) {
        Ok(value) => value
        Err(error) => return Err(error)
      }
      combine_alternatives(first, second)
    }
  }
}

///|
/// Expand an expression into at most 64 conjunctive alternatives.
pub fn Expression::alternatives(
  self : Expression,
) -> Result[Array[Array[LicenseAtom]], Diagnostic] {
  expand_expression(self)
}

///|
pub fn render_alternative(atoms : Array[LicenseAtom]) -> String {
  let values = atoms.map(fn(atom) { atom.canonical() })
  join_text(values, " AND ")
}

///|
fn max_int(left : Int, right : Int) -> Int {
  if left > right {
    left
  } else {
    right
  }
}

///|
fn min_int(left : Int, right : Int) -> Int {
  if left < right {
    left
  } else {
    right
  }
}