///|
enum Located[T, L] {
  Wrap(T)
  Empty
}

///|
fn expect[T, L](self : Located[T, L]) -> T {
  match self {
    Located::Wrap(t) => t
    Located::Empty =>
      abort(
        "Bug: unwrapping an empty value. This should never happen. " + CONTACT_AUTHOR,
      )
  }
}

///|
pub trait Location: Show + Hash {
  name(Self) -> String
}

///|
impl Eq for &Location with op_equal(self, other) {
  self.name() == other.name()
}

///|
struct Unwrapper[L] {}

///|
pub fn unwrap[T, L](self : Unwrapper[L], v : Located[T, L]) -> T {
  expect(v)
}

///|
struct ChoreoContext {
  location : &Location
  channels : Map[(&Location, &Location), @channel.T[Json]]
}

enum NetworkConfig {
  LocalConfig(Map[(&Location, &Location), @channel.T[Json]])
  HttpConfig // TODO: 
}

pub fn make_local_config(locations: Array[&Location]) -> NetworkConfig {
  let channels = {}
  for from in locations {
    for to in locations {
      if from != to {
        let channel = @channel.make()
        channels[(from, to)] = channel
      }
    }
  }
  return LocalConfig(channels)
}

pub async fn run_choreo[T, L: Location](config: NetworkConfig, choreography: async (ChoreoContext) -> T, role: L) -> T {
  match config {
    LocalConfig(channels) => {
      let context = ChoreoContext::{
        location: role,
        channels: channels
      }
      choreography!(context)
    }
    _ => {
      abort("Unsupported network configuration")
    }
  }
}

///|
pub fn locally[T, L : Location](
  self : ChoreoContext,
  location : L,
  computation : (Unwrapper[L]) -> T
) -> Located[T, L] {
  if location as &Location == self.location {
    let unwrapper = Unwrapper::{  }
    let result = computation(unwrapper)
    return Located::Wrap(result)
  } else {
    return Located::Empty
  }
}

///|
pub async fn comm[T : @json.FromJson + ToJson, From : Location, To : Location](
  self : ChoreoContext,
  from : From,
  to : To,
  value : Located[T, From]
) -> Located[T, To] {
  if self.location == from {
    let channel = self.channels[(from, to)].unwrap()
    let value = expect(value)
    channel.push!(value.to_json())
    Located::Empty
  } else if self.location == to {
    let channel = self.channels[(from, to)].unwrap()
    let value = try {
      channel.pop!() |> @json.from_json!()
    } catch {
      _ =>
        abort(
          "Bug: deserialzing JSON failed. " + CONTACT_AUTHOR,
        )
    }
    Located::Wrap(value)
  } else {
    return Located::Empty
  }
}