// Canonical structure wire encoding and decoding.

import std.bytes { Bytes }
import std.json
import util { 바이트로, 바이트배열로, 바이트추가, 부분배열 }

export record 정수읽기 { 값: int, 다음: int }
export record 문자열읽기 { 값: string, 다음: int }
export record 사전읽기 { 값들: Array<string>, 다음: int }
export record 코드단위문자열 {
  키: string,
  안전값: string,
  안전: bool
}
export record 코드단위문자열읽기 { 값: 코드단위문자열, 다음: int }
export record 코드단위사전읽기 { 값들: Array<코드단위문자열>, 다음: int }
export record 갈루아표 { 지수: Array<int>, 로그: Array<int> }

// 구조 와이어의 낱말들. 십육진 표기와 가변 정수와 정렬 사전이
// 위의 모든 계층이 함께 쓰는 어휘다.
function 소문자십육진글자(값: int) -> string {
  ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"][값]
}

function 대문자십육진글자(값: int) -> string {
  ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"][값]
}

function 소문자십육진네자리(값: int) -> string {
  소문자십육진글자((값 / 4096) % 16) + 소문자십육진글자((값 / 256) % 16) +
    소문자십육진글자((값 / 16) % 16) + 소문자십육진글자(값 % 16)
}

export function 대문자십육진네자리(값: int) -> string {
  대문자십육진글자((값 / 4096) % 16) + 대문자십육진글자((값 / 256) % 16) +
    대문자십육진글자((값 / 16) % 16) + 대문자십육진글자(값 % 16)
}

export function 대문자십육진두자리(값: int) -> string {
  대문자십육진글자((값 / 16) % 16) + 대문자십육진글자(값 % 16)
}

function 십육진글자인가(글자: string) -> bool {
  (글자 >= "0" && 글자 <= "9") || (글자 >= "a" && 글자 <= "f") ||
    (글자 >= "A" && 글자 <= "F")
}

function 소문자십육진글자인가(글자: string) -> bool {
  (글자 >= "0" && 글자 <= "9") || (글자 >= "a" && 글자 <= "f")
}

function 제이슨십육진숫자(글자: string) -> int {
  if 글자 >= "0" && 글자 <= "9" { return (글자.codePointAt(0) ?? 48) - 48 }
  if 글자 >= "a" && 글자 <= "f" { return (글자.codePointAt(0) ?? 97) - 97 + 10 }
  (글자.codePointAt(0) ?? 65) - 65 + 10
}

function 높은대리자인가(단위: int) -> bool { 단위 >= 55296 && 단위 <= 56319 }
function 낮은대리자인가(단위: int) -> bool { 단위 >= 56320 && 단위 <= 57343 }

export function 널문자있음(값: string) -> bool {
  for 글자 in 값.scalars() {
    if (글자.codePointAt(0) ?? -1) == 0 { return true }
  }
  false
}

function 코드단위키만들기(단위들: Array<int>) -> string {
  let mut 조각들: Array<string> = []
  for 단위 in 단위들 { 조각들.push(소문자십육진네자리(단위)) }
  조각들.join("")
}

export function 안전문자열코드단위키(값: string) -> string {
  let mut 조각들: Array<string> = []
  for 글자 in 값.scalars() {
    let 코드 = 글자.codePointAt(0) ?? 0
    if 코드 <= 65535 { 조각들.push(소문자십육진네자리(코드)) }
    else {
      let 나머지 = 코드 - 65536
      조각들.push(소문자십육진네자리(55296 + 나머지 / 1024))
      조각들.push(소문자십육진네자리(56320 + 나머지 % 1024))
    }
  }
  조각들.join("")
}

export function 코드단위키읽기(키: string, 빈값허용: bool) -> Result<Array<int>, string> {
  let 글자들 = 키.scalars()
  if (!빈값허용 && 글자들.length == 0) || 글자들.length % 4 != 0 {
    return Err("UTF-16 코드 단위 키 길이가 틀립니다")
  }
  let mut 단위들: Array<int> = []
  let mut 색인 = 0
  while 색인 < 글자들.length {
    let mut 자리 = 0
    let mut 값 = 0
    while 자리 < 4 {
      let 글자 = 글자들[색인 + 자리]
      if !소문자십육진글자인가(글자) { return Err("UTF-16 코드 단위 키가 소문자 십육진수가 아닙니다") }
      값 = 값 * 16 + 제이슨십육진숫자(글자)
      자리 = 자리 + 1
    }
    단위들.push(값)
    색인 = 색인 + 4
  }
  Ok(단위들)
}

export function 짝없는대리자있음(단위들: Array<int>) -> bool {
  let mut 색인 = 0
  while 색인 < 단위들.length {
    let 단위 = 단위들[색인]
    if 높은대리자인가(단위) {
      if 색인 + 1 < 단위들.length && 낮은대리자인가(단위들[색인 + 1]) { 색인 = 색인 + 2 }
      else { return true }
    } else if 낮은대리자인가(단위) { return true }
    else { 색인 = 색인 + 1 }
  }
  false
}

function 안전코드단위값(단위들: Array<int>) -> Result<string, string> {
  if 짝없는대리자있음(단위들) { return Err("짝없는 UTF-16 대리자는 Topaz 문자열이 될 수 없습니다") }
  let mut 조각들: Array<string> = []
  let mut 색인 = 0
  while 색인 < 단위들.length {
    let 단위 = 단위들[색인]
    let 코드 = if 높은대리자인가(단위) {
      let 낮음 = 단위들[색인 + 1]
      색인 = 색인 + 2
      65536 + (단위 - 55296) * 1024 + (낮음 - 56320)
    } else {
      색인 = 색인 + 1
      단위
    }
    match fromCodePoint(코드) {
      case Some(글자) => { 조각들.push(글자) }
      case None => return Err("UTF-16 코드 단위가 유니코드 스칼라가 아닙니다")
    }
  }
  Ok(조각들.join(""))
}

function 와이어제이슨문자열(키: string) -> Result<string, string> {
  let 단위들 = 코드단위키읽기(키, true)?
  let mut 조각들: Array<string> = ["\""]
  let mut 색인 = 0
  while 색인 < 단위들.length {
    let 단위 = 단위들[색인]
    if 단위 == 34 { 조각들.push("\\\"") }
    else if 단위 == 92 { 조각들.push("\\\\") }
    else if 단위 == 8 { 조각들.push("\\b") }
    else if 단위 == 12 { 조각들.push("\\f") }
    else if 단위 == 10 { 조각들.push("\\n") }
    else if 단위 == 13 { 조각들.push("\\r") }
    else if 단위 == 9 { 조각들.push("\\t") }
    else if 높은대리자인가(단위) && 색인 + 1 < 단위들.length && 낮은대리자인가(단위들[색인 + 1]) {
      let 코드 = 65536 + (단위 - 55296) * 1024 + (단위들[색인 + 1] - 56320)
      match fromCodePoint(코드) {
        case Some(문자) => { 조각들.push(문자) }
        case None => return Err("UTF-16 대리자 쌍이 유니코드 스칼라가 아닙니다")
      }
      색인 = 색인 + 1
    } else if 높은대리자인가(단위) || 낮은대리자인가(단위) || 단위 < 32 {
      조각들.push("\\u")
      조각들.push(소문자십육진네자리(단위))
    } else {
      match fromCodePoint(단위) {
        case Some(문자) => { 조각들.push(문자) }
        case None => return Err("UTF-16 코드 단위가 유니코드 스칼라가 아닙니다")
      }
    }
    색인 = 색인 + 1
  }
  조각들.push("\"")
  Ok(조각들.join(""))
}

export function 코드단위문자열만들기(키: string) -> Result<코드단위문자열, string> {
  let 단위들 = 코드단위키읽기(키, true)?
  if 짝없는대리자있음(단위들) {
    return Ok(코드단위문자열 { 키: 키, 안전값: "", 안전: false })
  }
  Ok(코드단위문자열 { 키: 키, 안전값: 안전코드단위값(단위들)?, 안전: true })
}

function 제이슨문자열코드단위로(직렬: string) -> Result<코드단위문자열, string> {
  let 글자들 = 직렬.scalars()
  if 글자들.length < 2 || 글자들[0] != "\"" || 글자들[글자들.length - 1] != "\"" {
    return Err("스칼라 문자열 사전 값이 JSON 문자열이 아닙니다")
  }
  let mut 단위들: Array<int> = []
  let mut 색인 = 1
  while 색인 < 글자들.length - 1 {
    let 글자 = 글자들[색인]
    if 글자 == "\"" { return Err("스칼라 문자열 사전에 닫는 따옴표 뒤 글자가 있습니다") }
    if 글자 == "\\" {
      if 색인 + 1 >= 글자들.length - 1 { return Err("스칼라 문자열 사전의 탈출 문자가 잘렸습니다") }
      let 탈출 = 글자들[색인 + 1]
      if 탈출 == "u" {
        if 색인 + 5 >= 글자들.length - 1 { return Err("스칼라 문자열 사전의 유니코드 탈출이 잘렸습니다") }
        let mut 자리 = 0
        let mut 단위 = 0
        while 자리 < 4 {
          let 숫자글자 = 글자들[색인 + 2 + 자리]
          if !십육진글자인가(숫자글자) { return Err("스칼라 문자열 사전의 유니코드 탈출이 틀립니다") }
          단위 = 단위 * 16 + 제이슨십육진숫자(숫자글자)
          자리 = 자리 + 1
        }
        단위들.push(단위)
        색인 = 색인 + 6
      } else {
        if 탈출 == "\"" { 단위들.push(34) }
        else if 탈출 == "\\" { 단위들.push(92) }
        else if 탈출 == "/" { 단위들.push(47) }
        else if 탈출 == "b" { 단위들.push(8) }
        else if 탈출 == "f" { 단위들.push(12) }
        else if 탈출 == "n" { 단위들.push(10) }
        else if 탈출 == "r" { 단위들.push(13) }
        else if 탈출 == "t" { 단위들.push(9) }
        else { return Err("스칼라 문자열 사전의 탈출 문자가 틀립니다") }
        색인 = 색인 + 2
      }
    } else {
      let 코드 = 글자.codePointAt(0) ?? -1
      if 코드 < 32 { return Err("스칼라 문자열 사전에 탈출하지 않은 제어 문자가 있습니다") }
      if 코드 <= 65535 { 단위들.push(코드) }
      else {
        let 나머지 = 코드 - 65536
        단위들.push(55296 + 나머지 / 1024)
        단위들.push(56320 + 나머지 % 1024)
      }
      색인 = 색인 + 1
    }
  }
  if 색인 != 글자들.length - 1 { return Err("스칼라 문자열 사전 값 길이가 틀립니다") }
  let 키 = 코드단위키만들기(단위들)
  if 와이어제이슨문자열(키)? != 직렬 { return Err("스칼라 문자열 사전 값이 정규 JSON이 아닙니다") }
  if 짝없는대리자있음(단위들) {
    Ok(코드단위문자열 { 키: 키, 안전값: "", 안전: false })
  } else {
    Ok(코드단위문자열 { 키: 키, 안전값: JSON.parseAs<string>(직렬)?, 안전: true })
  }
}

export function 사전색인(값들: Array<string>, 찾기: string) -> int {
  let mut 색인 = 0
  while 색인 < 값들.length {
    if 값들[색인] == 찾기 { return 색인 }
    색인 = 색인 + 1
  }
  -1
}

export function 정렬고유(값들: Array<string>) -> Array<string> {
  let 정렬 = 값들.sorted()
  let mut 결과: Array<string> = []
  for 값 in 정렬 {
    if 결과.length == 0 || 결과[결과.length - 1] != 값 { 결과.push(값) }
  }
  결과
}

export function 가변정수쓰기(출력: Array<int>, 원본: int) -> Array<int> {
  let mut 결과 = 출력
  let mut 남음 = 원본
  while 남음 >= 128 {
    결과.push((남음 % 128) + 128)
    남음 = 남음 / 128
  }
  결과.push(남음)
  결과
}

export function 삼십이쓰기(출력: Array<int>, 값: int) -> Array<int> {
  let mut 결과 = 출력
  결과.push((값 / 16777216) % 256)
  결과.push((값 / 65536) % 256)
  결과.push((값 / 256) % 256)
  결과.push(값 % 256)
  결과
}

export function 삼십이읽기(입력: Array<int>, 위치: int) -> int {
  입력[위치] * 16777216 + 입력[위치 + 1] * 65536 + 입력[위치 + 2] * 256 + 입력[위치 + 3]
}

function 정규문자열쓰기(출력: Array<int>, 값: string) -> Result<Array<int>, string> {
  let 직렬 = JSON.stringify(값)?
  let 바이트들 = 바이트배열로(Bytes.encodeUtf8(직렬))
  let 길이쓴 = 가변정수쓰기(출력, 바이트들.length)
  Ok(바이트추가(길이쓴, 바이트들))
}

function 코드단위문자열쓰기(출력: Array<int>, 키: string) -> Result<Array<int>, string> {
  let 직렬 = 와이어제이슨문자열(키)?
  let 바이트들 = 바이트배열로(Bytes.encodeUtf8(직렬))
  let 길이쓴 = 가변정수쓰기(출력, 바이트들.length)
  Ok(바이트추가(길이쓴, 바이트들))
}

export function 사전쓰기(출력: Array<int>, 값들: Array<string>) -> Result<Array<int>, string> {
  let mut 결과 = 가변정수쓰기(출력, 값들.length)
  for 값 in 값들 { 결과 = 정규문자열쓰기(결과, 값)? }
  Ok(결과)
}

export function 코드단위사전쓰기(출력: Array<int>, 키들: Array<string>) -> Result<Array<int>, string> {
  let mut 결과 = 가변정수쓰기(출력, 키들.length)
  for 키 in 키들 { 결과 = 코드단위문자열쓰기(결과, 키)? }
  Ok(결과)
}

export function 가변정수읽기(입력: Array<int>, 시작: int) -> Result<정수읽기, string> {
  let mut 값 = 0
  let mut 배수 = 1
  let mut 위치 = 시작
  let mut 색인 = 0
  while 색인 < 8 {
    if 위치 >= 입력.length { return Err("가변 정수가 잘렸습니다") }
    let 바이트 = 입력[위치]
    위치 = 위치 + 1
    let 숫자 = 바이트 % 128
    if 숫자 > (9007199254740991 - 값) / 배수 { return Err("가변 정수가 안전 범위를 넘습니다") }
    값 = 값 + 숫자 * 배수
    if 바이트 < 128 { return Ok(정수읽기 { 값: 값, 다음: 위치 }) }
    배수 = 배수 * 128
    색인 = 색인 + 1
  }
  Err("가변 정수가 너무 깁니다")
}

function 정규문자열읽기(입력: Array<int>, 시작: int) -> Result<문자열읽기, string> {
  let 길이읽기 = 가변정수읽기(입력, 시작)?
  let 끝 = 길이읽기.다음 + 길이읽기.값
  if 끝 > 입력.length { return Err("문자열이 잘렸습니다") }
  let 조각 = 바이트로(부분배열(입력, 길이읽기.다음, 끝))?
  let 직렬 = 조각.decodeUtf8()?
  let 값 = JSON.parseAs<string>(직렬)?
  if JSON.stringify(값)? != 직렬 { return Err("문자열이 정규 JSON이 아닙니다") }
  Ok(문자열읽기 { 값: 값, 다음: 끝 })
}

function 코드단위문자열해석(입력: Array<int>, 시작: int) -> Result<코드단위문자열읽기, string> {
  let 길이읽기 = 가변정수읽기(입력, 시작)?
  let 끝 = 길이읽기.다음 + 길이읽기.값
  if 끝 > 입력.length { return Err("스칼라 문자열이 잘렸습니다") }
  let 조각 = 바이트로(부분배열(입력, 길이읽기.다음, 끝))?
  let 직렬 = 조각.decodeUtf8()?
  Ok(코드단위문자열읽기 { 값: 제이슨문자열코드단위로(직렬)?, 다음: 끝 })
}

export function 사전해석(입력: Array<int>, 시작: int) -> Result<사전읽기, string> {
  let 수읽기 = 가변정수읽기(입력, 시작)?
  let mut 위치 = 수읽기.다음
  let mut 값들: Array<string> = []
  let mut 색인 = 0
  while 색인 < 수읽기.값 {
    let 읽기 = 정규문자열읽기(입력, 위치)?
    if 색인 > 0 && 값들[색인 - 1] >= 읽기.값 { return Err("사전이 엄격히 정렬되지 않았습니다") }
    값들.push(읽기.값)
    위치 = 읽기.다음
    색인 = 색인 + 1
  }
  Ok(사전읽기 { 값들: 값들, 다음: 위치 })
}

export function 코드단위사전해석(입력: Array<int>, 시작: int) -> Result<코드단위사전읽기, string> {
  let 수읽기 = 가변정수읽기(입력, 시작)?
  let mut 위치 = 수읽기.다음
  let mut 값들: Array<코드단위문자열> = []
  let mut 색인 = 0
  while 색인 < 수읽기.값 {
    let 읽기 = 코드단위문자열해석(입력, 위치)?
    if 색인 > 0 && 값들[색인 - 1].키 >= 읽기.값.키 {
      return Err("스칼라 문자열 사전이 UTF-16 코드 단위 순서로 정렬되지 않았습니다")
    }
    값들.push(읽기.값)
    위치 = 읽기.다음
    색인 = 색인 + 1
  }
  Ok(코드단위사전읽기 { 값들: 값들, 다음: 위치 })
}
