///|
pub(all) struct Station {
  name : String
  graph : StationGraph
  mut next_node_id : Int
  mut next_edge_id : Int
  mut next_track_id : Int
  mut next_turnout_id : Int
  mut next_signal_id : Int
} derive(Debug)

///|
pub fn Station::new(name : String) -> Station {
  {
    name,
    graph: StationGraph::new(),
    next_node_id: 1,
    next_edge_id: 1,
    next_track_id: 1,
    next_turnout_id: 1,
    next_signal_id: 1,
  }
}

///|
pub fn Station::create_node(
  self : Station,
  name : String,
  node_type : NodeType,
) -> NodeId {
  let id = NodeId::new(self.next_node_id)
  self.next_node_id = self.next_node_id + 1
  let node = TopologyNode::new(id, name, node_type)
  self.graph.add_node(node)
  id
}

///|
pub fn Station::create_track(
  self : Station,
  name : String,
  length : Double,
  speed : Double,
  start_node : NodeId,
  end_node : NodeId,
  direction : TrackDirection,
) -> TrackId {
  let track_id = TrackId::new(self.next_track_id)
  self.next_track_id = self.next_track_id + 1

  let track = TrackSegment::new(
    track_id, name, length, speed, start_node, end_node, direction,
  )
  self.graph.add_track(track)

  let edge_id1 = EdgeId::new(self.next_edge_id)
  self.next_edge_id = self.next_edge_id + 1
  let edge1 = TopologyEdge::new(
    edge_id1, start_node, end_node, track_id, length, direction,
  )
  self.graph.add_edge(edge1)

  if direction == BiDirectional {
    let edge_id2 = EdgeId::new(self.next_edge_id)
    self.next_edge_id = self.next_edge_id + 1
    let edge2 = TopologyEdge::new(
      edge_id2, end_node, start_node, track_id, length, direction,
    )
    self.graph.add_edge(edge2)
  }

  track_id
}

///|
pub fn Station::create_turnout(
  self : Station,
  name : String,
  base_node : NodeId,
  normal_next_node : NodeId,
  reverse_next_node : NodeId,
) -> TurnoutId {
  let t_id = TurnoutId::new(self.next_turnout_id)
  self.next_turnout_id = self.next_turnout_id + 1

  let turnout = Turnout::new(
    t_id, name, base_node, normal_next_node, reverse_next_node,
  )
  self.graph.add_turnout(turnout)
  for node_id in [base_node, normal_next_node, reverse_next_node] {
    match self.graph.get_node(node_id) {
      Some(node) => node.associated_turnout = Some(t_id)
      None => ()
    }
  }
  t_id
}

///|
pub fn Station::create_signal(
  self : Station,
  name : String,
  signal_type : SignalType,
  bound_node : NodeId,
  direction : TrackDirection,
) -> SignalId {
  let s_id = SignalId::new(self.next_signal_id)
  self.next_signal_id = self.next_signal_id + 1

  let signal = Signal::new(s_id, name, signal_type, bound_node, direction)
  self.graph.add_signal(signal)
  match self.graph.get_node(bound_node) {
    Some(node) => node.associated_signal = Some(s_id)
    None => ()
  }
  s_id
}

///|
pub fn Station::set_turnout_position(
  self : Station,
  t_id : TurnoutId,
  pos : TurnoutPosition,
) -> Result[Unit, String] {
  match self.graph.get_turnout(t_id) {
    None => Err("Turnout not found: " + t_id.id.to_string())
    Some(t) =>
      if t.is_locked {
        Err("Cannot throw turnout " + t.name + ": it is locked by route!")
      } else if t.is_in_fault {
        Err("Cannot throw turnout " + t.name + ": turnout in fault state!")
      } else {
        t.position = pos
        t.target_position = pos
        Ok(())
      }
  }
}

///|
pub fn Station::lock_turnout(
  self : Station,
  t_id : TurnoutId,
) -> Result[Unit, String] {
  match self.graph.get_turnout(t_id) {
    None => Err("Turnout not found: " + t_id.id.to_string())
    Some(t) => {
      t.is_locked = true
      Ok(())
    }
  }
}

///|
pub fn Station::unlock_turnout(
  self : Station,
  t_id : TurnoutId,
) -> Result[Unit, String] {
  match self.graph.get_turnout(t_id) {
    None => Err("Turnout not found: " + t_id.id.to_string())
    Some(t) => {
      t.is_locked = false
      Ok(())
    }
  }
}

///|
pub fn Station::set_track_state(
  self : Station,
  tr_id : TrackId,
  state : TrackState,
  train_id : String,
) -> Result[Unit, String] {
  match self.graph.get_track(tr_id) {
    None => Err("Track segment not found: " + tr_id.id.to_string())
    Some(track) => {
      track.state = state
      track.occupying_train_id = train_id
      Ok(())
    }
  }
}

///|
pub fn Station::set_signal_aspect(
  self : Station,
  s_id : SignalId,
  aspect : SignalAspect,
) -> Result[Unit, String] {
  match self.graph.get_signal(s_id) {
    None => Err("Signal not found: " + s_id.id.to_string())
    Some(sig) => {
      sig.current_aspect = aspect
      sig.requested_aspect = aspect
      Ok(())
    }
  }
}

///|
pub fn Station::lock_signal(
  self : Station,
  s_id : SignalId,
) -> Result[Unit, String] {
  match self.graph.get_signal(s_id) {
    None => Err("Signal not found: " + s_id.id.to_string())
    Some(sig) => {
      sig.is_locked = true
      Ok(())
    }
  }
}

///|
pub fn Station::unlock_signal(
  self : Station,
  s_id : SignalId,
) -> Result[Unit, String] {
  match self.graph.get_signal(s_id) {
    None => Err("Signal not found: " + s_id.id.to_string())
    Some(sig) => {
      sig.is_locked = false
      Ok(())
    }
  }
}