///|
pub(all) struct SectionalReleaseResult {
  sections_unlocked : Array[@topology.TrackId]
  switches_unlocked : Array[@topology.TurnoutId]
  is_route_completed : Bool
} derive(Debug)

///|
fn ActiveRouteState::observe_occupancy(
  self : ActiveRouteState,
  station : @topology.Station,
) -> Unit {
  let had_observed_entry = if self.rule.track_sections.length() == 0 {
    false
  } else {
    self.was_occupied(self.rule.track_sections[0])
  }
  let mut index = 0
  while index < self.rule.track_sections.length() {
    let track_id = self.rule.track_sections[index]
    match station.graph.get_track(track_id) {
      Some(track) =>
        if track.state == Occupied {
          if track_id.id >= 0 && track_id.id < 256 {
            self.occupied_sections[track_id.id] = true
          }
          if index > self.highest_observed_section_index + 1 {
            self.occupancy_order_violation = true
          }
          if index > 0 && !had_observed_entry {
            self.occupancy_order_violation = true
          }
          if index > self.highest_observed_section_index {
            self.highest_observed_section_index = index
          }
          if track.occupying_train_id != "" {
            if self.observed_train_id == "" {
              self.observed_train_id = track.occupying_train_id
            } else if self.observed_train_id != track.occupying_train_id {
              self.occupancy_order_violation = true
            }
          }
          if index == 0 {
            let _ = station.set_signal_aspect(self.rule.entry_signal, Red)
            self.state = TrainInRoute
            self.release_stage = StageEntrySectionOccupied
          }
        }
      None => ()
    }
    index = index + 1
  }
}

///|
fn ActiveRouteState::was_occupied(
  self : ActiveRouteState,
  track_id : @topology.TrackId,
) -> Bool {
  if track_id.id < 0 || track_id.id >= 256 {
    false
  } else {
    self.occupied_sections[track_id.id]
  }
}

///|
fn ActiveRouteState::was_unlocked(
  self : ActiveRouteState,
  track_id : @topology.TrackId,
) -> Bool {
  if track_id.id < 0 || track_id.id >= 256 {
    false
  } else {
    self.unlocked_sections[track_id.id]
  }
}

///|
fn ActiveRouteState::can_release_section(
  self : ActiveRouteState,
  station : @topology.Station,
  index : Int,
) -> Bool {
  let sections = self.rule.track_sections
  let current_id = sections[index]
  if self.occupancy_order_violation {
    return false
  }
  let current_clear = match station.graph.get_track(current_id) {
    Some(track) => track.state == Clear
    None => false
  }
  if !current_clear || !self.was_occupied(current_id) {
    return false
  }

  if index == 0 {
    if self.state != TrainInRoute {
      return false
    }
    if sections.length() == 1 {
      return true
    }
    let next_id = sections[1]
    match station.graph.get_track(next_id) {
      Some(next_track) =>
        next_track.state == Occupied ||
        (next_track.state == Clear && self.was_occupied(next_id))
      None => false
    }
  } else {
    let previous_id = sections[index - 1]
    self.was_unlocked(previous_id)
  }
}

///|
fn ActiveRouteState::mark_released(
  self : ActiveRouteState,
  track_id : @topology.TrackId,
  index : Int,
) -> Unit {
  if track_id.id >= 0 && track_id.id < 256 {
    self.unlocked_sections[track_id.id] = true
  }
  if index == 0 {
    self.release_stage = StageEntrySectionOccupied
  } else if index == self.rule.track_sections.length() - 1 {
    self.release_stage = StageExitSectionOccupied
  } else {
    self.release_stage = StageMiddleSectionOccupied
  }
}

///|
fn ActiveRouteState::all_sections_released(self : ActiveRouteState) -> Bool {
  for track_id in self.rule.track_sections {
    if !self.was_unlocked(track_id) {
      return false
    }
  }
  true
}

///|
pub fn InterlockingTable::process_sectional_release(
  self : InterlockingTable,
  station : @topology.Station,
  route_id : RouteId,
) -> SectionalReleaseResult {
  let unlocked_sections : Array[@topology.TrackId] = []
  let unlocked_switches : Array[@topology.TurnoutId] = []

  match self.get_active_route(route_id) {
    None =>
      {
        sections_unlocked: [],
        switches_unlocked: [],
        is_route_completed: false,
      }
    Some(active_route) => {
      let rule = active_route.rule
      let sections = rule.track_sections
      if active_route.state == RouteFault {
        return {
          sections_unlocked: [],
          switches_unlocked: [],
          is_route_completed: false,
        }
      }
      if sections.length() == 0 {
        active_route.release_stage = StageFullyReleased
        active_route.state = RouteInactive
        self.deactivate_route(route_id)
        let _ = station.set_signal_aspect(rule.entry_signal, Red)
        let _ = station.unlock_signal(rule.entry_signal)
        return {
          sections_unlocked: [],
          switches_unlocked: [],
          is_route_completed: true,
        }
      }

      active_route.observe_occupancy(station)
      let mut i = 0
      while i < sections.length() {
        let track_id = sections[i]
        if !active_route.was_unlocked(track_id) &&
          active_route.can_release_section(station, i) {
          active_route.mark_released(track_id, i)
          unlocked_sections.push(track_id)
        }
        i = i + 1
      }

      if active_route.all_sections_released() {
        for switch_requirement in rule.switches {
          let _ = station.unlock_turnout(switch_requirement.turnout_id)
          unlocked_switches.push(switch_requirement.turnout_id)
        }
        for flank in rule.flank_switches {
          let _ = station.unlock_turnout(flank.turnout_id)
          unlocked_switches.push(flank.turnout_id)
        }
        active_route.release_stage = StageFullyReleased
        active_route.state = RouteInactive
        self.deactivate_route(route_id)
        let _ = station.set_signal_aspect(rule.entry_signal, Red)
        let _ = station.unlock_signal(rule.entry_signal)
        {
          sections_unlocked: unlocked_sections,
          switches_unlocked: unlocked_switches,
          is_route_completed: true,
        }
      } else {
        {
          sections_unlocked: unlocked_sections,
          switches_unlocked: unlocked_switches,
          is_route_completed: false,
        }
      }
    }
  }
}

///|
pub fn InterlockingTable::process_sectional_release_fast(
  self : InterlockingTable,
  station : @topology.Station,
  route_id : RouteId,
) -> ReleaseCounters {
  let counters = ReleaseCounters::empty()
  match self.get_active_route(route_id) {
    None => counters
    Some(active_route) => {
      let rule = active_route.rule
      let sections = rule.track_sections
      if active_route.state == RouteFault {
        return counters
      }
      if sections.length() == 0 {
        active_route.release_stage = StageFullyReleased
        active_route.state = RouteInactive
        self.deactivate_route(route_id)
        let _ = station.set_signal_aspect(rule.entry_signal, Red)
        let _ = station.unlock_signal(rule.entry_signal)
        counters.is_route_completed = true
        return counters
      }

      active_route.observe_occupancy(station)
      if active_route.state == TrainInRoute {
        counters.entered_route = true
      }

      let mut i = 0
      while i < sections.length() {
        let track_id = sections[i]
        if !active_route.was_unlocked(track_id) &&
          active_route.can_release_section(station, i) {
          active_route.mark_released(track_id, i)
          counters.sections_unlocked = counters.sections_unlocked + 1
        }
        i = i + 1
      }

      if active_route.all_sections_released() {
        for switch_requirement in rule.switches {
          let _ = station.unlock_turnout(switch_requirement.turnout_id)
          counters.switches_unlocked = counters.switches_unlocked + 1
        }
        for flank in rule.flank_switches {
          let _ = station.unlock_turnout(flank.turnout_id)
          counters.switches_unlocked = counters.switches_unlocked + 1
        }
        active_route.release_stage = StageFullyReleased
        active_route.state = RouteInactive
        self.deactivate_route(route_id)
        let _ = station.set_signal_aspect(rule.entry_signal, Red)
        let _ = station.unlock_signal(rule.entry_signal)
        counters.is_route_completed = true
      }
      counters
    }
  }
}