// Canonical raster and PNG rendering.

import std.bytes { Bytes }
import std.hash
import plan { 보호길이, 프로필해시, 픽셀글꼴 }
import util { 거듭제곱이, 문자열배열에있음, 작은값, 큰값 }

export record 이미지머리 {
  버전: int,
  언어번호: int,
  팔레트번호: int,
  너비: int,
  높이: int,
  판행수: int,
  띠위: int,
  띠행수: int,
  노드수: int,
  보호길이: int,
  논리길이: int,
  패리티길이: int,
  프로필해시: string,
  키해시: string,
  구조해시앞: string,
  쪽번호: int,
  쪽수: int,
  노드시작: int,
  크롬번호: int,
  제목: string,
  원본줄수: int,
  세트해시: string
}

export record 비트기록 { 출력: ByteBuffer, 출력길이: int, 값: int, 수: int }
export record 일치 { 길이: int, 거리: int }
export record 비트읽기 { 값: int, 다음: int }
export record 부풀림 { 잡은바이트: Array<int>, 출력길이: int }
export record 원시쪽 { 머리: 이미지머리, 머리바이트: Array<int>, 띠바이트: Array<int> }

export function 이역할변형(필드: string) -> int {
  if 문자열배열에있음(["exported", "exprName", "id", "imported", "key", "label", "local", "meta", "name", "namespace"], 필드) { return 0 }
  if 문자열배열에있음(["param", "parameter", "parameterName", "parameters", "params", "rest"], 필드) { return 1 }
  if 문자열배열에있음(["argument", "arguments", "callee", "object", "property", "qualification", "qualifier", "quasi", "quasis", "tag"], 필드) { return 2 }
  if 문자열배열에있음(["alternate", "cases", "consequent", "default", "discriminant", "finalizer", "handler", "test"], 필드) { return 3 }
  if 문자열배열에있음(["block", "body", "children", "closingElement", "closingFragment", "openingElement", "openingFragment", "program"], 필드) { return 4 }
  if 문자열배열에있음(["expression", "expressions", "init", "initializer", "left", "literal", "right", "source", "update", "value"], 필드) { return 5 }
  if 문자열배열에있음(["assertions", "attributes", "declaration", "declarations", "decorators", "directives", "elements", "members", "options", "properties", "specifiers"], 필드) { return 6 }
  if 문자열배열에있음(["bound", "callProperties", "checkType", "constraint", "elementType", "elementTypes", "extends", "extendsType", "falseType", "implements", "impltype", "indexers", "indexType", "internalSlots", "mixins", "moduleReference", "nameType", "objectType", "predicate", "returnType", "superClass", "supertype", "superTypeParameters", "this", "trueType", "typeAnnotation", "typeArguments", "typeName", "typeParameter", "typeParameters", "types", "variance"], 필드) { return 7 }
  8
}

export function 사각안(가로: int, 세로: int, 왼쪽: int, 위: int, 너비: int, 높이: int) -> bool {
  가로 >= 왼쪽 && 가로 < 왼쪽 + 너비 && 세로 >= 위 && 세로 < 위 + 높이
}

export function 글자그림(글: string, 왼쪽: int, 위: int, 배율: int, 가로: int, 세로: int) -> bool {
  if 세로 < 위 || 세로 >= 위 + 5 * 배율 || 가로 < 왼쪽 { return false }
  let 글자들 = 글.scalars()
  let 상대가로 = 가로 - 왼쪽
  let 글자폭 = 4 * 배율
  let 글자색인 = 상대가로 / 글자폭
  if 글자색인 >= 글자들.length { return false }
  let 글자안가로 = 상대가로 % 글자폭
  if 글자안가로 >= 3 * 배율 { return false }
  let 열 = 글자안가로 / 배율
  let 행 = (세로 - 위) / 배율
  픽셀글꼴(글자들[글자색인]).scalars()[행 * 3 + 열] == "1"
}

function 비트뒤집기(값: int, 수: int) -> int {
  let mut 원본 = 값
  let mut 결과 = 0
  let mut 색인 = 0
  while 색인 < 수 { 결과 = 결과 * 2 + 원본 % 2; 원본 = 원본 / 2; 색인 = 색인 + 1 }
  결과
}

function 고정기호비트(기호: int) -> int {
  if 기호 <= 143 { return 비트뒤집기(48 + 기호, 8) }
  if 기호 <= 255 { return 비트뒤집기(400 + 기호 - 144, 9) }
  if 기호 <= 279 { return 비트뒤집기(기호 - 256, 7) }
  비트뒤집기(192 + 기호 - 280, 8)
}

function 고정기호길이(기호: int) -> int {
  if 기호 <= 143 { return 8 }
  if 기호 <= 255 { return 9 }
  if 기호 <= 279 { return 7 }
  8
}

function 비트쓰기(기록: 비트기록, 값: int, 수: int) -> 비트기록 {
  let mut 출력 = 기록.출력
  let mut 출력길이 = 기록.출력길이
  let mut 대기값 = 기록.값 + 값 * 거듭제곱이(기록.수)
  let mut 대기수 = 기록.수 + 수
  while 대기수 >= 8 {
    출력.set(출력길이, 대기값 % 256); 출력길이 = 출력길이 + 1
    대기값 = 대기값 / 256; 대기수 = 대기수 - 8
  }
  비트기록 { 출력: 출력, 출력길이: 출력길이, 값: 대기값, 수: 대기수 }
}

function 고정기호쓰기(기록: 비트기록, 기호: int) -> 비트기록 {
  비트쓰기(기록, 고정기호비트(기호), 고정기호길이(기호))
}

function 길이밑들() -> Array<int> { [3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258] }
function 길이더비트들() -> Array<int> { [0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0] }
function 거리밑들() -> Array<int> { [1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577] }
function 거리더비트들() -> Array<int> { [0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13] }

function 밑색인(밑들: Array<int>, 값: int) -> int {
  let mut 색인 = 밑들.length - 1
  while 색인 >= 0 { if 값 >= 밑들[색인] { return 색인 }; 색인 = 색인 - 1 }
  -1
}

function 일치비트수(길이: int, 거리: int) -> int {
  let 길이색인 = 밑색인(길이밑들(), 길이)
  let 거리색인 = 밑색인(거리밑들(), 거리)
  고정기호길이(257 + 길이색인) + 길이더비트들()[길이색인] + 5 + 거리더비트들()[거리색인]
}

function 글자비트수(입력: ByteBuffer, 위치: int, 길이: int) -> int {
  let mut 비트 = 0
  let mut 색인 = 위치
  while 색인 < 위치 + 길이 { 비트 = 비트 + if 입력.get(색인) <= 143 { 8 } else { 9 }; 색인 = 색인 + 1 }
  비트
}

function 위치넣기(입력: ByteBuffer, 위치: int, 전체길이: int, 해시머리: ByteBuffer, 이전위치: ByteBuffer, 자리위치: ByteBuffer) -> bool {
  if 위치 + 3 > 전체길이 { return false }
  let 값 = 입력.get(위치) * 65536 + 입력.get(위치 + 1) * 256 + 입력.get(위치 + 2)
  let 해시 = ((값 * 506832829) % 4294967296) / 65536
  let 자리 = 위치 % 32768
  let 해시자리 = 해시 * 4
  let 이전 = 해시머리.get(해시자리) * 16777216 + 해시머리.get(해시자리 + 1) * 65536 +
    해시머리.get(해시자리 + 2) * 256 + 해시머리.get(해시자리 + 3)
  let 자리바이트 = 자리 * 4
  let mut 이전출력 = 이전위치
  이전출력.set(자리바이트, (이전 / 16777216) % 256); 이전출력.set(자리바이트 + 1, (이전 / 65536) % 256)
  이전출력.set(자리바이트 + 2, (이전 / 256) % 256); 이전출력.set(자리바이트 + 3, 이전 % 256)
  let 저장 = 위치 + 1
  let mut 자리출력 = 자리위치
  자리출력.set(자리바이트, (저장 / 16777216) % 256); 자리출력.set(자리바이트 + 1, (저장 / 65536) % 256)
  자리출력.set(자리바이트 + 2, (저장 / 256) % 256); 자리출력.set(자리바이트 + 3, 저장 % 256)
  let mut 머리출력 = 해시머리
  머리출력.set(해시자리, (저장 / 16777216) % 256); 머리출력.set(해시자리 + 1, (저장 / 65536) % 256)
  머리출력.set(해시자리 + 2, (저장 / 256) % 256); 머리출력.set(해시자리 + 3, 저장 % 256)
  true
}

function 일치찾기(입력: ByteBuffer, 위치: int, 전체길이: int, 해시머리: ByteBuffer, 이전위치: ByteBuffer, 자리위치: ByteBuffer) -> 일치 {
  if 위치 + 3 > 전체길이 { return 일치 { 길이: 0, 거리: 0 } }
  let 값 = 입력.get(위치) * 65536 + 입력.get(위치 + 1) * 256 + 입력.get(위치 + 2)
  let 해시 = ((값 * 506832829) % 4294967296) / 65536
  let 최소위치 = 큰값(0, 위치 - 32768)
  let 최대길이 = 작은값(258, 전체길이 - 위치)
  let 해시자리 = 해시 * 4
  let mut 후보 = 해시머리.get(해시자리) * 16777216 + 해시머리.get(해시자리 + 1) * 65536 +
    해시머리.get(해시자리 + 2) * 256 + 해시머리.get(해시자리 + 3) - 1
  let mut 찾은수 = 0
  let mut 가장길이 = 0
  let mut 가장거리 = 0
  while 후보 >= 최소위치 && 후보 >= 0 && 찾은수 < 128 {
    let 자리 = 후보 % 32768
    let 자리바이트 = 자리 * 4
    let 자리값 = 자리위치.get(자리바이트) * 16777216 + 자리위치.get(자리바이트 + 1) * 65536 +
      자리위치.get(자리바이트 + 2) * 256 + 자리위치.get(자리바이트 + 3) - 1
    if 자리값 != 후보 { 후보 = -1 }
    else {
      if 가장길이 == 0 || 가장길이 == 최대길이 || 입력.get(후보 + 가장길이) == 입력.get(위치 + 가장길이) {
        let mut 길이 = 0
        while 길이 < 최대길이 && 입력.get(후보 + 길이) == 입력.get(위치 + 길이) { 길이 = 길이 + 1 }
        if 길이 > 가장길이 {
          가장길이 = 길이; 가장거리 = 위치 - 후보
          if 가장길이 == 최대길이 { 후보 = -1 }
        }
      }
      if 후보 >= 0 {
        후보 = 이전위치.get(자리바이트) * 16777216 + 이전위치.get(자리바이트 + 1) * 65536 +
          이전위치.get(자리바이트 + 2) * 256 + 이전위치.get(자리바이트 + 3) - 1
        찾은수 = 찾은수 + 1
      }
    }
  }
  if 가장길이 < 3 || 일치비트수(가장길이, 가장거리) >= 글자비트수(입력, 위치, 가장길이) { return 일치 { 길이: 0, 거리: 0 } }
  일치 { 길이: 가장길이, 거리: 가장거리 }
}

function 고정일치쓰기(기록: 비트기록, 길이: int, 거리: int) -> 비트기록 {
  let 길이밑 = 길이밑들(); let 길이더 = 길이더비트들()
  let 거리밑 = 거리밑들(); let 거리더 = 거리더비트들()
  let 길이색인 = 밑색인(길이밑, 길이)
  let mut 결과 = 고정기호쓰기(기록, 257 + 길이색인)
  if 길이더[길이색인] > 0 { 결과 = 비트쓰기(결과, 길이 - 길이밑[길이색인], 길이더[길이색인]) }
  let 거리색인 = 밑색인(거리밑, 거리)
  결과 = 비트쓰기(결과, 비트뒤집기(거리색인, 5), 5)
  if 거리더[거리색인] > 0 { 결과 = 비트쓰기(결과, 거리 - 거리밑[거리색인], 거리더[거리색인]) }
  결과
}

export function 청크만들기(종류바이트: Bytes, 자료바이트: Bytes) -> Bytes {
  let 종류 = ByteBuffer.fromBytes(종류바이트)
  let 자료 = ByteBuffer.fromBytes(자료바이트)
  let 자료길이 = 자료.length()
  let mut 결과 = ByteBuffer.allocate(12 + 자료길이)
  결과.set(0, (자료길이 / 16777216) % 256); 결과.set(1, (자료길이 / 65536) % 256)
  결과.set(2, (자료길이 / 256) % 256); 결과.set(3, 자료길이 % 256)
  결과.copy(종류, 0, 4, 종류.length()); 결과.copy(자료, 0, 8, 자료길이)
  let mut 검사입력 = ByteBuffer.allocate(종류.length() + 자료길이)
  검사입력.copy(종류, 0, 0, 종류.length()); 검사입력.copy(자료, 0, 종류.length(), 자료길이)
  let 검사합 = Hash.crc32(검사입력.toBytes())
  let 위치 = 8 + 자료길이
  결과.set(위치, (검사합 / 16777216) % 256); 결과.set(위치 + 1, (검사합 / 65536) % 256)
  결과.set(위치 + 2, (검사합 / 256) % 256); 결과.set(위치 + 3, 검사합 % 256)
  결과.toBytes()
}
