///|
fn full_horizon(days : Int) -> TimeRange {
  TimeRange::new(0, days * 1440)
}

///|
fn day_shift(day : Int, prefix : String) -> Shift {
  let start = day * 1440 + 8 * 60
  Shift::new(
    prefix + "-day-" + (day + 1).to_string(),
    "Day " + (day + 1).to_string(),
    start,
    start + 8 * 60,
    2,
  )
  .with_kind(Day)
  .with_weekend(day % 7 == 5 || day % 7 == 6)
}

///|
fn evening_shift(day : Int, prefix : String) -> Shift {
  let start = day * 1440 + 16 * 60
  Shift::new(
    prefix + "-evening-" + (day + 1).to_string(),
    "Evening " + (day + 1).to_string(),
    start,
    start + 6 * 60,
    1,
  )
  .with_kind(Evening)
  .with_weekend(day % 7 == 5 || day % 7 == 6)
}

///|
fn night_shift(day : Int, prefix : String) -> Shift {
  let start = day * 1440 + 22 * 60
  Shift::new(
    prefix + "-night-" + (day + 1).to_string(),
    "Night " + (day + 1).to_string(),
    start,
    start + 8 * 60,
    1,
  )
  .with_kind(Night)
  .with_weekend(day % 7 == 5 || day % 7 == 6)
}

///|
fn standard_worker(
  id : String,
  name : String,
  days : Int,
  target : Int,
) -> Worker {
  Worker::new(id, name)
  .add_availability(full_horizon(days))
  .with_limits(0, target, target + 960)
  .with_rest_rules(5, 600)
}

///|
pub fn clinic_scenario() -> PlanningProblem {
  let days = 7
  let nurse = Skill::new("nurse", "Registered nurse")
  let triage = Skill::new("triage", "Triage")
  let lead = Skill::new("lead", "Shift leadership")
  let workers : Array[Worker] = [
    standard_worker("ada", "Ada", days + 1, 2400)
    .add_skill(WorkerSkill::new("nurse", Expert))
    .add_skill(WorkerSkill::new("triage", Advanced))
    .add_skill(WorkerSkill::new("lead", Advanced)),
    standard_worker("grace", "Grace", days + 1, 2400)
    .add_skill(WorkerSkill::new("nurse", Advanced))
    .add_skill(WorkerSkill::new("triage", Expert)),
    standard_worker("linus", "Linus", days + 1, 2160)
    .add_skill(WorkerSkill::new("nurse", Qualified))
    .add_skill(WorkerSkill::new("lead", Qualified)),
    standard_worker("margaret", "Margaret", days + 1, 2160)
    .add_skill(WorkerSkill::new("nurse", Expert))
    .add_skill(WorkerSkill::new("triage", Qualified)),
    standard_worker("alan", "Alan", days + 1, 1920).add_skill(
      WorkerSkill::new("nurse", Qualified),
    ),
    standard_worker("barbara", "Barbara", days + 1, 1920)
    .add_skill(WorkerSkill::new("nurse", Advanced))
    .add_skill(WorkerSkill::new("lead", Expert)),
    standard_worker("edsger", "Edsger", days + 1, 1920)
    .add_skill(WorkerSkill::new("nurse", Qualified))
    .add_skill(WorkerSkill::new("triage", Advanced)),
  ]
  let mut problem = PlanningProblem::new("Community clinic weekly roster")
    .add_skill(nurse)
    .add_skill(triage)
    .add_skill(lead)
  for worker in workers {
    problem = problem.add_worker(worker)
  }
  for day = 0; day < days; day = day + 1 {
    let daytime = day_shift(day, "clinic")
      .add_requirement(SkillRequirement::new("nurse", Qualified, 2))
      .add_requirement(SkillRequirement::new("triage", Qualified, 1))
    let evening = evening_shift(day, "clinic").add_requirement(
      SkillRequirement::new("nurse", Qualified, 1),
    )
    let overnight = night_shift(day, "clinic").add_requirement(
      SkillRequirement::new("nurse", Qualified, 1),
    )
    problem = problem.add_shift(daytime).add_shift(evening).add_shift(overnight)
  }
  problem
}

///|
pub fn volunteer_scenario() -> PlanningProblem {
  let days = 3
  let registration = Skill::new("registration", "Registration desk")
  let first_aid = Skill::new("first-aid", "First aid")
  let logistics = Skill::new("logistics", "Logistics")
  let workers : Array[Worker] = [
    standard_worker("v1", "Xiaoming", days, 720).add_skill(
      WorkerSkill::new("registration", Advanced),
    ),
    standard_worker("v2", "Xiaohong", days, 720).add_skill(
      WorkerSkill::new("first-aid", Qualified),
    ),
    standard_worker("v3", "Xiaolan", days, 720).add_skill(
      WorkerSkill::new("logistics", Expert),
    ),
    standard_worker("v4", "Xiaoyu", days, 720)
    .add_skill(WorkerSkill::new("registration", Qualified))
    .add_skill(WorkerSkill::new("first-aid", Qualified)),
    standard_worker("v5", "Xiaozhou", days, 720).add_skill(
      WorkerSkill::new("logistics", Advanced),
    ),
    standard_worker("v6", "Xiaoli", days, 720).add_skill(
      WorkerSkill::new("registration", Expert),
    ),
  ]
  let mut problem = PlanningProblem::new("Three-day community event")
    .add_skill(registration)
    .add_skill(first_aid)
    .add_skill(logistics)
  for worker in workers {
    problem = problem.add_worker(worker)
  }
  for day = 0; day < days; day = day + 1 {
    let morning_start = day * 1440 + 8 * 60
    let afternoon_start = day * 1440 + 13 * 60
    let morning = Shift::new(
        "event-morning-" + day.to_string(),
        "Event morning " + (day + 1).to_string(),
        morning_start,
        morning_start + 4 * 60,
        2,
      )
      .with_kind(Morning)
      .add_requirement(SkillRequirement::new("registration", Qualified, 1))
    let afternoon = Shift::new(
        "event-afternoon-" + day.to_string(),
        "Event afternoon " + (day + 1).to_string(),
        afternoon_start,
        afternoon_start + 5 * 60,
        2,
      )
      .with_kind(Day)
      .add_requirement(SkillRequirement::new("first-aid", Qualified, 1))
      .add_requirement(SkillRequirement::new("logistics", Qualified, 1))
    problem = problem.add_shift(morning).add_shift(afternoon)
  }
  problem
}

///|
pub fn operations_scenario() -> PlanningProblem {
  let days = 5
  let linux = Skill::new("linux", "Linux operations")
  let database = Skill::new("database", "Database operations")
  let incident = Skill::new("incident", "Incident command")
  let workers : Array[Worker] = [
    standard_worker("op-ada", "Ada", days + 1, 1800)
    .add_skill(WorkerSkill::new("linux", Expert))
    .add_skill(WorkerSkill::new("incident", Advanced)),
    standard_worker("op-grace", "Grace", days + 1, 1800)
    .add_skill(WorkerSkill::new("database", Expert))
    .add_skill(WorkerSkill::new("incident", Qualified)),
    standard_worker("op-linus", "Linus", days + 1, 1800).add_skill(
      WorkerSkill::new("linux", Expert),
    ),
    standard_worker("op-ken", "Ken", days + 1, 1800)
    .add_skill(WorkerSkill::new("linux", Advanced))
    .add_skill(WorkerSkill::new("database", Advanced)),
    standard_worker("op-margaret", "Margaret", days + 1, 1800)
    .add_skill(WorkerSkill::new("database", Qualified))
    .add_skill(WorkerSkill::new("incident", Expert)),
  ]
  let mut problem = PlanningProblem::new("Operations on-call rotation")
    .add_skill(linux)
    .add_skill(database)
    .add_skill(incident)
  for worker in workers {
    problem = problem.add_worker(worker)
  }
  for day = 0; day < days; day = day + 1 {
    let start = day * 1440 + 18 * 60
    let primary = Shift::new(
        "primary-" + day.to_string(),
        "Primary on-call " + (day + 1).to_string(),
        start,
        start + 12 * 60,
        1,
      )
      .with_kind(OnCall)
      .add_requirement(SkillRequirement::new("linux", Advanced, 1))
    let backup = Shift::new(
        "backup-" + day.to_string(),
        "Backup on-call " + (day + 1).to_string(),
        start,
        start + 12 * 60,
        1,
      )
      .with_kind(OnCall)
      .add_requirement(SkillRequirement::new("database", Qualified, 1))
    problem = problem.add_shift(primary).add_shift(backup)
  }
  problem
}

///|
pub fn scenario_by_name(name : String) -> PlanningProblem? {
  match name.trim().to_lower() {
    "clinic" => Some(clinic_scenario())
    "volunteer" => Some(volunteer_scenario())
    "operations" => Some(operations_scenario())
    _ => None
  }
}

///|
pub fn scenario_names() -> Array[String] {
  ["clinic", "volunteer", "operations"]
}