// CAN frame transport and candle/candump text parsing.
// The toolkit is offline: it reads recorded frames, it does not open a bus.

pub(all) struct CanFrame {
  channel : String
  id : Int
  extended : Bool
  data : Array[Int]
}

pub fn CanFrame::new(
  channel : String,
  id : Int,
  extended : Bool,
  data : Array[Int],
) -> CanFrame {
  { channel, id, extended, data }
}

fn hex_digit(c : Int) -> Int? {
  if c >= 48 && c <= 57 {
    Some(c - 48)
  } else if c >= 65 && c <= 70 {
    Some(c - 55)
  } else if c >= 97 && c <= 102 {
    Some(c - 87)
  } else {
    None
  }
}

pub fn hex_nibble_char(value : Int) -> Char {
  let digits = "0123456789ABCDEF"
  match digits.get_char(value & 0xf) {
    Some(c) => c
    None => '?'
  }
}

pub fn hex_int(value : Int, width : Int) -> String {
  let out = StringBuilder()
  for i in 0..> shift))
  }
  out.to_string()
}

pub fn hex_can_id(id : Int) -> String {
  hex_int(id, if id > 0x7ff { 8 } else { 3 })
}

pub fn hex_encode(bytes : Array[Int]) -> String {
  let out = StringBuilder()
  for i in 0.. 0 {
      out.write_char(' ')
    }
    let v = bytes[i]
    out.write_char(hex_nibble_char(v >> 4))
    out.write_char(hex_nibble_char(v))
  }
  out.to_string()
}

pub fn hex_decode(text : String) -> Array[Int]? {
  let out : Array[Int] = Array::new()
  let mut high = -1
  for i in 0..
        if high < 0 {
          high = v
        } else {
          out.push((high << 4) | v)
          high = -1
        }
      None => return None
    }
  }
  if high >= 0 {
    None
  } else {
    Some(out)
  }
}

fn parse_hex_int(text : String) -> Int? {
  let mut value = 0
  if text.length() == 0 {
    return None
  }
  for i in 0.. value = value * 16 + v
      None => return None
    }
  }
  Some(value)
}

fn split_ws(text : String) -> Array[String] {
  let out : Array[String] = Array::new()
  let mut start = 0
  let mut i = 0
  while i <= text.length() {
    let at_end = i == text.length()
    let c = if at_end { 0 } else { text[i].to_int() }
    let is_space = !at_end && (c == 32 || c == 9 || c == 13)
    if at_end || is_space {
      if i > start {
        out.push(text.substring(start=start, end=i))
      }
      start = i + 1
    }
    i = i + 1
  }
  out
}

// Accepts classic candump (`can0 7E8 [8] 02 10 03 ...`) and
// candump -L (`(1690000000.123) can0 7E8#0210030000000000`).
pub fn parse_candump_line(line : String) -> CanFrame? {
  let mut body = line.trim_space().to_string()
  if body.length() == 0 || body.has_prefix("#") {
    return None
  }
  if body.has_prefix("(") {
    match body.find(")") {
      Some(close) => body = body.substring(start=close + 1).trim_space().to_string()
      None => return None
    }
  }
  match body.find("#") {
    Some(hash) => {
      let left = body.substring(start=0, end=hash).trim_space().to_string()
      let payload_hex = body.substring(start=hash + 1).trim_space().to_string()
      let parts = split_ws(left)
      if parts.length() != 2 {
        return None
      }
      let id = match parse_hex_int(parts[1]) {
        Some(v) => v
        None => return None
      }
      match hex_decode(payload_hex) {
        Some(data) =>
          Some({ channel: parts[0], id, extended: parts[1].length() > 3, data })
        None => None
      }
    }
    None => {
      let parts = split_ws(body)
      if parts.length() < 4 {
        return None
      }
      let channel = parts[0]
      let id = match parse_hex_int(parts[1]) {
        Some(v) => v
        None => return None
      }
      let len_text = parts[2]
      if !len_text.has_prefix("[") || !len_text.has_suffix("]") {
        return None
      }
      let declared = match parse_hex_int(
        len_text.substring(start=1, end=len_text.length() - 1),
      ) {
        Some(v) => v
        None => return None
      }
      let data : Array[Int] = Array::new()
      let mut i = 3
      while i < parts.length() {
        match hex_decode(parts[i]) {
          Some(chunk) => {
            for b in chunk {
              data.push(b)
            }
          }
          None => return None
        }
        i = i + 1
      }
      if declared < 0 || declared > 64 || data.length() < declared {
        return None
      }
      while data.length() > declared {
        ignore(data.pop())
      }
      Some({ channel, id, extended: parts[1].length() > 3, data })
    }
  }
}

pub fn parse_candump(text : String) -> Array[CanFrame] {
  let frames : Array[CanFrame] = Array::new()
  for line in text.split("\n").to_array() {
    match parse_candump_line(line.to_string()) {
      Some(frame) => frames.push(frame)
      None => ()
    }
  }
  frames
}

pub fn CanFrame::to_candump(self : CanFrame) -> String {
  let id_text = if self.extended {
    let out = StringBuilder()
    for shift in [28, 24, 20, 16, 12, 8, 4, 0] {
      out.write_char(hex_nibble_char(self.id >> shift))
    }
    out.to_string()
  } else {
    let out = StringBuilder()
    for shift in [8, 4, 0] {
      out.write_char(hex_nibble_char(self.id >> shift))
    }
    out.to_string()
  }
  self.channel + " " + id_text + " [" + self.data.length().to_string() + "] " +
  hex_encode(self.data)
}