///|
/// Create an application-facing deadline request.
pub fn DeadlineRequest::new(
  id~ : String,
  start~ : Date,
  rule~ : DateRule,
  start_rule~ : StartDateRule,
  calendar~ : Calendar,
  extension~ : ExtensionPolicy,
) -> DeadlineRequest {
  { id, start, rule, start_rule, calendar, extension }
}

///|
fn DeadlineRequest::validate(
  self : DeadlineRequest,
) -> Result[Unit, ServiceError] {
  if self.id.length() == 0 {
    return Err(ServiceInvalidId)
  }
  match self.rule.validate() {
    Ok(_) => Ok(())
    Err(error) => Err(ServiceInvalidRule(error))
  }
}

///|
/// Validate and calculate one request into a trackable record.
pub fn DeadlineService::evaluate(
  request : DeadlineRequest,
) -> Result[DeadlineRecord, ServiceError] {
  match request.validate() {
    Ok(_) => ()
    Err(error) => return Err(error)
  }
  let deadline = Deadline::new(
    start_date=request.start,
    rule=request.rule,
    start_rule=request.start_rule,
    calendar=request.calendar,
    extension=request.extension,
  )
  match deadline.calculate() {
    Ok(result) => Ok(DeadlineRecord::new(request.id, result))
    Err(error) => Err(ServiceDeadlineFailure(error))
  }
}

///|
pub enum LedgerError {
  LedgerDuplicate(String)
} derive(Debug, Eq, Compare)

///|
pub struct DeadlineLedger {
  records : Array[DeadlineRecord]
}

///|
/// Create an empty in-memory deadline ledger.
pub fn DeadlineLedger::new() -> DeadlineLedger {
  { records: [] }
}

///|
/// Add a record with a unique application identifier.
pub fn DeadlineLedger::add(
  self : DeadlineLedger,
  record : DeadlineRecord,
) -> Result[DeadlineLedger, LedgerError] {
  for existing in self.records {
    if existing.id() == record.id() {
      return Err(LedgerDuplicate(record.id()))
    }
  }
  let records = self.records.copy()
  records.push(record)
  Ok({ records, })
}

///|
/// Find a record by application identifier.
pub fn DeadlineLedger::find(
  self : DeadlineLedger,
  id : String,
) -> DeadlineRecord? {
  for record in self.records {
    if record.id() == id {
      return Some(record)
    }
  }
  None
}

///|
fn records_with_status(
  records : Array[DeadlineRecord],
  as_of : Date,
  status : DeadlineStatus,
) -> Array[String] {
  let ids = Array::new()
  for record in records {
    if record.status(as_of) == status {
      ids.push(record.id())
    }
  }
  ids
}

///|
/// Return identifiers due on the supplied date.
pub fn DeadlineLedger::due(
  self : DeadlineLedger,
  as_of : Date,
) -> Array[String] {
  records_with_status(self.records, as_of, DueToday)
}

///|
/// Return identifiers past due and not completed.
pub fn DeadlineLedger::overdue(
  self : DeadlineLedger,
  as_of : Date,
) -> Array[String] {
  records_with_status(self.records, as_of, Overdue)
}

///|
/// Return identifiers with completion evidence.
pub fn DeadlineLedger::completed(
  self : DeadlineLedger,
  as_of : Date,
) -> Array[String] {
  records_with_status(self.records, as_of, Completed)
}