discard """
"""

import macros, strUtils

proc symToIdent(x: NimNode): NimNode =
  case x.kind:
    of nnkCharLit..nnkUInt64Lit:
      result = newNimNode(x.kind)
      result.intVal = x.intVal
    of nnkFloatLit..nnkFloat64Lit:
      result = newNimNode(x.kind)
      result.floatVal = x.floatVal
    of nnkStrLit..nnkTripleStrLit:
      result = newNimNode(x.kind)
      result.strVal = x.strVal
    of nnkIdent, nnkSym:
      result = newIdentNode($x)
    else:
      result = newNimNode(x.kind)
      for c in x:
        result.add symToIdent(c)

# check getTypeInst and getTypeImpl for given symbol x
macro testX(x,inst0: typed; recurse: static[bool]; implX: typed) =
  # check that getTypeInst(x) equals inst0
  let inst = x.getTypeInst
  let instr = inst.symToIdent.treeRepr
  let inst0r = inst0.symToIdent.treeRepr
  if instr != inst0r:
    echo "instr:\n", instr
    echo "inst0r:\n", inst0r
    doAssert(instr == inst0r)

  # check that getTypeImpl(x) is correct
  #  if implX is nil then compare to inst0
  #  else we expect implX to be a type definition
  #       and we extract the implementation from that
  let impl = x.getTypeImpl
  var impl0 =
    if implX.kind == nnkNilLit: inst0
    else: implX[0][2]
  let implr = impl.symToIdent.treerepr
  let impl0r = impl0.symToIdent.treerepr
  if implr != impl0r:
    echo "implr:\n", implr
    echo "impl0r:\n", impl0r
    doAssert(implr == impl0r)

  result = newStmtList()
  #template echoString(s: string) = echo s.replace("\n","\n  ")
  #result.add getAst(echoString("  " & inst0.repr))
  #result.add getAst(echoString("  " & inst.repr))
  #result.add getAst(echoString("  " & impl0.repr))
  #result.add getAst(echoString("  " & impl.repr))

  if recurse:
    # now test using a newly formed variable of type getTypeInst(x)
    template testDecl(n,m: typed) =
      testV(n, false):
        type _ = m
    result.add getAst(testDecl(inst.symToIdent, impl.symToIdent))

# test with a variable (instance) of type
template testV(inst, recurse, impl) =
  block:
    #echo "testV(" & astToStr(inst) & ", " & $recurse & "):" & astToStr(impl)
    var x: inst
    testX(x, inst, recurse, impl)

# test with a newly created typedesc (myType)
# using the passed type as the implementation
template testT(impl, recurse) =
  block:
    type myType = impl
    testV(myType, recurse):
      type _ = impl

# test a built-in type whose instance is equal to the implementation
template test(inst) =
  testT(inst, false)
  testV(inst, true, nil)

# test a custom type with provided implementation
template test(inst, impl) =
  testV(inst, true, impl)

type
  Model = object of RootObj
  User = object of Model
    name : string
    password : string

  Tree = object of RootObj
    value : int
    left,right : ref Tree

  MyEnum = enum
    valueA, valueB, valueC

  MySet = set[MyEnum]
  MySeq = seq[int]
  MyIntPtr = ptr int
  MyIntRef = ref int

  GenericObject[T] = object
    value:T
  Foo[N:static[int],T] = object
  Bar[N:static[int],T] = object
    #baz:Foo[N+1,GenericObject[T]]  # currently fails
    baz:Foo[N,GenericObject[T]]

  Generic[T] = seq[int]
  Concrete = Generic[int]

  Generic2[T1, T2] = seq[T1]
  Concrete2 = Generic2[int, float]

  Alias1 = float
  Alias2 = Concrete
  Alias3 = Concrete2

  Vec[N: static[int],T] = object
    arr: array[N,T]
  Vec4[T] = Vec[4,T]


test(bool)
test(char)
test(int)
test(float)
test(ptr int)
test(ref int)
test(array[1..10,Bar[2,Foo[3,float]]])
test(array[MyEnum,Bar[2,Foo[3,float]]])
test(distinct Bar[2,Foo[3,float]])
test(tuple[a:int,b:Foo[-1,float]])
test(seq[int])
test(set[MyEnum])
test(proc (a: int, b: Foo[2,float]))
test(proc (a: int, b: Foo[2,float]): Bar[3,int])

test(MyEnum):
  type _ = enum
    valueA, valueB, valueC
test(Bar[2,Foo[3,float]]):
  type _ = object
    baz: Foo[2, GenericObject[Foo[3, float]]]
test(Model):
  type _ = object of RootObj
test(User):
  type _ = object of Model
    name: string
    password: string
test(Tree):
  type _ = object of RootObj
    value: int
    left: ref Tree
    right: ref Tree
test(Concrete):
  type _ = seq[int]
test(Generic[int]):
  type _ = seq[int]
test(Generic[float]):
  type _ = seq[int]
test(Concrete2):
  type _ = seq[int]
test(Generic2[int,float]):
  type _ = seq[int]
test(Alias1):
  type _ = float
test(Alias2):
  type _ = seq[int]
test(Alias3):
  type _ = seq[int]
test(Vec[4,float32]):
  type _ = object
    arr: array[0..3,float32]
test(Vec4[float32]):
  type _ = object
    arr: array[0..3,float32]

# bug #4862
static:
  discard typedesc[(int, int)].getTypeImpl

# custom pragmas
template myAttr() {.pragma.}
template myAttr2() {.pragma.}
template myAttr3() {.pragma.}
template serializationKey(key: string) {.pragma.}

type
  MyObj = object {.packed,myAttr,serializationKey: "one".}
    myField {.myAttr2,serializationKey: "two".}: int
    myField2 {.myAttr3,serializationKey: "three".}: float

# field pragmas not currently supported
test(MyObj):
  type
    _ = object {.packed,myAttr,serializationKey: "one".}
      myField: int
      myField2: float

block t9600:
  type
    Apple = ref object of RootObj

  macro mixer(x: typed): untyped =
    let w = getType(x)
    let v = getTypeImpl(w[1])

  var z: Apple
  mixer(z)
