///|
pub(all) struct Request {
  invoke : Int32
  service : Int32
  max_apdu : Int32
  max_segments : Int32
  segmented_response_accepted : Bool
  body : Bytes
} derive(Debug)

///|
pub(all) enum Apdu {
  Confirmed(Request)
  Unconfirmed(Int32, Bytes)
  SimpleAck(Int32, Int32)
  ComplexAck(Int32, Int32, Bytes)
  ErrorPdu(Int32, Int32, Bytes)
  Reject(Int32, Int32)
  Abort(Int32, Bool, Int32)
} derive(Debug)

///|
pub fn max_apdu_size(code : Int32) -> Int32 raise BacnetError {
  match code {
    0 => 50
    1 => 128
    2 => 206
    3 => 480
    4 => 1024
    5 => 1476
    _ => raise Invalid("max APDU code", 0)
  }
}

///|
pub fn Request::new(
  invoke : Int32,
  service : Int32,
  body : Bytes,
  max_apdu~ : Int32 = 5,
) -> Request raise BacnetError {
  require_range(invoke, 0, 255, "invoke ID")
  require_range(service, 0, 255, "service choice")
  ignore(max_apdu_size(max_apdu))
  {
    invoke,
    service,
    max_apdu,
    max_segments: 0,
    segmented_response_accepted: false,
    body,
  }
}

///|
pub fn encode_apdu(
  apdu : Apdu,
  limits~ : Limits = Limits::default(),
) -> Bytes raise BacnetError {
  limits.validate()
  let w = Writer::new(limits.max_bytes)
  match apdu {
    Confirmed(r) => {
      require_range(r.invoke, 0, 255, "invoke ID")
      require_range(r.service, 0, 255, "service choice")
      require_range(r.max_segments, 0, 7, "max segments code")
      ignore(max_apdu_size(r.max_apdu))
      w.u8(if r.segmented_response_accepted { 2 } else { 0 })
      w.u8((r.max_segments << 4) | r.max_apdu)
      w.u8(r.invoke)
      w.u8(r.service)
      w.put(r.body)
    }
    Unconfirmed(service, body) => {
      w.u8(0x10)
      w.u8(service)
      w.put(body)
    }
    SimpleAck(invoke, service) => {
      w.u8(0x20)
      w.u8(invoke)
      w.u8(service)
    }
    ComplexAck(invoke, service, body) => {
      w.u8(0x30)
      w.u8(invoke)
      w.u8(service)
      w.put(body)
    }
    ErrorPdu(invoke, service, body) => {
      w.u8(0x50)
      w.u8(invoke)
      w.u8(service)
      w.put(body)
    }
    Reject(invoke, reason) => {
      w.u8(0x60)
      w.u8(invoke)
      w.u8(reason)
    }
    Abort(invoke, server, reason) => {
      w.u8(if server { 0x71 } else { 0x70 })
      w.u8(invoke)
      w.u8(reason)
    }
  }
  w.finish()
}

///|
pub fn decode_apdu(
  data : Bytes,
  limits~ : Limits = Limits::default(),
) -> Apdu raise BacnetError {
  let r = Reader::new(data, limits)
  let first = r.u8()
  let kind = first >> 4
  let flags = first & 15
  let result : Apdu = match kind {
    0 => {
      if (flags & 13) != 0 {
        raise Unsupported("segmented or reserved confirmed request")
      }
      let sizes = r.u8()
      let max_apdu = sizes & 15
      ignore(max_apdu_size(max_apdu))
      if sizes >> 4 > 7 {
        raise Invalid("max segments code", 1)
      }
      let invoke = r.u8()
      let service = r.u8()
      Confirmed({
        invoke,
        service,
        max_apdu,
        max_segments: sizes >> 4,
        segmented_response_accepted: flags == 2,
        body: r.take(r.remaining()),
      })
    }
    1 => {
      if flags != 0 {
        raise Invalid("unconfirmed flags", 0)
      }
      Unconfirmed(r.u8(), r.take(r.remaining()))
    }
    2 => {
      if flags != 0 {
        raise Invalid("simple ack flags", 0)
      }
      SimpleAck(r.u8(), r.u8())
    }
    3 => {
      if flags != 0 {
        raise Unsupported("segmented or reserved complex ack")
      }
      ComplexAck(r.u8(), r.u8(), r.take(r.remaining()))
    }
    5 => {
      if flags != 0 {
        raise Invalid("error flags", 0)
      }
      ErrorPdu(r.u8(), r.u8(), r.take(r.remaining()))
    }
    6 => {
      if flags != 0 {
        raise Invalid("reject flags", 0)
      }
      Reject(r.u8(), r.u8())
    }
    7 => {
      if flags > 1 {
        raise Invalid("abort flags", 0)
      }
      Abort(r.u8(), flags == 1, r.u8())
    }
    _ => raise Unsupported("APDU type or segmentation")
  }
  r.done()
  result
}

///|
pub extend Request with @debug.Debug::{to_repr}

///|
pub extend Apdu with @debug.Debug::{to_repr}