///|
pub struct SignalingStateMachine {
  mut state : SignalingState
  mut current_local : SessionDescription?
  mut current_remote : SessionDescription?
  mut pending_local : SessionDescription?
  mut pending_remote : SessionDescription?
}

///|
pub fn SignalingStateMachine::new() -> SignalingStateMachine {
  {
    state: Stable,
    current_local: None,
    current_remote: None,
    pending_local: None,
    pending_remote: None,
  }
}

///|
pub fn SignalingStateMachine::state(
  self : SignalingStateMachine,
) -> SignalingState {
  self.state
}

///|
pub fn SignalingStateMachine::local_description(
  self : SignalingStateMachine,
) -> SessionDescription? {
  match self.pending_local {
    Some(description) => Some(description)
    None => self.current_local
  }
}

///|
pub fn SignalingStateMachine::remote_description(
  self : SignalingStateMachine,
) -> SessionDescription? {
  match self.pending_remote {
    Some(description) => Some(description)
    None => self.current_remote
  }
}

///|
pub fn SignalingStateMachine::current_local_description(
  self : SignalingStateMachine,
) -> SessionDescription? {
  self.current_local
}

///|
pub fn SignalingStateMachine::current_remote_description(
  self : SignalingStateMachine,
) -> SessionDescription? {
  self.current_remote
}

///|
pub fn SignalingStateMachine::pending_local_description(
  self : SignalingStateMachine,
) -> SessionDescription? {
  self.pending_local
}

///|
pub fn SignalingStateMachine::pending_remote_description(
  self : SignalingStateMachine,
) -> SessionDescription? {
  self.pending_remote
}

///|
fn validate_description(
  description : SessionDescription,
) -> Unit raise SdpError {
  match description.sdp_type {
    Rollback =>
      if !description.sdp.is_empty() {
        raise InvalidSyntax("rollback description must have empty SDP")
      }
    Offer | Pranswer | Answer => ignore(description.datachannel_parameters())
  }
}

///|
fn SignalingStateMachine::commit_pending(self : SignalingStateMachine) -> Unit {
  match self.pending_local {
    Some(description) => self.current_local = Some(description)
    None => ()
  }
  match self.pending_remote {
    Some(description) => self.current_remote = Some(description)
    None => ()
  }
  self.pending_local = None
  self.pending_remote = None
  self.state = Stable
}

///|
fn SignalingStateMachine::rollback_pending(
  self : SignalingStateMachine,
) -> Unit {
  self.pending_local = None
  self.pending_remote = None
  self.state = Stable
}

///|
pub fn SignalingStateMachine::set_local_description(
  self : SignalingStateMachine,
  description : SessionDescription,
) -> Unit raise SdpError {
  if self.state == Closed {
    raise InvalidState("cannot set a description after close")
  }
  validate_description(description)
  match (self.state, description.sdp_type) {
    (Stable, Offer) => {
      self.pending_local = Some(description)
      self.state = HaveLocalOffer
    }
    (HaveRemoteOffer, Pranswer) => {
      self.pending_local = Some(description)
      self.state = HaveLocalPranswer
    }
    (HaveRemoteOffer | HaveLocalPranswer, Answer) => {
      self.pending_local = Some(description)
      self.commit_pending()
    }
    (HaveLocalOffer | HaveRemotePranswer, Rollback) => self.rollback_pending()
    _ => raise InvalidState("invalid local-description signaling transition")
  }
}

///|
pub fn SignalingStateMachine::set_remote_description(
  self : SignalingStateMachine,
  description : SessionDescription,
) -> Unit raise SdpError {
  if self.state == Closed {
    raise InvalidState("cannot set a description after close")
  }
  validate_description(description)
  match (self.state, description.sdp_type) {
    (Stable, Offer) => {
      self.pending_remote = Some(description)
      self.state = HaveRemoteOffer
    }
    (HaveLocalOffer, Pranswer) => {
      self.pending_remote = Some(description)
      self.state = HaveRemotePranswer
    }
    (HaveLocalOffer | HaveRemotePranswer, Answer) => {
      self.pending_remote = Some(description)
      self.commit_pending()
    }
    (HaveRemoteOffer | HaveLocalPranswer, Rollback) => self.rollback_pending()
    _ => raise InvalidState("invalid remote-description signaling transition")
  }
}

///|
pub fn SignalingStateMachine::close(self : SignalingStateMachine) -> Unit {
  self.pending_local = None
  self.pending_remote = None
  self.state = Closed
}