///|
/// One dimension in the input space. Values are kept in declaration order so
/// generation and exported output remain deterministic.
pub(all) struct Parameter {
  name : String
  values : Array[String]
} derive(Debug, Eq)

///|
pub fn Parameter::new(name : String, values : Array[String]) -> Parameter {
  { name, values }
}

///|
/// A validated ordered collection of parameters.
pub struct Model {
  parameters : Array[Parameter]
}

///|
pub fn Model::new(
  parameters : Array[Parameter],
) -> Result[Model, CaseWeaveError] {
  if parameters.length() == 0 {
    return Err(EmptyModel)
  }
  for i = 0; i < parameters.length(); i = i + 1 {
    let parameter = parameters[i]
    if parameter.name.length() == 0 {
      return Err(EmptyParameterName(i))
    }
    for j = 0; j < i; j = j + 1 {
      if parameters[j].name == parameter.name {
        return Err(DuplicateParameter(parameter.name))
      }
    }
    if parameter.values.length() == 0 {
      return Err(EmptyValues(parameter.name))
    }
    for value_index = 0
        value_index < parameter.values.length()
        value_index = value_index + 1 {
      let value = parameter.values[value_index]
      if value.length() == 0 {
        return Err(EmptyValue(parameter.name, value_index))
      }
      for previous = 0; previous < value_index; previous = previous + 1 {
        if parameter.values[previous] == value {
          return Err(DuplicateValue(parameter.name, value))
        }
      }
    }
  }
  Ok({ parameters, })
}

///|
pub fn Model::parameter_count(self : Model) -> Int {
  self.parameters.length()
}

///|
pub fn Model::parameters(self : Model) -> Array[Parameter] {
  self.parameters.copy()
}

///|
pub fn Model::parameter(self : Model, index : Int) -> Parameter? {
  if index < 0 || index >= self.parameters.length() {
    None
  } else {
    Some(self.parameters[index])
  }
}

///|
pub fn Model::parameter_index(self : Model, name : String) -> Int? {
  for i = 0; i < self.parameters.length(); i = i + 1 {
    if self.parameters[i].name == name {
      return Some(i)
    }
  }
  None
}

///|
pub fn Model::value_index(
  self : Model,
  parameter_index : Int,
  value : String,
) -> Int? {
  if parameter_index < 0 || parameter_index >= self.parameters.length() {
    return None
  }
  let values = self.parameters[parameter_index].values
  for i = 0; i < values.length(); i = i + 1 {
    if values[i] == value {
      return Some(i)
    }
  }
  None
}

///|
pub fn Model::combination_count(self : Model) -> Int64 {
  let mut count = 1L
  for parameter in self.parameters {
    count = count * parameter.values.length().to_int64()
  }
  count
}

///|
/// A concrete test vector in model order.
pub(all) struct TestCase {
  values : Array[String]
} derive(Debug, Eq)

///|
pub fn TestCase::new(values : Array[String]) -> TestCase {
  { values, }
}

///|
pub fn TestCase::get(self : TestCase, index : Int) -> String? {
  if index < 0 || index >= self.values.length() {
    None
  } else {
    Some(self.values[index])
  }
}

///|
pub fn TestCase::value_for(
  self : TestCase,
  model : Model,
  parameter : String,
) -> String? {
  match model.parameter_index(parameter) {
    None => None
    Some(index) => self.get(index)
  }
}

///|
pub fn Model::validate_case(
  self : Model,
  test_case : TestCase,
) -> Result[Unit, CaseWeaveError] {
  if test_case.values.length() != self.parameters.length() {
    return Err(
      InvalidCaseWidth(self.parameters.length(), test_case.values.length()),
    )
  }
  for i = 0; i < self.parameters.length(); i = i + 1 {
    if self.value_index(i, test_case.values[i]) is None {
      return Err(InvalidCaseValue(self.parameters[i].name, test_case.values[i]))
    }
  }
  Ok(())
}

///|
fn Model::decode(self : Model, row : Array[Int]) -> TestCase {
  let values : Array[String] = []
  for i = 0; i < self.parameters.length(); i = i + 1 {
    values.push(self.parameters[i].values[row[i]])
  }
  { values, }
}

///|
fn Model::encode(
  self : Model,
  test_case : TestCase,
) -> Result[Array[Int], CaseWeaveError] {
  match self.validate_case(test_case) {
    Err(error) => Err(error)
    Ok(_) => {
      let row : Array[Int] = []
      for i = 0; i < self.parameters.length(); i = i + 1 {
        match self.value_index(i, test_case.values[i]) {
          None =>
            return Err(
              InvalidCaseValue(self.parameters[i].name, test_case.values[i]),
            )
          Some(value_index) => row.push(value_index)
        }
      }
      Ok(row)
    }
  }
}