#
#
#           The Nim Compiler
#        (c) Copyright 2012 Andreas Rumpf
#
#    See the file "copying.txt", included in this
#    distribution, for details about the copyright.
#

## Serialization utilities for the compiler.
import std/[strutils, math]

when defined(nimPreviewSlimSystem):
  import std/assertions

# bcc on windows doesn't have C99 functions
when defined(windows) and defined(bcc):
  {.emit: """#if defined(_MSC_VER) && _MSC_VER < 1900
  #include <stdarg.h>
  static int c99_vsnprintf(char *outBuf, size_t size, const char *format, va_list ap) {
    int count = -1;
    if (size != 0) count = _vsnprintf_s(outBuf, size, _TRUNCATE, format, ap);
    if (count == -1) count = _vscprintf(format, ap);
    return count;
  }
  int snprintf(char *outBuf, size_t size, const char *format, ...) {
    int count;
    va_list ap;
    va_start(ap, format);
    count = c99_vsnprintf(outBuf, size, format, ap);
    va_end(ap);
    return count;
  }
  #endif
  """.}

proc c_snprintf(s: cstring; n: uint; frmt: cstring): cint {.importc: "snprintf", header: "<stdio.h>", nodecl, varargs.}


when not declared(signbit):
  proc c_signbit(x: SomeFloat): cint {.importc: "signbit", header: "<math.h>".}
  proc signbit*(x: SomeFloat): bool {.inline.} =
    result = c_signbit(x) != 0

import std/formatfloat

proc toStrMaxPrecision*(f: BiggestFloat | float32): string =
  const literalPostfix = when f is float32: "f" else: ""
  case classify(f)
  of fcNan:
    if signbit(f):
      result = "-NAN"
    else:
      result = "NAN"
  of fcNegZero:
    result = "-0.0" & literalPostfix
  of fcZero:
    result = "0.0" & literalPostfix
  of fcInf:
    result = "INF"
  of fcNegInf:
    result = "-INF"
  else:
    result = ""
    result.addFloatRoundtrip(f)
    result.add literalPostfix

proc encodeStr*(s: string, result: var string) =
  for i in 0..<s.len:
    case s[i]
    of 'a'..'z', 'A'..'Z', '0'..'9', '_': result.add(s[i])
    else: result.add('\\' & toHex(ord(s[i]), 2))

proc hexChar(c: char, xi: var int) =
  case c
  of '0'..'9': xi = (xi shl 4) or (ord(c) - ord('0'))
  of 'a'..'f': xi = (xi shl 4) or (ord(c) - ord('a') + 10)
  of 'A'..'F': xi = (xi shl 4) or (ord(c) - ord('A') + 10)
  else: discard

proc decodeStr*(s: cstring, pos: var int): string =
  var i = pos
  result = ""
  while true:
    case s[i]
    of '\\':
      inc(i, 3)
      var xi = 0
      hexChar(s[i-2], xi)
      hexChar(s[i-1], xi)
      result.add(chr(xi))
    of 'a'..'z', 'A'..'Z', '0'..'9', '_':
      result.add(s[i])
      inc(i)
    else: break
  pos = i

const chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

{.push overflowChecks: off.}

# since negative numbers require a leading '-' they use up 1 byte. Thus we
# subtract/add `vintDelta` here to save space for small negative numbers
# which are common in ROD files:
const vintDelta = 5

template encodeIntImpl(self) =
  var d: char
  var v = x
  var rem = v mod 190
  if rem < 0:
    result.add('-')
    v = - (v div 190)
    rem = - rem
  else:
    v = v div 190
  var idx = int(rem)
  if idx < 62: d = chars[idx]
  else: d = chr(idx - 62 + 128)
  if v != 0: self(v, result)
  result.add(d)

proc encodeVBiggestIntAux(x: BiggestInt, result: var string) =
  ## encode a biggest int as a variable length base 190 int.
  encodeIntImpl(encodeVBiggestIntAux)

proc encodeVBiggestInt*(x: BiggestInt, result: var string) =
  ## encode a biggest int as a variable length base 190 int.
  encodeVBiggestIntAux(x +% vintDelta, result)
  #  encodeIntImpl(encodeVBiggestInt)

proc encodeVIntAux(x: int, result: var string) =
  ## encode an int as a variable length base 190 int.
  encodeIntImpl(encodeVIntAux)

proc encodeVInt*(x: int, result: var string) =
  ## encode an int as a variable length base 190 int.
  encodeVIntAux(x +% vintDelta, result)

template decodeIntImpl() =
  var i = pos
  var sign = - 1
  assert(s[i] in {'a'..'z', 'A'..'Z', '0'..'9', '-', '\x80'..'\xFF'})
  if s[i] == '-':
    inc(i)
    sign = 1
  result = 0
  while true:
    case s[i]
    of '0'..'9': result = result * 190 - (ord(s[i]) - ord('0'))
    of 'a'..'z': result = result * 190 - (ord(s[i]) - ord('a') + 10)
    of 'A'..'Z': result = result * 190 - (ord(s[i]) - ord('A') + 36)
    of '\x80'..'\xFF': result = result * 190 - (ord(s[i]) - 128 + 62)
    else: break
    inc(i)
  result = result * sign -% vintDelta
  pos = i

proc decodeVInt*(s: cstring, pos: var int): int =
  decodeIntImpl()

proc decodeVBiggestInt*(s: cstring, pos: var int): BiggestInt =
  decodeIntImpl()

{.pop.}

iterator decodeVIntArray*(s: cstring): int =
  var i = 0
  while s[i] != '\0':
    yield decodeVInt(s, i)
    if s[i] == ' ': inc i

iterator decodeStrArray*(s: cstring): string =
  var i = 0
  while s[i] != '\0':
    yield decodeStr(s, i)
    if s[i] == ' ': inc i
