// ISO 7816-4 command APDU construction and response parsing (short form).

pub(all) suberror ApduError {
  Truncated
  BadLength
  LeTooLarge(Int)
  DataTooLarge(Int)
}

pub(all) struct Apdu {
  cla : Int
  ins : Int
  p1 : Int
  p2 : Int
  data : Array[Int]
  le : Int?
}

pub fn Apdu::new(
  cla : Int,
  ins : Int,
  p1 : Int,
  p2 : Int,
  data? : Array[Int],
  le? : Int,
) -> Apdu {
  let body = match data {
    Some(values) => values
    None => Array::new()
  }
  { cla, ins, p1, p2, data: body, le }
}

pub fn Apdu::encode(self : Apdu) -> Array[Int] raise ApduError {
  if self.data.length() > 255 {
    raise DataTooLarge(self.data.length())
  }
  let out : Array[Int] = [self.cla, self.ins, self.p1, self.p2]
  if self.data.length() > 0 {
    out.push(self.data.length())
    for b in self.data {
      out.push(b)
    }
  }
  match self.le {
    Some(le) => {
      if le < 0 || le > 256 {
        raise LeTooLarge(le)
      }
      out.push(if le == 256 { 0 } else { le })
    }
    None => ()
  }
  out
}

pub fn Apdu::encode_extended(self : Apdu) -> Array[Int] raise ApduError {
  let le_needs_extended = match self.le {
    Some(le) => le > 256
    None => false
  }
  let needs_extended = self.data.length() > 255 || le_needs_extended
  if !needs_extended {
    return self.encode()
  }
  if self.data.length() > 65535 {
    raise DataTooLarge(self.data.length())
  }
  let out : Array[Int] = [self.cla, self.ins, self.p1, self.p2, 0]
  if self.data.length() > 0 {
    out.push((self.data.length() >> 8) & 0xff)
    out.push(self.data.length() & 0xff)
    for b in self.data {
      out.push(b)
    }
  }
  match self.le {
    Some(le) => {
      if le < 1 || le > 65536 {
        raise LeTooLarge(le)
      }
      let encoded = if le == 65536 { 0 } else { le }
      out.push((encoded >> 8) & 0xff)
      out.push(encoded & 0xff)
    }
    None => ()
  }
  out
}

pub fn parse_apdu(bytes : Array[Int]) -> Apdu raise ApduError {
  if bytes.length() < 4 {
    raise Truncated
  }
  if bytes.length() == 4 {
    return { cla: bytes[0], ins: bytes[1], p1: bytes[2], p2: bytes[3], data: [], le: None }
  }
  if bytes.length() == 5 {
    return {
      cla: bytes[0],
      ins: bytes[1],
      p1: bytes[2],
      p2: bytes[3],
      data: [],
      le: Some(if bytes[4] == 0 { 256 } else { bytes[4] }),
    }
  }
  if bytes[4] == 0 && bytes.length() > 5 {
    let rest = bytes.length() - 5
    if rest == 2 {
      let le = (bytes[5] << 8) | bytes[6]
      return {
        cla: bytes[0],
        ins: bytes[1],
        p1: bytes[2],
        p2: bytes[3],
        data: [],
        le: Some(if le == 0 { 65536 } else { le }),
      }
    }
    if rest < 2 {
      raise BadLength
    }
    let lc = (bytes[5] << 8) | bytes[6]
    if lc < 0 || bytes.length() < 7 + lc {
      raise Truncated
    }
    let data : Array[Int] = Array::new()
    for i in 0.. ApduResponse raise ApduError {
  if bytes.length() < 2 {
    raise Truncated
  }
  let data : Array[Int] = Array::new()
  for i in 0..<(bytes.length() - 2) {
    data.push(bytes[i])
  }
  { data, sw: (bytes[bytes.length() - 2] << 8) | bytes[bytes.length() - 1] }
}

pub fn status_description(sw : Int) -> String {
  if sw == 0x9000 {
    "success"
  } else if sw >= 0x6100 && sw <= 0x61ff {
    "response available"
  } else if sw == 0x6200 {
    "warning: state unchanged"
  } else if sw == 0x6281 {
    "warning: part of returned data may be corrupted"
  } else if sw == 0x6282 {
    "warning: end of file reached"
  } else if sw == 0x6300 {
    "authentication failed"
  } else if sw >= 0x63c0 && sw <= 0x63cf {
    "verification failed, " + (sw & 0xf).to_string() + " tries left"
  } else if sw == 0x6700 {
    "wrong length"
  } else if sw == 0x6800 {
    "function in CLA not supported"
  } else if sw == 0x6881 {
    "logical channel not supported"
  } else if sw == 0x6882 {
    "secure messaging not supported"
  } else if sw == 0x6982 {
    "security status not satisfied"
  } else if sw == 0x6983 {
    "authentication method blocked"
  } else if sw == 0x6985 {
    "conditions of use not satisfied"
  } else if sw == 0x6986 {
    "command not allowed"
  } else if sw == 0x6a80 {
    "incorrect parameters in data field"
  } else if sw == 0x6a82 {
    "file or application not found"
  } else if sw == 0x6a83 {
    "record not found"
  } else if sw == 0x6a84 {
    "not enough memory"
  } else if sw == 0x6a86 {
    "incorrect P1/P2"
  } else if sw == 0x6a88 {
    "referenced data not found"
  } else if sw == 0x6b00 {
    "wrong parameters P1/P2"
  } else if sw == 0x6c00 {
    "wrong Le"
  } else if sw >= 0x6c01 && sw <= 0x6cff {
    "wrong Le, " + (sw & 0xff).to_string() + " available"
  } else if sw == 0x6d00 {
    "instruction not supported"
  } else if sw == 0x6e00 {
    "class not supported"
  } else if sw == 0x6f00 {
    "unknown error"
  } else {
    "unknown status"
  }
}

pub fn command_name(apdu : Apdu) -> String {
  if apdu.ins == 0xa4 {
    "SELECT"
  } else if apdu.ins == 0xb2 {
    "READ RECORD"
  } else if apdu.ins == 0x20 {
    "VERIFY"
  } else if apdu.ins == 0xca {
    "GET DATA"
  } else if apdu.ins == 0xa8 {
    "GET PROCESSING OPTIONS"
  } else if apdu.ins == 0xae {
    "GENERATE AC"
  } else if apdu.ins == 0x24 {
    "PROCESSING"
  } else if apdu.ins == 0x88 {
    "INTERNAL AUTHENTICATE"
  } else if apdu.ins == 0x84 {
    "GET CHALLENGE"
  } else {
    "UNKNOWN"
  }
}