///|
pub struct AsSegment {
  kind : Int
  asns : Array[UInt]
} derive(Eq, ToJson)

///|
pub extend AsSegment with Eq::{equal, not_equal}

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

///|
pub struct RawAttribute {
  flags : Int
  code : Int
  value : Bytes
} derive(Eq)

///|
pub impl ToJson for RawAttribute with fn to_json(self) {
  Json::object({
    "flags": self.flags.to_json(),
    "code": self.code.to_json(),
    "value": self.value.to_array().map(fn(b) { b.to_int() }).to_json(),
  })
}

///|
pub extend RawAttribute with Eq::{equal, not_equal}

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

///|
pub struct Attributes {
  origin : Int?
  path : Array[AsSegment]
  wire_path : Array[AsSegment]
  as4_path : Array[AsSegment]
  next_hop : String?
  med : UInt?
  local_pref : UInt?
  atomic_aggregate : Bool
  aggregator : Aggregator?
  wire_aggregator : Aggregator?
  as4_aggregator : Aggregator?
  raw_attributes : Array[RawAttribute]
  communities : Array[UInt]
  large_communities : Array[(UInt, UInt, UInt)]
  other : Array[RawAttribute]
} derive(Eq, ToJson)

///|
pub extend Attributes with Eq::{equal, not_equal}

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

///|
fn empty_attributes() -> Attributes {
  {
    origin: None,
    path: [],
    wire_path: [],
    as4_path: [],
    next_hop: None,
    med: None,
    local_pref: None,
    atomic_aggregate: false,
    aggregator: None,
    wire_aggregator: None,
    as4_aggregator: None,
    raw_attributes: [],
    communities: [],
    large_communities: [],
    other: [],
  }
}

///|
fn read_as_path(c : Cursor, width : Int) -> Array[AsSegment] raise ParseError {
  let segments = []
  while c.remaining() > 0 {
    let kind = c.u8()
    let count = c.u8()
    if kind < 1 || kind > 4 || count == 0 {
      raise Invalid(c.pos - 2, "无效 AS 路径段")
    }
    let asns = []
    for i = 0; i < count; i = i + 1 {
      asns.push(
        if width == 4 {
          c.u32()
        } else {
          c.u16().reinterpret_as_uint()
        },
      )
    }
    segments.push({ kind, asns, })
  }
  segments
}

///|
pub fn parse_attributes(
  data : Bytes,
  asn_width : Int,
) -> Attributes raise ParseError {
  if asn_width != 2 && asn_width != 4 {
    raise Invalid(0, "ASN 宽度必须为 2 或 4")
  }
  let c = Cursor::new(data)
  let seen : Map[Int, Bool] = Map([])
  let mut origin = None
  let mut path = []
  let mut as4_path = []
  let mut next_hop = None
  let mut med = None
  let mut local_pref = None
  let mut atomic_aggregate = false
  let mut wire_aggregator = None
  let mut as4_aggregator = None
  let raw_attributes = []
  let communities = []
  let large_communities = []
  let other = []
  while c.remaining() > 0 {
    let position = c.pos
    let flags = c.u8()
    let code = c.u8()
    validate_attribute_flags(flags, code, position)
    let length = if (flags & 16) != 0 { c.u16() } else { c.u8() }
    if seen.contains(code) {
      raise Invalid(c.pos, "重复 BGP 属性")
    }
    seen[code] = true
    let value = c.take(length)
    let raw = value.data[value.pos:value.end].to_owned()
    raw_attributes.push({ flags, code, value: raw, })
    match code {
      1 => {
        let n = value.u8()
        if n > 2 {
          raise Invalid(value.pos - 1, "无效 ORIGIN 属性")
        }
        origin = Some(n)
      }
      2 => path = read_as_path(value, asn_width)
      3 => next_hop = Some(ip_text(value.bytes(4)))
      4 => med = Some(value.u32())
      5 => local_pref = Some(value.u32())
      6 => {
        if length != 0 {
          raise Invalid(value.pos, "ATOMIC_AGGREGATE 长度必须为零")
        }
        atomic_aggregate = true
        other.push({ flags, code, value: raw, })
      }
      7 => {
        wire_aggregator = Some(read_aggregator(value, asn_width))
        other.push({ flags, code, value: raw, })
      }
      8 => {
        if length == 0 || length % 4 != 0 {
          raise Invalid(
            value.pos,
            "COMMUNITY 长度必须是非零的 4 字节倍数",
          )
        }
        while value.remaining() > 0 {
          communities.push(value.u32())
        }
      }
      17 => {
        if length < 6 {
          raise Invalid(
            value.pos,
            "AS4_PATH 必须至少包含一个 AS 号码",
          )
        }
        as4_path = read_as_path(value, 4)
      }
      18 => {
        as4_aggregator = Some(read_aggregator(value, 4))
        other.push({ flags, code, value: raw, })
      }
      32 => {
        let raw = value.bytes(value.remaining())
        if raw.length() == 0 || raw.length() % 12 != 0 {
          raise Invalid(
            value.pos - raw.length(),
            "LARGE_COMMUNITY 长度必须是非零的 12 字节倍数",
          )
        }
        let values = Cursor::new(raw)
        while values.remaining() > 0 {
          large_communities.push((values.u32(), values.u32(), values.u32()))
        }
        other.push({ flags, code, value: raw, })
      }
      _ => other.push({ flags, code, value: value.bytes(value.remaining()), })
    }
    value.finish()
  }
  {
    origin,
    path: effective_path(
      path, as4_path, wire_aggregator, as4_aggregator, asn_width,
    ),
    wire_path: path,
    as4_path,
    next_hop,
    med,
    local_pref,
    atomic_aggregate,
    aggregator: effective_aggregator(wire_aggregator, as4_aggregator, asn_width),
    wire_aggregator,
    as4_aggregator,
    raw_attributes,
    communities,
    large_communities: unique_large_communities(large_communities),
    other,
  }
}

///|
/// AS_SET 计一个跳数,联盟路径不参与常规路径长度统计。
pub fn path_length(path : Array[AsSegment]) -> Int {
  let mut length = 0
  for segment in path {
    if segment.kind == 1 {
      length += 1
    }
    if segment.kind == 2 {
      length += segment.asns.length()
    }
  }
  length
}

///|
/// 末段为 AS_SET 时无法唯一确定起源 AS。
pub fn origin_as(path : Array[AsSegment]) -> UInt? {
  for i = path.length() - 1; i >= 0; i = i - 1 {
    let segment = path[i]
    if segment.kind == 1 {
      return None
    }
    if segment.kind == 2 {
      return segment.asns.last()
    }
  }
  None
}