///|
/// JIS 三码解码:Shift_JIS / EUC-JP / ISO-2022-JP。判词真源 = 锁版 WHATWG
/// Encoding Standard(`.encodings/encoding-standard.html` §12.1.1 / §12.2.1 /
/// §12.3.1;标签映射 §4.2)。数据面 = `jis_tables_gen.mbt`(jis0208_index /
/// jis0212_index,槽 0 = 未定义)。
/// 错误即拒:规范的 restore-to-ioQueue 只服务容错续解,本引擎无续解语义,
/// 故省略恢复步(错误位置 = 违例字节)。
/// 施工账:ctx.md §9(役J1)。

///|
/// JIS 编码判别(声明名已小写;§4.2 名→编码映射逐标签处理)。
priv enum JisEncoding {
  ShiftJis
  EucJp
  Iso2022Jp
}

///|
fn jis_encoding_of_label(name : String) -> JisEncoding? {
  match name {
    "shift_jis"
    | "shift-jis"
    | "sjis"
    | "windows-31j"
    | "csshiftjis"
    | "ms932"
    | "ms_kanji"
    | "x-sjis" => Some(ShiftJis)
    "euc-jp" | "cseucpkdfmtjapanese" | "x-euc-jp" => Some(EucJp)
    "iso-2022-jp" | "csiso2022jp" => Some(Iso2022Jp)
    _ => None
  }
}

///|
/// 索引查码(spec "the index code point for pointer in index …"):
/// 越界即 null;槽 0 = 表内未定义槽,同为 null。0 作哨码位返回。
fn jis_index_codepoint(table : Array[Int32], pointer : Int32) -> Int32 {
  if pointer < 0 || pointer >= table.length() {
    return 0
  }
  table[pointer]
}

///|
fn decode_jis(
  encoding : JisEncoding,
  view : BytesView,
) -> String raise XmlError {
  match encoding {
    ShiftJis => decode_shift_jis(view)
    EucJp => decode_euc_jp(view)
    Iso2022Jp => decode_iso_2022_jp(view)
  }
}

///|
/// Shift_JIS(§12.3.1):单字节三段(ASCII|0x80→同码位、半角片假名
/// 0xA1-0xDF、双字节引字节 0x81-0x9F/0xE0-0xFC);双字节 pointer =
/// (lead−leadOffset)×188 + byte−offset,leadOffset 按 lead<0xA0 取
/// 0x81/0xC1,offset 按 byte<0x7F 取 0x40/0x41(0x7F 自然出局);
/// pointer 8836-10715 = EUDC 段,映射 0xE000−8836+pointer,先于表查。
fn decode_shift_jis(view : BytesView) -> String raise XmlError {
  let chars : Array[Char] = []
  let mut leading = 0
  for cursor = 0, scan = view {
    match scan {
      [] => break
      [byte, .. rest] => {
        let value = byte.to_int()
        if leading != 0 {
          let lead = leading
          leading = 0
          let offset = if value < 0x7F { 0x40 } else { 0x41 }
          let lead_offset = if lead < 0xA0 { 0x81 } else { 0xC1 }
          let mut pointer = -1
          if (value >= 0x40 && value <= 0x7E) ||
            (value >= 0x80 && value <= 0xFC) {
            pointer = (lead - lead_offset) * 188 + value - offset
          }
          if pointer >= 8836 && pointer <= 10715 {
            chars.push((0xE000 - 8836 + pointer).unsafe_to_char())
            continue cursor + 1, rest
          }
          let codepoint = jis_index_codepoint(jis0208_index, pointer)
          if codepoint != 0 {
            chars.push(codepoint.unsafe_to_char())
            continue cursor + 1, rest
          }
          raise encoding_error(view, cursor, "invalid Shift_JIS sequence")
        }
        if value <= 0x7F || value == 0x80 {
          chars.push(value.unsafe_to_char())
          continue cursor + 1, rest
        }
        if value >= 0xA1 && value <= 0xDF {
          chars.push((0xFF61 - 0xA1 + value).unsafe_to_char())
          continue cursor + 1, rest
        }
        if (value >= 0x81 && value <= 0x9F) || (value >= 0xE0 && value <= 0xFC) {
          leading = value
          continue cursor + 1, rest
        }
        raise encoding_error(view, cursor, "invalid Shift_JIS byte")
      }
    }
  }
  if leading != 0 {
    raise encoding_error(
      view,
      view.length() - 1,
      "truncated Shift_JIS sequence",
    )
  }
  String::from_array(chars[:])
}

///|
/// EUC-JP(§12.1.1):单字节 0x00-0x7F 直通;0x8E + 0xA1-0xDF = 半角
/// 片假名;0x8F + 0xA1-0xFE = jis0212 引字节;其余 0x8E/0x8F/0xA1-0xFE
/// 引 jis0208 对:pointer = (lead−0xA1)×94 + byte−0xA1,jis0212 旗只在
/// 0x8F 面立,用毕即清。
fn decode_euc_jp(view : BytesView) -> String raise XmlError {
  let chars : Array[Char] = []
  let mut leading = 0
  let mut in_jis0212 = false
  for cursor = 0, scan = view {
    match scan {
      [] => break
      [byte, .. rest] => {
        let value = byte.to_int()
        if leading == 0x8E && value >= 0xA1 && value <= 0xDF {
          leading = 0
          chars.push((0xFF61 - 0xA1 + value).unsafe_to_char())
          continue cursor + 1, rest
        }
        if leading == 0x8F && value >= 0xA1 && value <= 0xFE {
          in_jis0212 = true
          leading = value
          continue cursor + 1, rest
        }
        if leading != 0 {
          let lead = leading
          leading = 0
          let mut codepoint = 0
          if lead >= 0xA1 && lead <= 0xFE && value >= 0xA1 && value <= 0xFE {
            let pointer = (lead - 0xA1) * 94 + (value - 0xA1)
            let table = if in_jis0212 { jis0212_index } else { jis0208_index }
            codepoint = jis_index_codepoint(table, pointer)
          }
          in_jis0212 = false
          if codepoint != 0 {
            chars.push(codepoint.unsafe_to_char())
            continue cursor + 1, rest
          }
          raise encoding_error(view, cursor, "invalid EUC-JP sequence")
        }
        if value <= 0x7F {
          chars.push(value.unsafe_to_char())
          continue cursor + 1, rest
        }
        if value == 0x8E || value == 0x8F || (value >= 0xA1 && value <= 0xFE) {
          leading = value
          continue cursor + 1, rest
        }
        raise encoding_error(view, cursor, "invalid EUC-JP byte")
      }
    }
  }
  if leading != 0 {
    raise encoding_error(view, view.length() - 1, "truncated EUC-JP sequence")
  }
  String::from_array(chars[:])
}

///|
/// ISO-2022-JP 状态(spec §12.2.1 七态:ASCII / Roman / katakana /
/// lead byte / trail byte / escape start / escape)。
priv enum Jis2022State {
  Ascii
  Roman
  Katakana
  LeadByte
  TrailByte
  EscapeStart
  Escape
}

///|
/// ISO-2022-JP(§12.2.1):转义序 切换状态(ESC ( B→ASCII、
/// ESC ( J→Roman(0x5C→¥、0x7E→‾)、ESC ( I→半角片假名、ESC $ @|B→
/// 双字节 jis0208 对 0x21-0x7E);输出断言"转义后须吐码"——连转两次
/// 即错;ASCII/Roman 态 0x0E/0x0F 拒。终态 TrailByte/EscapeStart/Escape
/// 收尾 = 截断拒。
fn decode_iso_2022_jp(view : BytesView) -> String raise XmlError {
  let chars : Array[Char] = []
  let mut state = Ascii
  let mut output_state = Ascii
  let mut leading = 0
  let mut output = false
  for cursor = 0, scan = view {
    match scan {
      [] => break
      [byte, .. rest] => {
        let value = byte.to_int()
        match state {
          Ascii => {
            if value == 0x1B {
              state = EscapeStart
              continue cursor + 1, rest
            }
            if value <= 0x7F && value != 0x0E && value != 0x0F {
              output = false
              chars.push(value.unsafe_to_char())
              continue cursor + 1, rest
            }
            raise encoding_error(view, cursor, "invalid ISO-2022-JP ASCII byte")
          }
          Roman => {
            if value == 0x1B {
              state = EscapeStart
              continue cursor + 1, rest
            }
            if value == 0x5C {
              output = false
              chars.push((0x00A5).unsafe_to_char())
              continue cursor + 1, rest
            }
            if value == 0x7E {
              output = false
              chars.push((0x203E).unsafe_to_char())
              continue cursor + 1, rest
            }
            if value <= 0x7F && value != 0x0E && value != 0x0F {
              output = false
              chars.push(value.unsafe_to_char())
              continue cursor + 1, rest
            }
            raise encoding_error(view, cursor, "invalid ISO-2022-JP Roman byte")
          }
          Katakana => {
            if value == 0x1B {
              state = EscapeStart
              continue cursor + 1, rest
            }
            if value >= 0x21 && value <= 0x5F {
              output = false
              chars.push((0xFF61 - 0x21 + value).unsafe_to_char())
              continue cursor + 1, rest
            }
            raise encoding_error(
              view, cursor, "invalid ISO-2022-JP katakana byte",
            )
          }
          LeadByte => {
            if value == 0x1B {
              state = EscapeStart
              continue cursor + 1, rest
            }
            if value >= 0x21 && value <= 0x7E {
              output = false
              leading = value
              state = TrailByte
              continue cursor + 1, rest
            }
            raise encoding_error(view, cursor, "invalid ISO-2022-JP lead byte")
          }
          TrailByte => {
            if value == 0x1B {
              state = EscapeStart
              raise encoding_error(
                view, cursor, "ESC in ISO-2022-JP trailing byte",
              )
            }
            if value >= 0x21 && value <= 0x7E {
              state = LeadByte
              let pointer = (leading - 0x21) * 94 + (value - 0x21)
              let codepoint = jis_index_codepoint(jis0208_index, pointer)
              if codepoint == 0 {
                raise encoding_error(
                  view, cursor, "undefined ISO-2022-JP code point",
                )
              }
              output = false
              chars.push(codepoint.unsafe_to_char())
              continue cursor + 1, rest
            }
            state = LeadByte
            raise encoding_error(
              view, cursor, "invalid ISO-2022-JP trailing byte",
            )
          }
          EscapeStart => {
            if value == 0x24 || value == 0x28 {
              leading = value
              state = Escape
              continue cursor + 1, rest
            }
            output = false
            state = output_state
            raise encoding_error(
              view, cursor, "invalid ISO-2022-JP escape start",
            )
          }
          Escape => {
            let lead = leading
            leading = 0
            let entered : Jis2022State? = if lead == 0x28 && value == 0x42 {
              Some(Ascii)
            } else if lead == 0x28 && value == 0x4A {
              Some(Roman)
            } else if lead == 0x28 && value == 0x49 {
              Some(Katakana)
            } else if lead == 0x24 && (value == 0x40 || value == 0x42) {
              Some(LeadByte)
            } else {
              None
            }
            match entered {
              Some(next) => {
                state = next
                output_state = next
                let emitted_since_escape = output
                output = true
                if emitted_since_escape {
                  raise encoding_error(
                    view, cursor, "redundant ISO-2022-JP escape sequence",
                  )
                }
                continue cursor + 1, rest
              }
              None => {
                output = false
                state = output_state
                raise encoding_error(
                  view, cursor, "invalid ISO-2022-JP escape sequence",
                )
              }
            }
          }
        }
      }
    }
  }
  if state is TrailByte || state is EscapeStart || state is Escape {
    raise encoding_error(
      view,
      view.length() - 1,
      "truncated ISO-2022-JP sequence",
    )
  }
  String::from_array(chars[:])
}