// 公共数据类型。
//
// 本库不依赖任何第三方包,所有解析都只基于输入文本,
// 因此同一份报文在 native / wasm / js 后端上得到相同结果。

///|
/// 标准版本。
///
/// 版本号编码在报文 `Flag` 字段的 V5–V0 位里。现场设备并不统一:
/// 2005 版与 2017 版的数采仪可能挂在同一条链路上,因此解析器
/// 必须读完 `Flag` 才知道该用哪套字段规则去解释这条报文。
pub(all) enum Version {
  /// HJ/T 212-2005,版本位 `000000`
  V2005
  /// HJ 212-2017,版本位 `000001`
  V2017
  /// HJ 212-2025,版本位 `000010`
  V2025
  /// 未在标准中登记的版本位
  Unknown(Int32)
}

///|
pub fn Version::from_code(code : Int32) -> Version {
  match code {
    0 => V2005
    1 => V2017
    2 => V2025
    _ => Unknown(code)
  }
}

///|
pub fn Version::code(self : Version) -> Int32 {
  match self {
    V2005 => 0
    V2017 => 1
    V2025 => 2
    Unknown(n) => n
  }
}

///|
pub fn Version::label(self : Version) -> String {
  match self {
    V2005 => "HJ/T 212-2005"
    V2017 => "HJ 212-2017"
    V2025 => "HJ 212-2025"
    Unknown(n) => "未知版本(\{n})"
  }
}

///|
/// 报文的 `Flag` 字段,一个 8 位无符号整数。
///
/// 位布局(高位到低位):`V5 V4 V3 V2 V1 V0 | 分包位 | 应答位`
///
/// - `V5..V0`:标准版本号,`000000` = 2005 版,`000001` = 2017 版,`000010` = 2025 版;
/// - bit1:数据段是否携带 `PNUM` / `PNO` 分包序号;
/// - bit0:本条命令是否需要应答。
///
/// 注意:这两位的**字母**在两次修订里换过名。2017 版写作 `D`(分包)
/// 与 `A`(应答),2025 版写作 `A`(分包)与 `D`(应答)。位权没有变,
/// 变的只是字母,这也是最容易抄错的一处;本库只按位权判断,不引用字母。
///
/// 例:`Flag=9` 即 `00001001`,表示 2025 版、不分包、需要应答。
pub(all) struct Flag {
  raw : Int32
}

///|
pub fn Flag::from_raw(raw : Int32) -> Flag {
  { raw: raw & 0xFF, }
}

///|
pub fn Flag::new(version : Version, split : Bool, ack : Bool) -> Flag {
  let v = version.code() & 0x3F
  let d = if split { 1 } else { 0 }
  let a = if ack { 1 } else { 0 }
  { raw: (v << 2) | (d << 1) | a, }
}

///|
pub fn Flag::version(self : Flag) -> Version {
  Version::from_code((self.raw >> 2) & 0x3F)
}

///|
pub fn Flag::is_split(self : Flag) -> Bool {
  ((self.raw >> 1) & 1) == 1
}

///|
pub fn Flag::needs_ack(self : Flag) -> Bool {
  (self.raw & 1) == 1
}

///|
pub fn Flag::text(self : Flag) -> String {
  self.raw.to_string()
}

///|
/// 解码失败的原因。
///
/// 结构性错误(包头、长度、CRC、包尾)一律视为失败并拒绝该帧;
/// 字段取值是否符合行业惯例属于语义问题,交由 `Segment::validate` 报告,
/// 两者刻意分开。
pub(all) enum DecodeError {
  /// 没有以 `##` 开头
  MissingHeader
  /// 包头后的 4 位长度不是十进制数字
  BadLengthField(String)
  /// 长度字段声明的字节数超出实际可用文本
  Truncated(Int32, Int32)
  /// CRC 校验失败:声明的校验码、实际算出的校验码
  BadCrc(String, String)
  /// CRC 之后不是 ``
  BadTail(String)
  /// 数据段为空
  EmptySegment
  /// 缺少必需字段
  MissingField(String)
  /// 字段重复出现
  DuplicateField(String)
  /// 字段没有 `=` 分隔符
  BadField(String)
  /// 字段值不合法:字段名、取值
  BadFieldValue(String, String)
  /// 数据区结构不合法
  BadDataArea(String)
  /// CRC 与包尾之间还有多余字符
  TrailingGarbage(String)
}

///|
/// 稳定的错误码,供上层程序分支判断或写日志用。
///
/// 与 `message()` 的区别:`message()` 是给人看的中文描述,措辞可能随版本调整;
/// `kind()` 是给程序看的标识,一旦发布就不再变化。
pub fn DecodeError::kind(self : DecodeError) -> String {
  match self {
    MissingHeader => "MissingHeader"
    BadLengthField(_) => "BadLengthField"
    Truncated(_, _) => "Truncated"
    BadCrc(_, _) => "BadCrc"
    BadTail(_) => "BadTail"
    EmptySegment => "EmptySegment"
    MissingField(_) => "MissingField"
    DuplicateField(_) => "DuplicateField"
    BadField(_) => "BadField"
    BadFieldValue(_, _) => "BadFieldValue"
    BadDataArea(_) => "BadDataArea"
    TrailingGarbage(_) => "TrailingGarbage"
  }
}

///|
pub fn DecodeError::message(self : DecodeError) -> String {
  match self {
    MissingHeader => "报文没有以 \"##\" 开头"
    BadLengthField(s) => "长度字段 \"\{s}\" 不是 4 位十进制数字"
    Truncated(declared, available) =>
      "长度字段声明 \{declared} 字节,实际只有 \{available} 字节可用"
    BadCrc(declared, computed) =>
      "CRC 校验失败:报文声明 \{declared},实际应为 \{computed}"
    BadTail(s) => "包尾应为 CRLF,实际是 \{describe_invisible(s)}"
    EmptySegment => "数据段为空"
    MissingField(name) => "缺少必需字段 \{name}"
    DuplicateField(name) => "字段 \{name} 重复出现"
    BadField(name) => "字段 \{name} 缺少 \"=\" 分隔符"
    BadFieldValue(name, value) =>
      "字段 \{name} 的取值 \"\{value}\" 不合法"
    BadDataArea(reason) => "数据区不合法:\{reason}"
    TrailingGarbage(s) =>
      "包尾之后还有多余字符 \{describe_invisible(s)}"
  }
}

///|
/// 把不可见字符渲染成可读形式,用于错误信息。
fn describe_invisible(s : String) -> String {
  if s == "" {
    return "<空>"
  }
  let sb = StringBuilder()
  for c in s {
    let cp = c.to_int()
    if cp == 13 {
      sb.write_string("\\r")
    } else if cp == 10 {
      sb.write_string("\\n")
    } else if cp == 9 {
      sb.write_string("\\t")
    } else if cp == 32 {
      sb.write_string("␠")
    } else if cp < 32 {
      sb.write_string("\\x\{pad_hex2(cp)}")
    } else {
      sb.write_char(c)
    }
  }
  sb.to_string()
}

///|
/// 数据项的数据类型,对应标准 6.3.3.2.2 的类型定义表。
///
/// - `C4` 表示最多 4 位的字符串;
/// - `N5` 表示最多 5 位数字;
/// - `N14.2` 表示 14 位整数与 2 位小数,带符号,最大长度 18;
/// - `YYYY` / `MM` / `DD` / `hh` / `mm` / `ss` / `zzz` 表示时间片段。
pub(all) enum FieldType {
  Chars(Int32)
  Number(Int32, Int32)
  Year
  Month
  Day
  Hour
  Minute
  Second
  Millisecond
}

///|
/// 数据区中的一项:`名称=值`,同一项可以有多个用 `,` 分隔的值。
pub(all) struct DataItem {
  name : String
  mut values : Array[String]
}

///|
/// 指令参数(`CP`)的数据区。
///
/// 序列化时按标准约定:同项多值用 `,` 连接,不同项用 `;` 连接。
pub(all) struct DataArea {
  items : Array[DataItem]
}

///|
/// 数据段:一条命令的固定头部加上指令参数。
///
/// 字段顺序按标准固定为 `QN;ST;CN;PW;MN;Flag;PNUM;PNO;CP`。
/// `pnum` / `pno` 只在 `Flag` 的 D 位为 1 时出现;`extra` 用来保留
/// 现场设备自行追加的扩展字段,保证解码后再编码不会丢信息。
pub(all) struct Segment {
  qn : String
  st : String
  cn : String
  pw : String
  mn : String
  flag : Flag
  pnum : Int32?
  pno : Int32?
  cp : DataArea
  extra : Array[DataItem]
}

///|
/// 一条完整的报文。
pub(all) struct Frame {
  segment : Segment
}