///|
pub struct RouteSnapshot {
  timestamp : UInt
  microseconds : Int?
  active : Bool
  attributes : Attributes
  source : Source?
} derive(ToJson)

///|
pub extend RouteSnapshot with ToJson::{to_json}

///|
pub struct PathDiff {
  before : Array[AsSegment]
  after : Array[AsSegment]
} derive(ToJson)

///|
pub extend PathDiff with ToJson::{to_json}

///|
pub struct OriginDiff {
  before : UInt?
  after : UInt?
}

///|
pub impl ToJson for OriginDiff with fn to_json(self) {
  Json::object({
    "before": self.before.map(fn(n) { n.to_json() }).unwrap_or(Json::null()),
    "after": self.after.map(fn(n) { n.to_json() }).unwrap_or(Json::null()),
  })
}

///|
pub extend OriginDiff with ToJson::{to_json}

///|
pub struct HopDiff {
  before : String?
  after : String?
}

///|
pub impl ToJson for HopDiff with fn to_json(self) {
  Json::object({
    "before": self.before.map(fn(n) { n.to_json() }).unwrap_or(Json::null()),
    "after": self.after.map(fn(n) { n.to_json() }).unwrap_or(Json::null()),
  })
}

///|
pub extend HopDiff with ToJson::{to_json}

///|
pub struct CommunityDiff {
  before : Array[UInt]
  after : Array[UInt]
  added : Array[UInt]
  removed : Array[UInt]
} derive(ToJson)

///|
pub extend CommunityDiff with ToJson::{to_json}

///|
pub struct RouteDiff {
  atomic_aggregate : AtomicAggregateDiff?
  aggregator : AggregatorDiff?
  path : PathDiff?
  origin_as : OriginDiff?
  next_hop : HopDiff?
  communities : CommunityDiff?
  large_communities : LargeCommunityDiff?
} derive(ToJson)

///|
pub extend RouteDiff with ToJson::{to_json}

///|
fn unique_communities(values : Array[UInt]) -> Array[UInt] {
  let mut ordered = true
  for i = 1; i < values.length(); i = i + 1 {
    if values[i - 1] >= values[i] {
      ordered = false
      break
    }
  }
  if ordered {
    return values
  }
  let sorted = values.copy()
  sorted.sort()
  let result = []
  for value in sorted {
    if result.last() != Some(value) {
      result.push(value)
    }
  }
  result
}

///|
fn route_diff(a : Attributes, b : Attributes) -> RouteDiff {
  let ac = unique_communities(a.communities)
  let bc = unique_communities(b.communities)
  let al = unique_large_communities(a.large_communities)
  let bl = unique_large_communities(b.large_communities)
  {
    atomic_aggregate: if a.atomic_aggregate == b.atomic_aggregate {
      None
    } else {
      Some({ before: a.atomic_aggregate, after: b.atomic_aggregate, })
    },
    aggregator: if a.aggregator == b.aggregator {
      None
    } else {
      Some({ before: a.aggregator, after: b.aggregator, })
    },
    large_communities: if al == bl {
      None
    } else {
      Some({
        before: al,
        after: bl,
        added: bl.filter(fn(n) { !al.contains(n) }),
        removed: al.filter(fn(n) { !bl.contains(n) }),
      })
    },
    path: if same_path(a.path, b.path) {
      None
    } else {
      Some({ before: a.path, after: b.path, })
    },
    origin_as: if origin_as(a.path) == origin_as(b.path) {
      None
    } else {
      Some({ before: origin_as(a.path), after: origin_as(b.path), })
    },
    next_hop: if a.next_hop == b.next_hop {
      None
    } else {
      Some({ before: a.next_hop, after: b.next_hop, })
    },
    communities: if ac == bc {
      None
    } else {
      Some({
        before: ac,
        after: bc,
        added: bc.filter(fn(n) { !ac.contains(n) }),
        removed: ac.filter(fn(n) { !bc.contains(n) }),
      })
    },
  }
}

///|
pub struct LargeCommunityDiff {
  before : Array[(UInt, UInt, UInt)]
  after : Array[(UInt, UInt, UInt)]
  added : Array[(UInt, UInt, UInt)]
  removed : Array[(UInt, UInt, UInt)]
} derive(ToJson)

///|
pub extend LargeCommunityDiff with ToJson::{to_json}

///|
fn unique_large_communities(
  values : Array[(UInt, UInt, UInt)],
) -> Array[(UInt, UInt, UInt)] {
  let mut ordered = true
  for i = 1; i < values.length(); i = i + 1 {
    if values[i - 1].compare(values[i]) >= 0 {
      ordered = false
      break
    }
  }
  if ordered {
    return values
  }
  let sorted = values.copy()
  sorted.sort()
  let result = []
  for value in sorted {
    if result.last() != Some(value) {
      result.push(value)
    }
  }
  result
}

///|
fn route_snapshot(
  event : RouteEvent,
  active : Bool,
  attributes : Attributes,
) -> RouteSnapshot {
  {
    timestamp: event.timestamp,
    microseconds: event.microseconds,
    active,
    attributes,
    source: event.source,
  }
}