///|
/// A semantic event observed on an I²C bus.
pub enum I2cEventKind {
  Start
  RepeatedStart
  Stop
  Byte
  Acknowledge
  NotAcknowledge
  Unknown
} derive(Debug, Eq)

///|
/// I²C bus configuration.
pub struct I2cConfig {
  clock : String
  data : String
  address_bits : Int
  include_ack : Bool
  address_is_seven_bit : Bool
} derive(Debug)

///|
/// One decoded byte, including its address/data role and ACK result.
pub struct I2cByte {
  index : Int
  time : Int64
  value : Int
  text : String
  address : Bool
  read : Bool
  acknowledged : Bool?
  bit_count : Int
} derive(Debug)

///|
/// A complete I²C transaction between START and STOP.
pub struct I2cTransaction {
  index : Int
  start_time : Int64
  end_time : Int64
  repeated_start_count : Int
  bytes : Array[I2cByte]
  address : Int?
  read : Bool?
  completed : Bool
} derive(Debug)

///|
/// Aggregate I²C decode output.
pub struct I2cReport {
  config : I2cConfig
  transactions : Array[I2cTransaction]
  events : Int
  bytes : Int
  acknowledged : Int
  not_acknowledged : Int
  first_time : Int64?
  last_time : Int64?
} derive(Debug)

///|
/// List the event kinds used by the decoder.
pub fn i2c_event_catalog() -> Array[I2cEventKind] {
  [Start, RepeatedStart, Stop, Byte, Acknowledge, NotAcknowledge, Unknown]
}

///|
/// Create the usual seven-bit address I²C configuration.
pub fn i2c_config(clock : String, data : String) -> I2cConfig {
  {
    clock,
    data,
    address_bits: 7,
    include_ack: true,
    address_is_seven_bit: true,
  }
}

///|
/// Select seven- or ten-bit address interpretation.
pub fn I2cConfig::with_address_width(self : I2cConfig, bits : Int) -> I2cConfig {
  {
    ..self,
    address_bits: if bits >= 10 {
      10
    } else {
      7
    },
    address_is_seven_bit: bits < 10,
  }
}

///|
/// Include or omit ACK slots from byte grouping.
pub fn I2cConfig::with_ack_slots(
  self : I2cConfig,
  include_ack : Bool,
) -> I2cConfig {
  { ..self, include_ack, }
}

///|
/// Decode all transactions in a trace.
pub fn i2c_decode(trace : Trace, config : I2cConfig) -> Array[I2cTransaction] {
  let times = i2c_bus_times(trace, config)
  let result : Array[I2cTransaction] = []
  let mut active : I2cTransaction? = None
  let mut bits : Array[String] = []
  let mut byte_index = 0
  let mut repeated = 0
  let mut previous_sda = true
  let mut previous_scl = true
  for time in times {
    let scl = i2c_level(trace, config.clock, time, true)
    let sda = i2c_level(trace, config.data, time, true)
    let start = previous_sda && !sda && scl
    let stop = !previous_sda && sda && scl
    let rising = !previous_scl && scl
    if start {
      match active {
        Some(current) => {
          active = Some({ ..current, repeated_start_count: repeated + 1 })
          repeated += 1
        }
        None =>
          active = Some({
            index: result.length(),
            start_time: time,
            end_time: time,
            repeated_start_count: 0,
            bytes: [],
            address: None,
            read: None,
            completed: false,
          })
      }
      bits = []
      byte_index = 0
    } else if stop {
      match active {
        Some(current) => {
          result.push({ ..current, end_time: time, completed: true })
          active = None
        }
        None => ()
      }
      bits = []
    } else if rising && active is Some(_) {
      bits.push(if sda { "1" } else { "0" })
      if bits.length() == 8 {
        let value = i2c_bits_value(bits)
        let address_byte = byte_index == 0
        let read = if address_byte { (value & 1) == 1 } else { false }
        let byte = {
          index: byte_index,
          time,
          value,
          text: i2c_hex_byte(value),
          address: address_byte,
          read,
          acknowledged: None,
          bit_count: bits.length(),
        }
        match active {
          Some(current) => {
            let next_address = if address_byte {
              Some(value / 2)
            } else {
              current.address
            }
            let next_read = if address_byte { Some(read) } else { current.read }
            let next_bytes = current.bytes.copy()
            next_bytes.push(byte)
            active = Some({
              ..current,
              end_time: time,
              bytes: next_bytes,
              address: next_address,
              read: next_read,
            })
          }
          None => ()
        }
        byte_index += 1
        bits = []
      }
    }
    previous_sda = sda
    previous_scl = scl
  }
  match active {
    Some(current) =>
      result.push({ ..current, end_time: trace.end_time, completed: false })
    None => ()
  }
  result
}

///|
/// Decode and summarize an I²C capture.
pub fn i2c_report(trace : Trace, config : I2cConfig) -> I2cReport {
  let transactions = i2c_decode(trace, config)
  let bytes = transactions.fold(init=0, fn(total, item) {
    total + item.bytes.length()
  })
  let mut ack = 0
  let mut nack = 0
  for transaction in transactions {
    for byte in transaction.bytes {
      match byte.acknowledged {
        Some(true) => ack += 1
        Some(false) => nack += 1
        None => ()
      }
    }
  }
  let first = transactions.fold(init=None, fn(current, item) {
    match current {
      Some(value) => Some(Int64::min(value, item.start_time))
      None => Some(item.start_time)
    }
  })
  let last = transactions.fold(init=None, fn(current, item) {
    match current {
      Some(value) => Some(Int64::max(value, item.end_time))
      None => Some(item.end_time)
    }
  })
  {
    config,
    transactions,
    events: bytes + transactions.length(),
    bytes,
    acknowledged: ack,
    not_acknowledged: nack,
    first_time: first,
    last_time: last,
  }
}

///|
/// Return the number of decoded bytes.
pub fn I2cReport::byte_count(self : I2cReport) -> Int {
  self.bytes
}

///|
/// Return every byte from every transaction in wire order.
pub fn I2cReport::all_bytes(self : I2cReport) -> Array[I2cByte] {
  let result : Array[I2cByte] = []
  for transaction in self.transactions {
    for byte in transaction.bytes {
      result.push(byte)
    }
  }
  result
}

///|
/// Export transactions as CSV.
pub fn i2c_transactions_csv(transactions : Array[I2cTransaction]) -> String {
  let out = StringBuilder()
  out.write_string("index,start,end,address,read,bytes,completed\n")
  for item in transactions {
    out.write_string(item.index.to_string())
    out.write_char(',')
    out.write_string(item.start_time.to_string())
    out.write_char(',')
    out.write_string(item.end_time.to_string())
    out.write_char(',')
    out.write_string(i2c_optional_int(item.address))
    out.write_char(',')
    out.write_string(i2c_optional_bool(item.read))
    out.write_char(',')
    out.write_string(item.bytes.map(fn(byte) { byte.text }).join(" "))
    out.write_char(',')
    out.write_string(item.completed.to_string())
    out.write_char('\n')
  }
  out.to_string()
}

///|
/// Render an I²C report for a terminal.
pub fn I2cReport::to_text(self : I2cReport) -> String {
  "I2C transactions=" +
  self.transactions.length().to_string() +
  " bytes=" +
  self.bytes.to_string() +
  " ack=" +
  self.acknowledged.to_string() +
  " nack=" +
  self.not_acknowledged.to_string()
}

///|
/// Render an I²C report as JSON.
pub fn I2cReport::to_json(self : I2cReport) -> String {
  let transactions = self.transactions.map(i2c_transaction_json).join(",")
  "{\"clock\":\"" +
  escape_text(self.config.clock) +
  "\",\"data\":\"" +
  escape_text(self.config.data) +
  "\",\"bytes\":" +
  self.bytes.to_string() +
  ",\"acknowledged\":" +
  self.acknowledged.to_string() +
  ",\"not_acknowledged\":" +
  self.not_acknowledged.to_string() +
  ",\"transactions\":[" +
  transactions +
  "]}"
}

///|
/// Collect timestamps where either bus line changes.
fn i2c_bus_times(trace : Trace, config : I2cConfig) -> Array[Int64] {
  let result : Array[Int64] = []
  for change in trace.changes {
    if change.id == config.clock || change.id == config.data {
      if !result.contains(change.time) {
        result.push(change.time)
      }
    }
  }
  result.sort()
  result
}

///|
/// Read a digital line at or before a timestamp.
fn i2c_level(trace : Trace, id : String, time : Int64, default : Bool) -> Bool {
  match value_at(trace, id, time) {
    Some(Scalar(ch)) => ch == '1'
    Some(Vector(bits)) => bits == "1"
    _ => default
  }
}

///|
/// Convert eight wire bits to an integer.
fn i2c_bits_value(bits : Array[String]) -> Int {
  let mut result = 0
  for bit in bits {
    result *= 2
    if bit == "1" {
      result += 1
    }
  }
  result
}

///|
/// Format one data byte as hexadecimal.
fn i2c_hex_byte(value : Int) -> String {
  let high = value / 16
  let low = value % 16
  i2c_hex_digit(high) + i2c_hex_digit(low)
}

///|
/// Format a hexadecimal digit.
fn i2c_hex_digit(value : Int) -> String {
  let digits = [
    "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F",
  ]
  if value >= 0 && value < digits.length() {
    digits[value]
  } else {
    "?"
  }
}

///|
/// Encode an optional integer.
fn i2c_optional_int(value : Int?) -> String {
  match value {
    Some(item) => item.to_string()
    None => ""
  }
}

///|
/// Encode an optional Boolean.
fn i2c_optional_bool(value : Bool?) -> String {
  match value {
    Some(item) => item.to_string()
    None => ""
  }
}

///|
/// Encode one transaction as JSON.
fn i2c_transaction_json(item : I2cTransaction) -> String {
  let bytes = item.bytes
    .map(fn(byte) {
      "{\"index\":" +
      byte.index.to_string() +
      ",\"time\":" +
      byte.time.to_string() +
      ",\"value\":" +
      byte.value.to_string() +
      ",\"text\":\"" +
      byte.text +
      "\",\"address\":" +
      byte.address.to_string() +
      ",\"read\":" +
      byte.read.to_string() +
      "}"
    })
    .join(",")
  "{\"index\":" +
  item.index.to_string() +
  ",\"start\":" +
  item.start_time.to_string() +
  ",\"end\":" +
  item.end_time.to_string() +
  ",\"address\":" +
  i2c_optional_int(item.address) +
  ",\"read\":" +
  i2c_optional_bool(item.read) +
  ",\"completed\":" +
  item.completed.to_string() +
  ",\"bytes\":[" +
  bytes +
  "]}"
}