/*
 * Copyright 2021 Google Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
@file:Suppress("NOTHING_TO_INLINE")

package com.google.flatbuffers.kotlin

import com.google.flatbuffers.kotlin.FlexBuffersBuilder.Companion.SHARE_KEYS_AND_STRINGS
import kotlin.experimental.and
import kotlin.jvm.JvmInline
import kotlin.math.pow

/** Returns a minified version of this FlexBuffer as a JSON. */
public fun Reference.toJson(): String =
  ArrayReadWriteBuffer(1024).let {
    toJson(it)
    val data = it.data() // it.getString(0, it.writePosition)
    return data.decodeToString(0, it.writePosition)
  }

/**
 * Returns a minified version of this FlexBuffer as a JSON.
 *
 * @param out [ReadWriteBuffer] the JSON will be written.
 */
public fun Reference.toJson(out: ReadWriteBuffer) {
  when (type) {
    T_STRING -> {
      val start = buffer.indirect(end, parentWidth)
      val size = buffer.readULong(start - byteWidth, byteWidth).toInt()
      out.jsonEscape(buffer, start, size)
    }
    T_KEY -> {
      val start = buffer.indirect(end, parentWidth)
      val end = buffer.findFirst(0.toByte(), start)
      out.jsonEscape(buffer, start, end - start)
    }
    T_BLOB -> {
      val blob = toBlob()
      out.jsonEscape(out, blob.end, blob.size)
    }
    T_INT -> out.put(toLong().toString())
    T_UINT -> out.put(toULong().toString())
    T_FLOAT -> out.put(toDouble().toString())
    T_NULL -> out.put("null")
    T_BOOL -> out.put(toBoolean().toString())
    T_MAP -> toMap().toJson(out)
    T_VECTOR,
    T_VECTOR_BOOL,
    T_VECTOR_FLOAT,
    T_VECTOR_INT,
    T_VECTOR_UINT,
    T_VECTOR_KEY,
    T_VECTOR_STRING_DEPRECATED -> toVector().toJson(out)
    else -> error("Unable to convert type ${type.typeToString()} to JSON")
  }
}

/** Returns a minified version of this FlexBuffer as a JSON. */
public fun Map.toJson(): String =
  ArrayReadWriteBuffer(1024).let {
    toJson(it)
    it.toString()
  }

/**
 * Returns a minified version of this FlexBuffer as a JSON.
 *
 * @param out [ReadWriteBuffer] the JSON will be written.
 */
public fun Map.toJson(out: ReadWriteBuffer) {
  out.put('{'.code.toByte())
  // key values pairs
  for (i in 0 until size) {
    val key = keyAt(i)
    out.jsonEscape(buffer, key.start, key.sizeInBytes)
    out.put(':'.code.toByte())
    get(i).toJson(out)
    if (i != size - 1) {
      out.put(','.code.toByte())
    }
  }
  // close bracket
  out.put('}'.code.toByte())
}

/** Returns a minified version of this FlexBuffer as a JSON. */
public fun Vector.toJson(): String =
  ArrayReadWriteBuffer(1024).let {
    toJson(it)
    it.toString()
  }

/**
 * Returns a minified version of this FlexBuffer as a JSON.
 *
 * @param out that the JSON is being concatenated.
 */
public fun Vector.toJson(out: ReadWriteBuffer) {
  out.put('['.code.toByte())
  for (i in indices) {
    get(i).toJson(out)
    if (i != size - 1) {
      out.put(','.code.toByte())
    }
  }
  out.put(']'.code.toByte())
}

/**
 * JSONParser class is used to parse a JSON as FlexBuffers. Calling [JSONParser.parse] fiils
 * [output] and returns a [Reference] ready to be used.
 */
@ExperimentalUnsignedTypes
public class JSONParser(
  public var output: FlexBuffersBuilder = FlexBuffersBuilder(1024, SHARE_KEYS_AND_STRINGS)
) {
  private var readPos = 0
  private var scopes = ScopeStack()

  /** Parse a json as [String] and returns a [Reference] to a FlexBuffer. */
  public fun parse(data: String): Reference = parse(ArrayReadBuffer(data.encodeToByteArray()))

  /** Parse a json as [ByteArray] and returns a [Reference] to a FlexBuffer. */
  public fun parse(data: ByteArray): Reference = parse(ArrayReadBuffer(data))

  /** Parse a json as [ReadBuffer] and returns a [Reference] to a FlexBuffer. */
  public fun parse(data: ReadBuffer): Reference {
    reset()
    parseValue(data, nextToken(data), null)
    if (readPos < data.limit) {
      val tok = skipWhitespace(data)
      if (tok != CHAR_EOF) {
        makeError(data, "Extraneous charaters after parse has finished", tok)
      }
    }
    output.finish()
    return getRoot(output.buffer)
  }

  private fun parseValue(data: ReadBuffer, token: Token, key: String? = null): FlexBufferType {
    return when (token) {
      TOK_BEGIN_OBJECT -> parseObject(data, key)
      TOK_BEGIN_ARRAY -> parseArray(data, key)
      TOK_TRUE -> T_BOOL.also { output[key] = true }
      TOK_FALSE -> T_BOOL.also { output[key] = false }
      TOK_NULL -> T_NULL.also { output.putNull(key) }
      TOK_BEGIN_QUOTE -> parseString(data, key)
      TOK_NUMBER -> parseNumber(data, data.data(), key)
      else -> makeError(data, "Unexpected Character while parsing", 'x'.code.toByte())
    }
  }

  private fun parseObject(data: ReadBuffer, key: String? = null): FlexBufferType {
    this.scopes.push(SCOPE_OBJ_EMPTY)

    val fPos = output.startMap()
    val limit = data.limit
    while (readPos <= limit) {
      when (val tok = nextToken(data)) {
        TOK_END_OBJECT -> {
          this.scopes.pop()
          output.endMap(fPos, key)
          return T_MAP
        }
        TOK_BEGIN_QUOTE -> {
          val childKey = readString(data)
          parseValue(data, nextToken(data), childKey)
        }
        else -> makeError(data, "Expecting start of object key", tok)
      }
    }
    makeError(data, "Unable to parse the object", "x".toByte())
  }

  private fun parseArray(data: ReadBuffer, key: String? = null): FlexBufferType {
    this.scopes.push(SCOPE_ARRAY_EMPTY)
    val fPos = output.startVector()
    var elementType = T_INVALID
    var multiType = false
    val limit = data.limit

    while (readPos <= limit) {
      when (val tok = nextToken(data)) {
        TOK_END_ARRAY -> {
          this.scopes.pop()
          return if (!multiType && elementType.isScalar()) {
            output.endTypedVector(fPos, key)
            elementType.toElementTypedVector()
          } else {
            output.endVector(key, fPos)
            T_VECTOR
          }
        }

        else -> {
          val newType = parseValue(data, tok, null)

          if (elementType == T_INVALID) {
            elementType = newType
          } else if (newType != elementType) {
            multiType = true
          }
        }
      }
    }
    makeError(data, "Unable to parse the array")
  }

  private fun parseNumber(data: ReadBuffer, array: ByteArray, key: String?): FlexBufferType {
    val ary = array
    var cursor = readPos
    var c = data[readPos++]
    var useDouble = false
    val limit = ary.size
    var sign = 1
    var double: Double
    var long = 0L
    var digits = 0

    if (c == CHAR_MINUS) {
      cursor++
      checkEOF(data, cursor)
      c = ary[cursor]
      sign = -1
    }

    // peek first byte
    when (c) {
      CHAR_0 -> {
        cursor++
        if (cursor != limit) {
          c = ary[cursor]
        }
      }
      !in CHAR_0..CHAR_9 -> makeError(data, "Invalid Number", c)
      else -> {
        do {
          val digit = c - CHAR_0
          // double = 10.0 * double + digit
          long = 10 * long + digit
          digits++
          cursor++
          if (cursor == limit) break
          c = ary[cursor]
        } while (c in CHAR_0..CHAR_9)
      }
    }

    var exponent = 0
    // If we find '.' we need to convert to double
    if (c == CHAR_DOT) {
      useDouble = true
      checkEOF(data, cursor)
      c = ary[++cursor]
      if (c < CHAR_0 || c > CHAR_9) {
        makeError(data, "Invalid Number", c)
      }
      do {
        // double = double * 10 + (tok - CHAR_0)
        long = 10 * long + (c - CHAR_0)
        digits++
        --exponent
        cursor++
        if (cursor == limit) break
        c = ary[cursor]
      } while (c in CHAR_0..CHAR_9)
    }

    // If we find 'e' we need to convert to double
    if (c == CHAR_e || c == CHAR_E) {
      useDouble = true
      ++cursor
      checkEOF(data, cursor)
      c = ary[cursor]
      var negativeExponent = false
      if (c == CHAR_MINUS) {
        ++cursor
        checkEOF(data, cursor)
        negativeExponent = true
        c = ary[cursor]
      } else if (c == CHAR_PLUS) {
        ++cursor
        checkEOF(data, cursor)
        c = ary[cursor]
      }
      if (c < CHAR_0 || c > CHAR_9) {
        makeError(data, "Missing exponent", c)
      }
      var exp = 0
      do {
        val digit = c - CHAR_0
        exp = 10 * exp + digit
        ++cursor
        if (cursor == limit) break
        c = ary[cursor]
      } while (c in CHAR_0..CHAR_9)

      exponent += if (negativeExponent) -exp else exp
    }

    if (digits > 17 || exponent < -19 || exponent > 19) {
      // if the float number is not simple enough
      // we use language's Double parsing, which is slower but
      // produce more expected results for extreme numbers.
      val firstPos = readPos - 1
      val str = data.getString(firstPos, cursor - firstPos)
      if (useDouble) {
        double = str.toDouble()
        output[key] = double
      } else {
        long = str.toLong()
        output[key] = long
      }
    } else {
      // this happens on single numbers outside any object
      // or array
      if (useDouble || exponent != 0) {
        double = if (long == 0L) 0.0 else long.toDouble() * 10.0.pow(exponent)
        double *= sign
        output[key] = double
      } else {
        long *= sign
        output[key] = long
      }
    }
    readPos = cursor
    return if (useDouble) T_FLOAT else T_INT
  }

  private fun parseString(data: ReadBuffer, key: String?): FlexBufferType {
    output[key] = readString(data)
    return T_STRING
  }

  private fun readString(data: ReadBuffer): String {
    val limit = data.limit
    if (data is ArrayReadBuffer) {
      val ary = data.data()
      // enables range check elimination
      return readString(data, limit) { ary[it] }
    }
    return readString(data, limit) { data[it] }
  }

  private inline fun readString(
    data: ReadBuffer,
    limit: Int,
    crossinline fetch: (Int) -> Byte,
  ): String {
    var cursorPos = readPos
    var foundEscape = false
    var currentChar: Byte = 0
    // we loop over every 4 bytes until find any non-plain char
    while (limit - cursorPos >= 4) {
      currentChar = fetch(cursorPos)
      if (!isPlainStringChar(currentChar)) {
        foundEscape = true
        break
      }
      currentChar = fetch(cursorPos + 1)
      if (!isPlainStringChar(currentChar)) {
        cursorPos += 1
        foundEscape = true
        break
      }
      currentChar = fetch(cursorPos + 2)
      if (!isPlainStringChar(currentChar)) {
        cursorPos += 2
        foundEscape = true
        break
      }
      currentChar = fetch(cursorPos + 3)
      if (!isPlainStringChar(currentChar)) {
        cursorPos += 3
        foundEscape = true
        break
      }
      cursorPos += 4
    }
    if (!foundEscape) {
      // if non-plain string char is not found we loop over
      // the remaining bytes
      while (true) {
        if (cursorPos >= limit) {
          error("Unexpected end of string")
        }
        currentChar = fetch(cursorPos)
        if (!isPlainStringChar(currentChar)) {
          break
        }
        ++cursorPos
      }
    }
    if (currentChar == CHAR_DOUBLE_QUOTE) {
      val str = data.getString(readPos, cursorPos - readPos)
      readPos = cursorPos + 1
      return str
    }
    if (currentChar in 0..0x1f) {
      error("Illegal Codepoint")
    } else {
      // backslash or >0x7f
      return readStringSlow(data, currentChar, cursorPos)
    }
  }

  private fun readStringSlow(data: ReadBuffer, first: Byte, lastPos: Int): String {
    var cursorPos = lastPos

    var endOfString = lastPos
    while (true) {
      val pos = data.findFirst(CHAR_DOUBLE_QUOTE, endOfString)
      when {
        pos == -1 -> makeError(data, "Unexpected EOF, missing end of string '\"'", first)
        data[pos - 1] == CHAR_BACKSLASH && data[pos - 2] != CHAR_BACKSLASH -> {
          // here we are checking for double quotes preceded by backslash. eg \"
          // we have to look past pos -2 to make sure that the backlash is not
          // part of a previous escape, eg "\\"
          endOfString = pos + 1
        }
        else -> {
          endOfString = pos
          break
        }
      }
    }
    // copy everything before the escape
    val builder = StringBuilder(data.getString(readPos, lastPos - readPos))
    while (true) {
      when (val pos = data.findFirst(CHAR_BACKSLASH, cursorPos, endOfString)) {
        -1 -> {
          val doubleQuotePos = data.findFirst(CHAR_DOUBLE_QUOTE, cursorPos)
          if (doubleQuotePos == -1) makeError(data, "Reached EOF before enclosing string", first)
          val rest = data.getString(cursorPos, doubleQuotePos - cursorPos)
          builder.append(rest)
          readPos = doubleQuotePos + 1
          return builder.toString()
        }

        else -> {
          // we write everything up to \
          builder.append(data.getString(cursorPos, pos - cursorPos))
          val c = data[pos + 1]
          builder.append(readEscapedChar(data, c, pos))
          cursorPos = pos + if (c == CHAR_u) 6 else 2
        }
      }
    }
  }

  private inline fun isPlainStringChar(c: Byte): Boolean {
    val flags = parseFlags
    // return c in 0x20..0x7f && c != 0x22.toByte() && c != 0x5c.toByte()
    return (flags[c.toInt() and 0xFF] and 1) != 0.toByte()
  }

  private inline fun isWhitespace(c: Byte): Boolean {
    val flags = parseFlags
    // return c == '\r'.toByte() || c == '\n'.toByte() || c == '\t'.toByte() || c == ' '.toByte()
    return (flags[c.toInt() and 0xFF] and 2) != 0.toByte()
  }

  private fun reset() {
    readPos = 0
    output.clear()
    scopes.reset()
  }

  private fun nextToken(data: ReadBuffer): Token {
    val scope = this.scopes.last

    when (scope) {
      SCOPE_ARRAY_EMPTY -> this.scopes.last = SCOPE_ARRAY_FILLED
      SCOPE_ARRAY_FILLED -> {
        when (val c = skipWhitespace(data)) {
          CHAR_CLOSE_ARRAY -> return TOK_END_ARRAY
          CHAR_COMMA -> Unit
          else -> makeError(data, "Unfinished Array", c)
        }
      }
      SCOPE_OBJ_EMPTY,
      SCOPE_OBJ_FILLED -> {
        this.scopes.last = SCOPE_OBJ_KEY
        // Look for a comma before the next element.
        if (scope == SCOPE_OBJ_FILLED) {
          when (val c = skipWhitespace(data)) {
            CHAR_CLOSE_OBJECT -> return TOK_END_OBJECT
            CHAR_COMMA -> Unit
            else -> makeError(data, "Unfinished Object", c)
          }
        }
        return when (val c = skipWhitespace(data)) {
          CHAR_DOUBLE_QUOTE -> TOK_BEGIN_QUOTE
          CHAR_CLOSE_OBJECT ->
            if (scope != SCOPE_OBJ_FILLED) {
              TOK_END_OBJECT
            } else {
              makeError(data, "Expected Key", c)
            }
          else -> {
            makeError(data, "Expected Key/Value", c)
          }
        }
      }
      SCOPE_OBJ_KEY -> {
        this.scopes.last = SCOPE_OBJ_FILLED
        when (val c = skipWhitespace(data)) {
          CHAR_COLON -> Unit
          else -> makeError(data, "Expect ${CHAR_COLON.print()}", c)
        }
      }
      SCOPE_DOC_EMPTY -> this.scopes.last = SCOPE_DOC_FILLED
      SCOPE_DOC_FILLED -> {
        val c = skipWhitespace(data)
        if (c != CHAR_EOF) makeError(data, "Root object already finished", c)
        return TOK_EOF
      }
    }

    val c = skipWhitespace(data)
    when (c) {
      CHAR_CLOSE_ARRAY -> if (scope == SCOPE_ARRAY_EMPTY) return TOK_END_ARRAY
      CHAR_COLON -> makeError(data, "Unexpected character", c)
      CHAR_DOUBLE_QUOTE -> return TOK_BEGIN_QUOTE
      CHAR_OPEN_ARRAY -> return TOK_BEGIN_ARRAY
      CHAR_OPEN_OBJECT -> return TOK_BEGIN_OBJECT
      CHAR_t -> {
        checkEOF(data, readPos + 2)
        // 0x65757274 is equivalent to ['t', 'r', 'u', 'e' ] as a 4 byte Int
        if (data.getInt(readPos - 1) != 0x65757274) {
          makeError(data, "Expecting keyword \"true\"", c)
        }
        readPos += 3
        return TOK_TRUE
      }
      CHAR_n -> {
        checkEOF(data, readPos + 2)
        // 0x6c6c756e  is equivalent to ['n', 'u', 'l', 'l' ] as a 4 byte Int
        if (data.getInt(readPos - 1) != 0x6c6c756e) {
          makeError(data, "Expecting keyword \"null\"", c)
        }
        readPos += 3
        return TOK_NULL
      }
      CHAR_f -> {
        checkEOF(data, readPos + 3)
        // 0x65736c61 is equivalent to ['a', 'l', 's', 'e' ] as a 4 byte Int
        if (data.getInt(readPos) != 0x65736c61) {
          makeError(data, "Expecting keyword \"false\"", c)
        }
        readPos += 4
        return TOK_FALSE
      }
      CHAR_0,
      CHAR_1,
      CHAR_2,
      CHAR_3,
      CHAR_4,
      CHAR_5,
      CHAR_6,
      CHAR_7,
      CHAR_8,
      CHAR_9,
      CHAR_MINUS ->
        return TOK_NUMBER.also {
          readPos-- // rewind one position so we don't lose first digit
        }
    }
    makeError(data, "Expecting element", c)
  }

  // keeps increasing [readPos] until finds a non-whitespace byte
  private inline fun skipWhitespace(data: ReadBuffer): Byte {
    val limit = data.limit
    if (data is ArrayReadBuffer) {
      // enables range check elimination
      val ary = data.data()
      return skipWhitespace(limit) { ary[it] }
    }
    return skipWhitespace(limit) { data[it] }
  }

  private inline fun skipWhitespace(limit: Int, crossinline fetch: (Int) -> Byte): Byte {
    var pos = readPos
    while (pos < limit) {
      val d = fetch(pos++)
      if (!isWhitespace(d)) {
        readPos = pos
        return d
      }
    }
    readPos = limit
    return CHAR_EOF
  }

  // byte1 is expected to be first char before `\`
  private fun readEscapedChar(data: ReadBuffer, byte1: Byte, cursorPos: Int): Char {
    return when (byte1) {
      CHAR_u -> {
        checkEOF(data, cursorPos + 1 + 4)
        var result: Char = 0.toChar()
        var i = cursorPos + 2 // cursorPos is on '\\', cursorPos + 1 is 'u'
        val end = i + 4
        while (i < end) {
          val part: Byte = data[i]
          result = (result.code shl 4).toChar()
          result +=
            when (part) {
              in CHAR_0..CHAR_9 -> part - CHAR_0
              in CHAR_a..CHAR_f -> part - CHAR_a + 10
              in CHAR_A..CHAR_F -> part - CHAR_A + 10
              else -> makeError(data, "Invalid utf8 escaped character", -1)
            }
          i++
        }
        result
      }
      CHAR_b -> '\b'
      CHAR_t -> '\t'
      CHAR_r -> '\r'
      CHAR_n -> '\n'
      CHAR_f -> 12.toChar() // '\f'
      CHAR_DOUBLE_QUOTE,
      CHAR_BACKSLASH,
      CHAR_FORWARDSLASH -> byte1.toInt().toChar()
      else -> makeError(data, "Invalid escape sequence.", byte1)
    }
  }

  private fun Byte.print(): String =
    when (this) {
      in 0x21..0x7E -> "'${this.toInt().toChar()}'" // visible ascii chars
      CHAR_EOF -> "EOF"
      else -> "'0x${this.toString(16)}'"
    }

  private inline fun makeError(data: ReadBuffer, msg: String, tok: Byte? = null): Nothing {
    val (line, column) = calculateErrorPosition(data, readPos)
    if (tok != null) {
      error("Error At ($line, $column): $msg, got ${tok.print()}")
    } else {
      error("Error At ($line, $column): $msg")
    }
  }

  private inline fun makeError(data: ReadBuffer, msg: String, tok: Token): Nothing {
    val (line, column) = calculateErrorPosition(data, readPos)
    error("Error At ($line, $column): $msg, got ${tok.print()}")
  }

  private inline fun checkEOF(data: ReadBuffer, pos: Int) {
    if (pos >= data.limit) makeError(data, "Unexpected end of file", -1)
  }

  private fun calculateErrorPosition(data: ReadBuffer, endPos: Int): Pair<Int, Int> {
    var line = 1
    var column = 1
    var current = 0
    while (current < endPos - 1) {
      if (data[current++] == CHAR_NEWLINE) {
        ++line
        column = 1
      } else {
        ++column
      }
    }
    return Pair(line, column)
  }
}

internal inline fun Int.toPaddedHex(): String = "\\u${this.toString(16).padStart(4, '0')}"

private inline fun ReadWriteBuffer.jsonEscape(data: ReadBuffer, start: Int, size: Int) {
  val replacements = JSON_ESCAPE_CHARS
  put(CHAR_DOUBLE_QUOTE)
  var last = start
  val length: Int = size
  val ary = data.data()
  for (i in start until start + length) {
    val c = ary[i].toUByte()
    var replacement: ByteArray?
    if (c.toInt() < 128) {
      replacement = replacements[c.toInt()]
      if (replacement == null) {
        continue
      }
    } else {
      continue
    }
    if (last < i) {
      put(ary, last, i - last)
    }
    put(replacement, 0, replacement.size)
    last = i + 1
  }
  if (last < (last + length)) {
    put(ary, last, (start + length) - last)
  }
  put(CHAR_DOUBLE_QUOTE)
}

// Following escape strategy defined in RFC7159.
private val JSON_ESCAPE_CHARS: Array<ByteArray?> =
  arrayOfNulls<ByteArray>(128).apply {
    this['\n'.code] = "\\n".encodeToByteArray()
    this['\t'.code] = "\\t".encodeToByteArray()
    this['\r'.code] = "\\r".encodeToByteArray()
    this['\b'.code] = "\\b".encodeToByteArray()
    this[0x0c] = "\\f".encodeToByteArray()
    this['"'.code] = "\\\"".encodeToByteArray()
    this['\\'.code] = "\\\\".encodeToByteArray()
    for (i in 0..0x1f) {
      this[i] = "\\u${i.toPaddedHex()}".encodeToByteArray()
    }
  }

// Scope is used to the define current space that the scanner is operating.
@JvmInline private value class Scope(val id: Int)

private val SCOPE_DOC_EMPTY = Scope(0)
private val SCOPE_DOC_FILLED = Scope(1)
private val SCOPE_OBJ_EMPTY = Scope(2)
private val SCOPE_OBJ_KEY = Scope(3)
private val SCOPE_OBJ_FILLED = Scope(4)
private val SCOPE_ARRAY_EMPTY = Scope(5)
private val SCOPE_ARRAY_FILLED = Scope(6)

// Keeps the stack state of the scopes being scanned. Currently defined to have a
// max stack size of 22, as per tests cases defined in http://json.org/JSON_checker/
private class ScopeStack(
  private val ary: IntArray = IntArray(22) { SCOPE_DOC_EMPTY.id },
  var lastPos: Int = 0,
) {
  var last: Scope
    get() = Scope(ary[lastPos])
    set(x) {
      ary[lastPos] = x.id
    }

  fun reset() {
    lastPos = 0
    ary[0] = SCOPE_DOC_EMPTY.id
  }

  fun pop(): Scope {
    // println("Popping: ${last.print()}")
    return Scope(ary[lastPos--])
  }

  fun push(scope: Scope): Scope {
    if (lastPos == ary.size - 1)
      error("Too much nesting reached. Max nesting is ${ary.size} levels")
    // println("PUSHING : ${scope.print()}")
    ary[++lastPos] = scope.id
    return scope
  }
}

@JvmInline
private value class Token(val id: Int) {
  fun print(): String =
    when (this) {
      TOK_EOF -> "TOK_EOF"
      TOK_NONE -> "TOK_NONE"
      TOK_BEGIN_OBJECT -> "TOK_BEGIN_OBJECT"
      TOK_END_OBJECT -> "TOK_END_OBJECT"
      TOK_BEGIN_ARRAY -> "TOK_BEGIN_ARRAY"
      TOK_END_ARRAY -> "TOK_END_ARRAY"
      TOK_NUMBER -> "TOK_NUMBER"
      TOK_TRUE -> "TOK_TRUE"
      TOK_FALSE -> "TOK_FALSE"
      TOK_NULL -> "TOK_NULL"
      TOK_BEGIN_QUOTE -> "TOK_BEGIN_QUOTE"
      else -> this.toString()
    }
}

private val TOK_EOF = Token(-1)
private val TOK_NONE = Token(0)
private val TOK_BEGIN_OBJECT = Token(1)
private val TOK_END_OBJECT = Token(2)
private val TOK_BEGIN_ARRAY = Token(3)
private val TOK_END_ARRAY = Token(4)
private val TOK_NUMBER = Token(5)
private val TOK_TRUE = Token(6)
private val TOK_FALSE = Token(7)
private val TOK_NULL = Token(8)
private val TOK_BEGIN_QUOTE = Token(9)

private const val CHAR_NEWLINE = '\n'.code.toByte()
private const val CHAR_OPEN_OBJECT = '{'.code.toByte()
private const val CHAR_COLON = ':'.code.toByte()
private const val CHAR_CLOSE_OBJECT = '}'.code.toByte()
private const val CHAR_OPEN_ARRAY = '['.code.toByte()
private const val CHAR_CLOSE_ARRAY = ']'.code.toByte()
private const val CHAR_DOUBLE_QUOTE = '"'.code.toByte()
private const val CHAR_BACKSLASH = '\\'.code.toByte()
private const val CHAR_FORWARDSLASH = '/'.code.toByte()
private const val CHAR_f = 'f'.code.toByte()
private const val CHAR_a = 'a'.code.toByte()
private const val CHAR_r = 'r'.code.toByte()
private const val CHAR_t = 't'.code.toByte()
private const val CHAR_n = 'n'.code.toByte()
private const val CHAR_b = 'b'.code.toByte()
private const val CHAR_e = 'e'.code.toByte()
private const val CHAR_E = 'E'.code.toByte()
private const val CHAR_u = 'u'.code.toByte()
private const val CHAR_A = 'A'.code.toByte()
private const val CHAR_F = 'F'.code.toByte()
private const val CHAR_EOF = (-1).toByte()
private const val CHAR_COMMA = ','.code.toByte()
private const val CHAR_0 = '0'.code.toByte()
private const val CHAR_1 = '1'.code.toByte()
private const val CHAR_2 = '2'.code.toByte()
private const val CHAR_3 = '3'.code.toByte()
private const val CHAR_4 = '4'.code.toByte()
private const val CHAR_5 = '5'.code.toByte()
private const val CHAR_6 = '6'.code.toByte()
private const val CHAR_7 = '7'.code.toByte()
private const val CHAR_8 = '8'.code.toByte()
private const val CHAR_9 = '9'.code.toByte()
private const val CHAR_MINUS = '-'.code.toByte()
private const val CHAR_PLUS = '+'.code.toByte()
private const val CHAR_DOT = '.'.code.toByte()

// This template utilizes the One Definition Rule to create global arrays in a
// header. As seen in:
// https://github.com/chadaustin/sajson/blob/master/include/sajson.h
// bit 0 (1) - set if: plain ASCII string character
// bit 1 (2) - set if: whitespace
// bit 4 (0x10) - set if: 0-9 e E .
private val parseFlags =
  byteArrayOf(
    // 0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    2,
    2,
    0,
    0,
    2,
    0,
    0, // 0
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0, // 1
    3,
    1,
    0,
    1,
    1,
    1,
    1,
    1,
    1,
    1,
    1,
    1,
    1,
    1,
    0x11,
    1, // 2
    0x11,
    0x11,
    0x11,
    0x11,
    0x11,
    0x11,
    0x11,
    0x11,
    0x11,
    0x11,
    1,
    1,
    1,
    1,
    1,
    1, // 3
    1,
    1,
    1,
    1,
    1,
    0x11,
    1,
    1,
    1,
    1,
    1,
    1,
    1,
    1,
    1,
    1, // 4
    1,
    1,
    1,
    1,
    1,
    1,
    1,
    1,
    1,
    1,
    1,
    1,
    0,
    1,
    1,
    1, // 5
    1,
    1,
    1,
    1,
    1,
    0x11,
    1,
    1,
    1,
    1,
    1,
    1,
    1,
    1,
    1,
    1, // 6
    1,
    1,
    1,
    1,
    1,
    1,
    1,
    1,
    1,
    1,
    1,
    1,
    1,
    1,
    1,
    1, // 7

    // 128-255
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
  )
