import * as Either from "../../Either.js"
import type * as Encoding from "../../Encoding.js"
import { DecodeException } from "./common.js"

/** @internal */
export const encode = (bytes: Uint8Array) => {
  let result = ""
  for (let i = 0; i < bytes.length; ++i) {
    result += bytesToHex[bytes[i]]
  }

  return result
}

/** @internal */
export const decode = (str: string): Either.Either<Uint8Array, Encoding.DecodeException> => {
  const bytes = new TextEncoder().encode(str)
  if (bytes.length % 2 !== 0) {
    return Either.left(DecodeException(str, `Length must be a multiple of 2, but is ${bytes.length}`))
  }

  try {
    const length = bytes.length / 2
    const result = new Uint8Array(length)
    for (let i = 0; i < length; i++) {
      const a = fromHexChar(bytes[i * 2])
      const b = fromHexChar(bytes[i * 2 + 1])
      result[i] = (a << 4) | b
    }

    return Either.right(result)
  } catch (e) {
    return Either.left(DecodeException(str, e instanceof Error ? e.message : "Invalid input"))
  }
}

/** @internal */
const bytesToHex = [
  "00",
  "01",
  "02",
  "03",
  "04",
  "05",
  "06",
  "07",
  "08",
  "09",
  "0a",
  "0b",
  "0c",
  "0d",
  "0e",
  "0f",
  "10",
  "11",
  "12",
  "13",
  "14",
  "15",
  "16",
  "17",
  "18",
  "19",
  "1a",
  "1b",
  "1c",
  "1d",
  "1e",
  "1f",
  "20",
  "21",
  "22",
  "23",
  "24",
  "25",
  "26",
  "27",
  "28",
  "29",
  "2a",
  "2b",
  "2c",
  "2d",
  "2e",
  "2f",
  "30",
  "31",
  "32",
  "33",
  "34",
  "35",
  "36",
  "37",
  "38",
  "39",
  "3a",
  "3b",
  "3c",
  "3d",
  "3e",
  "3f",
  "40",
  "41",
  "42",
  "43",
  "44",
  "45",
  "46",
  "47",
  "48",
  "49",
  "4a",
  "4b",
  "4c",
  "4d",
  "4e",
  "4f",
  "50",
  "51",
  "52",
  "53",
  "54",
  "55",
  "56",
  "57",
  "58",
  "59",
  "5a",
  "5b",
  "5c",
  "5d",
  "5e",
  "5f",
  "60",
  "61",
  "62",
  "63",
  "64",
  "65",
  "66",
  "67",
  "68",
  "69",
  "6a",
  "6b",
  "6c",
  "6d",
  "6e",
  "6f",
  "70",
  "71",
  "72",
  "73",
  "74",
  "75",
  "76",
  "77",
  "78",
  "79",
  "7a",
  "7b",
  "7c",
  "7d",
  "7e",
  "7f",
  "80",
  "81",
  "82",
  "83",
  "84",
  "85",
  "86",
  "87",
  "88",
  "89",
  "8a",
  "8b",
  "8c",
  "8d",
  "8e",
  "8f",
  "90",
  "91",
  "92",
  "93",
  "94",
  "95",
  "96",
  "97",
  "98",
  "99",
  "9a",
  "9b",
  "9c",
  "9d",
  "9e",
  "9f",
  "a0",
  "a1",
  "a2",
  "a3",
  "a4",
  "a5",
  "a6",
  "a7",
  "a8",
  "a9",
  "aa",
  "ab",
  "ac",
  "ad",
  "ae",
  "af",
  "b0",
  "b1",
  "b2",
  "b3",
  "b4",
  "b5",
  "b6",
  "b7",
  "b8",
  "b9",
  "ba",
  "bb",
  "bc",
  "bd",
  "be",
  "bf",
  "c0",
  "c1",
  "c2",
  "c3",
  "c4",
  "c5",
  "c6",
  "c7",
  "c8",
  "c9",
  "ca",
  "cb",
  "cc",
  "cd",
  "ce",
  "cf",
  "d0",
  "d1",
  "d2",
  "d3",
  "d4",
  "d5",
  "d6",
  "d7",
  "d8",
  "d9",
  "da",
  "db",
  "dc",
  "dd",
  "de",
  "df",
  "e0",
  "e1",
  "e2",
  "e3",
  "e4",
  "e5",
  "e6",
  "e7",
  "e8",
  "e9",
  "ea",
  "eb",
  "ec",
  "ed",
  "ee",
  "ef",
  "f0",
  "f1",
  "f2",
  "f3",
  "f4",
  "f5",
  "f6",
  "f7",
  "f8",
  "f9",
  "fa",
  "fb",
  "fc",
  "fd",
  "fe",
  "ff"
]

/** @internal */
const fromHexChar = (byte: number) => {
  // '0' <= byte && byte <= '9'
  if (48 <= byte && byte <= 57) {
    return byte - 48
  }

  // 'a' <= byte && byte <= 'f'
  if (97 <= byte && byte <= 102) {
    return byte - 97 + 10
  }

  // 'A' <= byte && byte <= 'F'
  if (65 <= byte && byte <= 70) {
    return byte - 65 + 10
  }

  throw new TypeError("Invalid input")
}
