/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */

/*!
 *  Copyright (c) 2018 by Contributors
 * \brief gotvm package source for TVMFunction interface.
 * \file function.go
 */

package gotvm

//#include "gotvm.h"
import "C"

import (
    "unsafe"
    "encoding/binary"
    "errors"
    "runtime"
    "reflect"
    "fmt"
)

// Function type in golang hold pointer for the TVMFunction handle.
type Function uintptr

// nativeCPtr returns type freed uintptr for the Function.
func (tvmfunction Function) nativeCPtr() (retVal uintptr) {
    retVal = (uintptr)(tvmfunction)
    return
}

// Invoke calls the TVM packed function referred by the handle with given arguments.
func (tvmfunction *Function) Invoke(args ...interface{}) (retVal *Value, err error) {
    funccall := func (fargs ...interface{}) (*Value, error) {
        return callNativeFunction(tvmfunction, fargs)
    }
    // Check is any args are contain any ValueArray
    // Possible is it's a args forward from one packed function to another.
    valueArrayFound := false
    for ii := range args {
        switch args[ii].(type) {
            case []*Value:
                valueArrayFound = true
        }
    }

    if !valueArrayFound {
        return funccall(args...)
    }
    if len(args) != 1 {
        err = fmt.Errorf("Not supported if packed function args are a mix of []Value and other types")
        return
    }

    valArray := args[0].([]*Value)
    if len(valArray) > 0 {
        newArgs := make([]interface{}, len(valArray))
        for ii := range valArray {
            newVal := newTVMValue()
            newVal.moveFrom(valArray[ii])
            newArgs[ii] = newVal
        }

        return funccall(newArgs...)
    }
    return funccall()
}

// FuncListGlobalNames is used to query global callable packed function names from TVM.
//
// returns slice of string holding function names and error if any.
func FuncListGlobalNames() (retVal []string, err error) {
    var str string
    ret := (int32)(C._TVMFuncListGlobalNames(unsafe.Pointer((&str))))
    if ret != 0 {
        err = errors.New(getTVMLastError())
        return
    }

    str = goStringFromNative(*(*string)(unsafe.Pointer(&str)))
    bin := binary.LittleEndian
    size := bin.Uint64([]byte(str[:8]))
    str = str[8:]
    retVal = make([]string, size)
    for i := range retVal {
        len := bin.Uint64([]byte(str[:8]))
        str = str[8:]
        retVal[i] = str[:len]
        str = str[len:]
    }
    return
}

// GetGlobalFunction is to get handle to the given global function name.
//
// `funcname` is the name of global packed function.
//
// returns a function closure with signature
//         func (args ...interface{}) (interface{}, error) and  error if any.
//
// The closure function can be used to call Function with arguments directly.
//
// Variadic arguments can be any type which can be embed into Value.
func GetGlobalFunction(funcname string) (retVal *Function, err error) {
    var funp uintptr

    cfuncname := C.CString(funcname)
    ret := (int32)(C.TVMFuncGetGlobal(cfuncname,
                                      (*_Ctype_TVMFunctionHandle)(unsafe.Pointer(&funp))))
    C.free(unsafe.Pointer(cfuncname))

    if ret != 0 {
        err = errors.New(getTVMLastError())
        return
    }

    handle := new(Function)
    *handle = Function(funp)
    finalizer := func(fhandle *Function) {
        nativeTVMFuncFree(fhandle)
        fhandle = nil
    }
    runtime.SetFinalizer(handle, finalizer)
    retVal = handle
    return
}

// callNativeFunction is routine which calls gotvm native wrapper with given arguments.
//
// `handle` is the handle for Function.
//
// `args` are the variadic arguments to the Function.
//
// returns the interface for the return value from TVM if any and error if any.
func callNativeFunction(handle *Function, args []interface{}) (retVal *Value, err error) {
    argsIn := make([]*Value, len(args))
    var typeCodes []int32
    if len(args) != 0 {
        typeCodes = make([]int32, len(args))
    } else {
        typeCodes = make([]int32, 1)
    }

    for ii := range args {
        argsIn[ii] = newTVMValue()
        if typeCodes[ii], err = argsIn[ii].setValue(args[ii]); err != nil {
            return
        }
    }

    retVal = newTVMValue()
    argsOut := []*Value{retVal}
    retTypeCode := KNull
    err = nativeTVMFuncCall(handle, argsIn, typeCodes, argsOut, &retTypeCode)
    if err != nil {
        retVal = nil
        return
    }
    retVal.isLocal = false
    retVal.dtype = retTypeCode
    return
}

// nativeTVMFuncFree free the function handle allocated in TVM runtime.
//
// `funp` is the Function handle to be freed.
func nativeTVMFuncFree(funp *Function) (retVal int32) {
    retVal = (int32) (C.TVMFuncFree(C.TVMFunctionHandle(funp.nativeCPtr())))
    return
}

// nativeToGoSlice converts native TVMValue array to Golang slice of TVMValue
//
//
func nativeToGoSlice(nargValues (*C.void), argValues []*Value, typeCodes []int32) {
    for ii := range argValues {
        C._TVMValueNativeGet(unsafe.Pointer(argValues[ii].nativeCPtr()),
                             unsafe.Pointer(nargValues),
                             C.int(int32(ii)))
        argValues[ii].dtype = typeCodes[ii]
    }
}

// nativeFromGoSlice converts golang slice of TVMValue to native TVMValue array.
//
//
func nativeFromGoSlice(argValues []*Value) (nptr (*C.void)) {
    nargValues := ((uintptr)(C.malloc(C.ulong(C.sizeof_TVMValue * len(argValues)))))
    for ii := range argValues {
        C._TVMValueNativeSet(unsafe.Pointer(nargValues),
                             unsafe.Pointer(argValues[ii].nativeCPtr()),
                             C.int(int32(ii)))
    }
    nptr = (*C.void)(unsafe.Pointer(nargValues))
    return
}

// nativeTVMFuncCall executes the function with given arguments
//
// `funp` Function handle to the packed function.
//
// `argValues` is the slice of Value which are arguments to the packed function.
//
// `typeCodes` is the alice of argument type codes corresponding to argValues.
//
// `retValues` is return argument which is slice of return values from the packed function.
//
// `retTypeCode` is int32 holding type codes for retValue
//
// Returns err indicating native error if any.
func nativeTVMFuncCall(funp *Function, argValues []*Value, typeCodes []int32,
                 retValues []*Value, retTypeCode *int32) (err error) {
    nargValues := nativeFromGoSlice(argValues)
    nretValues := nativeFromGoSlice(retValues)
	result := (int32)(C.TVMFuncCall(_Ctype_TVMFunctionHandle(*funp),
                                    (*_Ctype_TVMValue)(unsafe.Pointer(nargValues)),
                                    (*_Ctype_int)(unsafe.Pointer(&(typeCodes[0]))),
                                    C.int(len(argValues)),
                                    (*_Ctype_TVMValue)(unsafe.Pointer(nretValues)),
                                    (*_Ctype_int)(unsafe.Pointer(retTypeCode))))
    nativeToGoSlice(nargValues, argValues, typeCodes)
    nativeToGoSlice(nretValues, retValues, (*[1<<31] int32)(unsafe.Pointer(retTypeCode))[:1:1])
    C.free(unsafe.Pointer(nargValues))
    C.free(unsafe.Pointer(nretValues))

    if result != 0 {
	    err = errors.New(getTVMLastError())
    }
    return
}

// goCallBack is a structure holding the go callback function pointer.
// This wrapping is necessary as cgo doesn't support
// passing golang functions type conversion to native.
type goCallBack struct {
    cb func (args ...*Value) (interface{}, error)
}

//export goTVMCallback
func goTVMCallback(args C.native_voidp, typeCodes C.native_voidp, numArgs int32,
                   retArg C.native_voidp, resourceHandle C.native_voidp) (ret int32){
    fcb := (*goCallBack)(resourceHandle)
    // Make Value Sice from native TVMValue pointer.
    argValues := make([]*Value, numArgs)

    for ii := range argValues {
        argValues[ii] = newTVMValue()
        argValues[ii].isLocal = false
    }

    // Prepare arguments for golang callback function
    nativeToGoSlice((*C.void)(unsafe.Pointer(args)), argValues,
                    (*[1<<31] int32)(unsafe.Pointer(typeCodes))[:numArgs:numArgs])
    cbargs := argValues

    // Execute the callback
    retVal, err := fcb.cb(cbargs...)
    if err != nil {
        errStr := err.Error()
        setTVMLastError(errStr)
        return -1
    }

    // It's possible a packed function directly return
    // the return value of another packed function.
    //
    // Inside a packed func :
    //      ```return pfunc.Invoke(args)```
    //
    // In this case pfunc returns nil which is
    // returned as an interface holding nil *Value.
    // Which becomes a valid retVal holding nil *Value.
    isRetNull := false
    switch retVal.(type) {
        case *Value:
            pRet := retVal.(*Value)
            if pRet == nil {
                isRetNull = true
            }
    }

    // Handle return value from callback function
    if retVal != nil && !isRetNull {
        var retTypeCode int32
        retValues := []*Value{newTVMValue()}

        retTypeCode, err = retValues[0].setValue(retVal)
        if err != nil {
            errStr := err.Error()
            setTVMLastError(errStr)
            return -1
        }
        nretValues := nativeFromGoSlice(retValues)

        // Handle KStr, KBytes: Local finalizers shouldn't try freeing them.
        retValues[0].isLocal = false

        apiRet := (int32) (C.TVMCFuncSetReturn(_Ctype_TVMRetValueHandle(retArg),
                                               (*_Ctype_TVMValue)(unsafe.Pointer(nretValues)),
                                               (*_Ctype_int)(unsafe.Pointer(&retTypeCode)), 1))
        C.free(unsafe.Pointer(nretValues))
        if apiRet != 0 {
            errStr := string("TVMCFuncSetReturn failed ")
            setTVMLastError(errStr)
        }
    }
    return
}

// ConvertFunction converts given golang function to TVM packed function.
//
// `args[0]` function pointer for a type ```func (args ...interface{}) (interface{})```
//
// Returns Function handle and err if any.
func ConvertFunction(args ...interface{}) (retVal *Function, err error) {
    function := args[0].(func (args ...*Value) (interface{}, error))
    fcb := &goCallBack{cb:function}
    var funp uintptr

    result := (int32) (C._ConvertFunction(unsafe.Pointer(fcb),
                                          unsafe.Pointer(&funp)))
    if result != 0 {
	    err = errors.New(getTVMLastError())
    }

    handle := new(Function)
    *handle = Function(funp)
    finalizer := func(fhandle *Function) {
        nativeTVMFuncFree(fhandle)
        fhandle = nil
    }
    runtime.SetFinalizer(handle, finalizer)
    retVal = handle
    return
}

// RegisterFunction registers the golang func in TVM runtime global space.
//
// `args[0]` function pointer for a type ```func (args ...interface{}) (interface{})```
//
// `args[1]` Optional argument of function name with which it will be registered.
//           If not passed we use function name from reflection.
//
// Returns err indicating native error if any.
func RegisterFunction(args ...interface{}) (err error) {
    fhandle, err := ConvertFunction(args...)
    if err != nil {
        return
    }

    funcname := runtime.FuncForPC(reflect.ValueOf(args[0]).Pointer()).Name()
    if len(args) > 1 {
        funcname = args[1].(string)
    }

    cfuncname := C.CString(funcname)
    result := (int32) (C.TVMFuncRegisterGlobal(cfuncname,
                                               _Ctype_TVMFunctionHandle(*fhandle),
                                               0)); // Override = False
    C.free(unsafe.Pointer(cfuncname))
    if result != 0 {
	    err = errors.New(getTVMLastError())
    }
    // Clear the finalizer as we don't need to control it anymore.
    runtime.SetFinalizer(fhandle, nil)
    return
}
