include ../unreal/prelude
import ../codegen/[modelconstructor, ueemit, uebind, models, uemeta, umacros]
import std/[json, strformat, jsonutils, sequtils, options, sugar, enumerate, strutils, tables]
import ../vm/[runtimefield, uecall]
import ../unreal/nimforue/nimforuebindings
#Primero coger el parametro.
#Luego devolverlo en el out
  uprops(EditAnywhere, BlueprintReadWrite):
uClass UObjectPOC of UObject:
  (BlueprintType, Reinstance)
      UE_Log "Hola from UObjectPOC instanceFunc"
    proc instanceFuncWithOneArgAndReturnTest(arg : int) : FVector = FVector(x:arg.float32, y:arg.float32, z:arg.float32)
    proc callWithOutArg(res: var int) : int =
      UE_Log &"Inside callWithOutArg {res}"
    proc callWithOutHitArg(res: var FHitResult) =
    proc callFuncWithNoArg() =
      UE_Log "Hola from UObjectPOC"
    proc callFuncWithOneIntArg(arg : int) =
      UE_Log "Hola from UObjectPOC with arg: " & $arg
    proc callFuncWithOneStrArg(arg : FString) =
      UE_Log "Hola from UObjectPOC with arg: " & $arg
    proc callFuncWithTwoIntArg(arg1 : int, arg2 : int) =
      UE_Log "Hola from UObjectPOC with arg1: " & $arg1 & " and arg2: " & $arg2
    proc callFuncWithTwoStrArg(arg1 : FString, arg2 : FString) =
      UE_Log "Hola from UObjectPOC with arg1: " & $arg1 & " and arg2: " & $arg2
    proc callFuncWithInt32Int64Arg(arg1 : int32, arg2 : int64) =
      UE_Log "Hola from UObjectPOC with arg1: " & $arg1 & " and arg2: " & $arg2
    proc saluteWitthTwoDifferentIntSizes2(arg1 : int64, arg2 : int32) =
      UE_Log "Hola from UObjectPOC with arg1: " & $arg1 & " and arg2: " & $arg2
    proc callFuncWithOneObjPtrArg(obj:UObjectPtr) =
      UE_Log "Object name: " & $obj.getName()
    proc callFuncWithObjPtrStrArg(obj:UObjectPtr, salute : FString) =
      UE_Log "Object name: " & $obj.getName() & " Salute: " & $salute
    proc callFuncWithObjPtrArgReturnInt(obj:UObjectPtr) : int =
      UE_Log "Object name: " & $obj.getName()
      UE_Log "Object addr: " & $cast[int](obj)
    proc callFuncWithObjPtrArgReturnObjPtr(obj:UObjectPtr) : UObjectPtr =
      UE_Log "Object name: " & $obj.getName()
    proc callFuncWithObjPtrArgReturnStr(obj:UObjectPtr) : FString =
      let str = "Object name: " & $obj.getName()
    proc callFuncWithOneFVectorArg(vec : FVector) =
    proc callFuncWithOneArrayIntArg(ints : TArray[int]) =
      UE_Log "Int array length: " & $ints.len
    proc callFuncWithOneArrayVectorArg(vecs : TArray[FVector]) =
      UE_Log "Vector array length: " & $vecs.len
    proc callThatReturnsArrayInt() : TArray[int] = makeTArray(1, 2, 3, 4, 5)
    proc receiveFloat32(arg : float32) = #: float32 =
      UE_Log "Float32: " & $arg
    proc receiveFloat64(arg : float) =
      UE_Log "Float64: " & $arg
    proc receiveVectorAndFloat32(dir:FVector, scale:float32) =
      UE_Error "receiveVectorAndFloat32 " & $dir & " scale:" & $scale
    proc callFuncWithOneFVectorArgReturnFVector(vec : FVector) : FVector =
    proc callFuncWithOneFVectorArgReturnFRotator(vec : FVector) : FRotator =
      FRotator(pitch:vec.x, yaw:vec.y, roll:vec.z)
1. [x] Create a function that makes a call by fn name
2. [x] Create a function that makes a call by fn name and pass a value argument
  2.1 [x] Create a function that makes a call by fn name and pass a two values of the same types as argument
  2.2 [x] Create a function that makes a call by fn name and pass a two values of different types as argument
  2.3 [x] Pass a int32 and a int64
3. [x] Create a function that makes a call by fn name and pass a pointer argument
4. [x] Create a function that makes a call by fn name and pass a value and pointer argument
5. [x] Create a function that makes a call by fn name and pass a value and pointer argument and return a value
6. [x] Create a function that makes a call by fn name and pass a value and pointer argument and return a pointer
  6.1 [x] Create a function that makes a call by fn name and pass a value and pointer argument and returns a string
7. [ ] Repeat 1-6 where value arguments are complex types
8. [ ] Add support for missing basic types
# proc registerVmTests*() =
#   unregisterAllNimTests()
#     ueTest "should create a ":
#     ueTest "another create a test2":
#maybe the way to go is by raising. Let's do a test to see if we can catch the errors in the actual actor
#Later on this can be an uobject that pulls and the actor will just run them. But this is fine as started point
uClass ANimTestBase of AActor:
      self.printSucceed = false
     #Traverse all the tests and run them. A test is a function that starts with "test" or "should
          it.getName().tolower.startsWith("test") or
          it.getName().tolower.startsWith("should"))
          UE_Log "Running test: " & $fn.getName()
          self.processEvent(fn, nil)
          self.printSucceed = false
        except CatchableError as e:
          UE_Error "Error in test: " & $fn.getName() & " " & $e.msg
      self.printSucceed = false
uClass AActorPOCVMTest of ANimTestBase:
    proc testCallFuncWithNoArg() =
      let callData = UECall(kind: uecFunc, fn: makeUEFunc("callFuncWithNoArg", "UObjectPOC"))
    proc testCallWithOutArg() =
          fn: makeUEFunc("callWithOutArg", "UObjectPOC"),
          value: (res: 1).toRuntimeField()
      let res =  uCall(callData)
    proc testCallWithOutHitArg() =
          fn: makeUEFunc("callWithOutHitArg", "UObjectPOC"),
          value: (res: hit).toRuntimeField()
      let res =  uCall(callData)
    # discard callWithOutArg(1, test, 2)
    # UE_Log "the value afterwards is " & $test
    proc testCallFuncWithOneIntArg() =
          fn: makeUEFunc("callFuncWithOneIntArg", "UObjectPOC"),
          value: (arg: 10).toRuntimeField()
    proc testCallFuncWithOneStrArg() =
          fn: makeUEFunc("callFuncWithOneStrArg", "UObjectPOC"),
          value: (arg: "10 cadena").toRuntimeField()
    proc testCallFuncWithTwoStrArg() =
          fn: makeUEFunc("callFuncWithTwoStrArg", "UObjectPOC"),
          value: (arg1: "10 cadena", arg2: "Hola").toRuntimeField()
    proc testCallFuncWithTwoIntArg() =
          fn: makeUEFunc("callFuncWithTwoIntArg", "UObjectPOC"),
          value: (arg1: 10, arg2: 10).toRuntimeField()
    proc testCallFuncWithInt32Int64Arg() =
          fn: makeUEFunc("callFuncWithInt32Int64Arg", "UObjectPOC"),
          value: (arg1: 15, arg2: 10).toRuntimeField()
    proc testCallFuncWithOneObjPtrArg() =
          fn: makeUEFunc("callFuncWithOneObjPtrArg", "UObjectPOC"),
          value: (obj: cast[int](self)).toRuntimeField()
    proc testCallFuncWithObjPtrStrArg() =
          fn: makeUEFunc("callFuncWithObjPtrStrArg", "UObjectPOC"),
          value: (obj: cast[int](self), salute: "Hola").toRuntimeField()
    proc testCallFuncWithObjPtrArgReturnInt() =
          fn: makeUEFunc("callFuncWithObjPtrArgReturnInt", "UObjectPOC"),
          value: (obj: cast[int](self)).toRuntimeField()
    proc testCallFuncWithObjPtrArgReturnObjPtr() =
          fn: makeUEFunc("callFuncWithObjPtrArgReturnObjPtr", "UObjectPOC"),
          value: (obj: cast[int](self)).toRuntimeField()
      let objAddr = uCall(callData).get(RuntimeField(kind:Int)).getInt()
      UE_Log &"Returned object addr is {objAddr}"
      let obj = cast[UObjectPtr](objAddr)
    proc testCallFuncWithObjPtrArgReturnStr() =
          fn: makeUEFunc("callFuncWithObjPtrArgReturnStr", "UObjectPOC"),
          value: (obj: cast[int](self)).toRuntimeField()
      let str = uCall(callData).get(RuntimeField(kind:String)).getStr()
      UE_Log "Returned string is " & str
    proc testCallFuncWithOneFVectorArg() =
          fn: makeUEFunc("callFuncWithOneFVectorArg", "UObjectPOC"),
          value: (vec:FVector(x:12, y:10)).toRuntimeField()
    proc testCallFuncWithOneArrayIntArg() =
          fn: makeUEFunc("callFuncWithOneArrayIntArg", "UObjectPOC"),
          value: (ints:[2, 10]).toRuntimeField()
    proc testCallFuncWithOneArrayVectorArg() =
          fn: makeUEFunc("callFuncWithOneArrayVectorArg", "UObjectPOC"),
          value: (vecs:[FVector(x:12, y:10), FVector(x:12, z:1)]).toRuntimeField()
    proc testCallFuncWithOneFVectorArgReturnFVector() =
          fn: makeUEFunc("callFuncWithOneFVectorArgReturnFVector", "UObjectPOC"),
          value: (vec:FVector(x:12, y:10)).toRuntimeField()
      UE_Log  $uCall(callData).get.runtimeFieldTo(FVector)
    proc testCallFuncWithOneFVectorArgReturnFRotator() =
          fn: makeUEFunc("callFuncWithOneFVectorArgReturnFRotator", "UObjectPOC"),
          value: (vec:FVector(x:12, y:10)).toRuntimeField()
    proc testGetRightVector() =
          fn: makeUEFunc("GetRightVector", "UKismetMathLibrary"),
          value: (vec:FVector(x:12, y:10)).toRuntimeField()
    proc testCallThatReturnsArrayInt() =
          fn: makeUEFunc("callThatReturnsArrayInt", "UObjectPOC"),
          # value: ().toRuntimeField()
    proc testRuntimeFieldCanRetrieveAStructMemberByName() =
      let vector = FVector(x:10, y:10)
      let rtStruct = vector.toRuntimeField()
      let rtField = rtStruct["x"]
      let x = rtField.getFloat()
    proc shouldReceiveFloat32() =
          fn: makeUEFunc("receiveFloat32", "UObjectPOC"),
          value: (arg: 10.0).toRuntimeField()
      let val =  uCall(callData)#.jsonTo(float)
    proc shouldReceiveFloat64() =
          fn: makeUEFunc("receiveFloat64", "UObjectPOC"),
          value: (arg: 10.0).toRuntimeField()
macro ownerName(someSym: typed): string = newLit someSym.owner.strVal()
macro astRepr(exp: typed): string = newLit repr exp
# macro deconstructExp(exp: typed): string =
#TODO do a nice macro that splits the call and tells you what part is wrong
template check(exp: typed) =
    let astRepr {.inject.} = astRepr(exp)
    let res {.inject.} = $exp
    let fnName {.inject.} = ownerName(a)
      let msg = &"{[fnName]}Check failed {astRepr} is  {res}"
      when compiles(self.printSucceed):
          UE_Log &"{[fnName]} Check passed"
        UE_Log &"{[fnName]} Check passed"
# proc makeFromLoaded*(entry: FNameEntryId): FName {.importcpp:"FNameHelper::MakeFromLoaded(FNameEntrySerialized(#))".}
uClass AUECallPropReadTest of ANimTestBase:
    proc shouldBeAbleToReadAnInt32Prop() =
          clsName: self.getClass.getCppName(),
          value: (intProp: default(int32)).toRuntimeField()
      let reply = uCall(callData)
      let val = reply.get(RuntimeField(kind:Int)).getInt()
      check(val == self.intProp)
    proc shouldBeAbleToReadAFStringProp() =
          clsName: self.getClass.getCppName(),
          value: (stringProp: default(FString)).toRuntimeField()
      let reply = uCall(callData)
      let val = reply.get(RuntimeField(kind:String)).getStr()
      check val == self.stringProp
    proc shouldBeAbleToReadABoolProp() =
          clsName: self.getClass.getCppName(),
          value: (boolProp: default(bool)).toRuntimeField()
      let reply = uCall(callData)
      let val = reply.get(RuntimeField(kind:Bool)).getBool()
      check val == self.boolProp
    proc shouldBeAbleToReadAnArrayProp() =
      self.arrayProp = @[1, 2, 3, 4, 5].toTArray()
          clsName: self.getClass.getCppName(),
          value: (arrayProp: default(TArray[int])).toRuntimeField()
      let reply = uCall(callData)
      let val = reply.get(RuntimeField(kind:Array)).runtimeFieldTo(seq[int]).toTArray()
      check val == self.arrayProp
    proc shouldBeAbleToReadAStructProp() =
      self.structProp = FVector(x:10, y:10, z:10)
          clsName: self.getClass.getCppName(),
          value: (structProp: default(FVector)).toRuntimeField()
      let reply = uCall(callData)
      let val = reply.get(RuntimeField(kind:Struct)).runtimeFieldTo(FVector)
      check val.x == self.structProp.x
    proc shouldBeAbleToReadAnEnumProp() =
      self.enumProp = EEnumVMTest.ValueC
          clsName: self.getClass.getCppName(),
          value: (enumProp: default(EEnumVMTest)).toRuntimeField()
      let reply = uCall(callData)
      let val = reply.get(RuntimeField(kind:Int)).runtimeFieldTo(EEnumVMTest)
      check val == self.enumProp
    proc shouldBeAbleToReadANameProp() =
          clsName: self.getClass.getCppName(),
          value: (nameProp: default(FName)).toRuntimeField()
      let reply = uCall(callData)
      let val = reply.get(RuntimeField(kind:Int)).getInt()
      let name = nameFromInt(val)
      check name == self.nameProp
    proc shouldBeAbleToWriteAVectorProp() =
      self.vectorProp = FVector(x:10, y:10, z:10)
          clsName: self.getClass.getCppName(),
          value: (vectorProp: default(FVector)).toRuntimeField()
      let reply = uCall(callData)
      let val = reply.get(RuntimeField(kind:Struct)).runtimeFieldTo(FVector)
      check val.x == self.vectorProp.x
    proc shouldBeAbleToReadAHitProp() =
      self.hitProp = FHitResult()
      self.hitProp.bBlockingHit = true
      self.hitProp.distance = 100
          clsName: self.getClass.getCppName(),
          value: (hitProp: default(FHitResult)).toRuntimeField()
      let reply = uCall(callData)
      let val = reply.get.runtimeFieldTo(FHitResult)
      check val == self.hitProp
    proc shouldBeAbleToReadATextProp() =
      self.textProp = "hola".toText()
          clsName: self.getClass.getCppName(),
          value: (textProp: default(FText)).toRuntimeField()
      let reply = uCall(callData)
      let val = reply.get.runtimeFieldTo(FText)
      check val == self.textProp
    proc shouldBeAbleToReadASetProp() =
      self.setProp = makeTSet(2, 1, 5)
          clsName: self.getClass.getCppName(),
          value: (setProp: default(TSet[int])).toRuntimeField()
      let reply = uCall(callData)
      # let val = reply.get.runtimeFieldTo(TSet[int])
      # check val == self.setProp
  (kind: Struct, structVal: @[("faceIndex", (kind: Int, intVal: 0)), ("time", (kind: Float, floatVal: 5.26354424712089e-315)), ("distance", (kind: Float, floatVal: 5.535528570914047e-315)), ("location", (kind: Struct, structVal: @[])), ("impactPoint", (kind: Struct, structVal: @[])), ("normal", (kind: Struct, structVal: @[])), ("impactNormal", (kind: Struct, structVal: @[])), ("traceStart", (kind: Struct, structVal: @[])), ("traceEnd", (kind: Struct, structVal: @[])), ("penetrationDepth", (kind: Float, floatVal: 0.0)), ("myItem", (kind: Int
, intVal: 4294967295)), ("item", (kind: Int, intVal: 0)), ("elementIndex", (kind: Int, intVal: 0)), ("bBlockingHit", (kind: Bool, boolVal: true)), ("bStartPenetrating", (kind: Bool, boolVal: true)), ("physMaterial", (kind: Int, intVal: 0)), ("hitObjectHandle", (kind: Struct, structVal: @[("actor", (kind: Int, intVal: 0))])), ("component", (kind: Int, intVal: 0)), ("boneName", (kind: Int, intVal: 0)), ("myBoneName", (kind: Int, intVal: 0))])
 (kind: Struct, structVal: @[("faceIndex", (kind: Int, intVal: 0)), ("time", (kind: Float, floatVal: 1.0)), ("distance", (kind: Float, floatVal: 100.0)), ("normal", (kind: Struct, structVal: @[("x", (kind: Float, floatVal: 0.0)), ("y", (kind: Float, floatVal: 0.0)), ("z", (kind: Float, floatVal: 0.0))])), ("impactNormal", (kind: Struct, structVal: @[("x", (kind: Float, floatVal: 0.0)), ("y", (kind: Float, floatVal: 0.0)), ("z", (kind: Float, floatVal: 0.0))])), ("penetrationDepth", (kind: Float, floatVal: 0.0)), ("myItem", (kind: Int,
 intVal: -1)), ("item", (kind: Int, intVal: 0)), ("elementIndex", (kind: Int, intVal: 0)), ("bBlockingHit", (kind: Bool, boolVal: true)), ("bStartPenetrating", (kind: Bool, boolVal: false)), ("boneName", (kind: Int, intVal: 0)), ("myBoneName", (kind: Int, intVal: 0))])
(faceIndex: 0, time: 1.0, distance: 100.0, normal: (x: 0.0, y: 0.0, z: 0.0), impactNormal: (x: 0.0, y: 0.0, z: 0.0), penetrationDepth: 0.0, myItem: -1, item: 0, elementIndex: 0, bBlockingHit: true, bStartPenetrating: false, boneName: None, myBoneName: None)
uClass AUECallPropWriteTest of ANimTestBase:
    mapProp: TMap[int, FString]
    proc shouldBeAbleToWritteAnInt32Prop() =
          clsName: self.getClass.getCppName(),
          value: (intProp: expectedValue).toRuntimeField()
      check expectedValue == self.intProp
    proc shouldBeAbleToWriteAnArrayProp() =
      let expected = @[1, 2, 4].toTArray()
      self.arrayProp = @[0].toTArray()
          clsName: self.getClass.getCppName(),
          value: (arrayProp: expected).toRuntimeField()
      check expected == self.arrayProp
    proc shouldBeAbleToWriteAnEnumProp() =
      let expected = EEnumVMTest.ValueC
      self.enumProp = EEnumVMTest.ValueA
          clsName: self.getClass.getCppName(),
          value: (enumProp: expected).toRuntimeField()
      check expected == self.enumProp
    proc shoulsBeAbleToWriteAStructProp() =
        let expected = FVector(x:10, y:10, z:10)
        self.structProp = FVector(x:0, y:0, z:0)
            clsName: self.getClass.getCppName(),
            value: (structProp: expected).toRuntimeField()
        check expected.x == self.structProp.x
    proc shouldBeAbleToWriteANameProp() =
          clsName: "A" & self.getClass.getName(),
          value: (nameProp: expected).toRuntimeField()
      check expected == self.nameProp
    proc shouldBeAbleToWriteAHitProp() =
      let expected = FHitResult(bBlockingHit: true, distance: 100)
      self.hitProp = FHitResult()
          clsName: "A" & self.getClass.getName(),
          value: (hitProp: expected).toRuntimeField()
      UE_Error &"Distance is {self.hitprop.distance} and expected {expected.distance}"
      check expected.distance == self.hitProp.distance
      check expected.bBlockingHit == self.hitProp.bBlockingHit
    proc shouldBeAbleToWriteATextProp() =
      let expected = "hola".toText()
      self.textProp = "empty".toText()
          clsName: "A" & self.getClass.getName(),
          value: (textProp: expected).toRuntimeField()
      check expected == self.textProp
    proc shouldBeAbleToWriteASetProp() =
      let expected = makeTSet(2, 1, 5)
      self.setProp = makeTSet[int]()
          clsName: "A" & self.getClass.getName(),
          value: (setProp: expected).toRuntimeField()
      check expected == self.setProp
uClass AUECallMapTest of ANimTestBase:
    mapIntIntProp: TMap[int, int]
    mapIntFStringProp: TMap[int, FString]
    mapIntBoolProp: TMap[int, bool]
    mapIntStructProp: TMap[int, FStructVMTest]
    mapStringIntProp: TMap[FString, int]
    proc shouldBeAbleToWriteAMapIntIntProp() =
      let expected = { 1: 10, 2: 20}.toTable().toTMap()
      self.mapIntIntProp = makeTMap[int, int]()
          clsName: "A" & self.getClass.getName(),
          value: (mapIntIntProp: expected.toTable()).toRuntimeField()
      check expected == self.mapIntIntProp
    proc shouldBeAbleToWriteAMapIntFringProp() =
      let expected = { 1: f"Hola", 2: f"Mundo"}.toTable().toTMap()
      self.mapIntFStringProp = makeTMap[int, FString]()
          clsName: "A" & self.getClass.getName(),
          value: (mapIntFStringProp: expected.toTable()).toRuntimeField()
      check expected == self.mapIntFStringProp
    proc shouldBeAbleToWriteAMapIntBoolProp() =
      let expected = { 1: true, 2: false}.toTable()
      self.mapIntBoolProp = makeTMap[int, bool]()
          clsName: "A" & self.getClass.getName(),
          value: (mapIntBoolProp: expected).toRuntimeField()
      check expected.toTMap() == self.mapIntBoolProp
    proc shouldBeAbleToWriteAMapStructProp() =
      let expected = { 1: FStructVMTest(x:10, y:10, z:10), 2: FStructVMTest(x:20, y:20, z:20)}.toTable()
      self.mapIntStructProp = makeTMap[int, FStructVMTest]()
          clsName: "A" & self.getClass.getName(),
          value: (mapIntStructProp: expected).toRuntimeField()
      check expected.toTMap() == self.mapIntStructProp
    proc shouldBeAbleToWriteAMapStringIntProp() =
      let expected = { f"Hola": 10, f"Mundo": 20}.toTable().toTMap()
      self.mapStringIntProp = makeTMap[FString, int]()
          clsName: "A" & self.getClass.getName(),
          value: (mapStringIntProp: expected.toTable()).toRuntimeField()
      check expected == self.mapStringIntProp