/* ----------------------------------------------------------------------- *//**
 *
 * @file FunctionHandle_impl.hpp
 *
 *//* ----------------------------------------------------------------------- */

#ifndef MADLIB_POSTGRES_FUNCTIONHANDLE_IMPL_HPP
#define MADLIB_POSTGRES_FUNCTIONHANDLE_IMPL_HPP

namespace madlib {

namespace dbconnector {

namespace postgres {

inline
FunctionHandle::Argument::Argument(AnyType inValue)
  : AnyType(inValue) { }

template <typename T>
inline
FunctionHandle::Argument::Argument(const T& inValue)
  : AnyType(inValue, true /* forceLazyConversionToDatum */) { }

inline
FunctionHandle::FunctionHandle(SystemInformation* inSysInfo, Oid inFuncID)
  : mSysInfo(inSysInfo),
    mFuncInfo(inSysInfo->functionInformation(inFuncID)),
    mFuncCallOptions(GarbageCollectionAfterCall) { }

inline
UDF::Pointer
FunctionHandle::funcPtr() {
    return mFuncInfo->cxx_func;
}

/**
 * @brief Return the OID of this function
 */
inline
Oid
FunctionHandle::funcID() const {
    return mFuncInfo->oid;
}

inline
FunctionHandle&
FunctionHandle::setFunctionCallOptions(uint32_t inFlags) {
    mFuncCallOptions = inFlags;
    return *this;
}

inline
FunctionHandle&
FunctionHandle::unsetFunctionCallOptions(uint32_t inFlags) {
    mFuncCallOptions &= ~inFlags;
    return *this;
}

inline
uint32_t
FunctionHandle::getFunctionCallOptions() const {
    return mFuncCallOptions;
}

/**
 * @brief Wrapper around FunctionCallInvoke
 *
 * We factor FunctionCallInvoke out in a separate function because local
 * variables could potentially be clobbered during the 'longjmp' otherwise.
 */
inline
Datum
FunctionHandle::internalInvoke(FunctionCallInfo inFCInfo) {
    Datum result = 0;
    MADLIB_PG_TRY {
        result = FunctionCallInvoke(inFCInfo);
    } MADLIB_PG_CATCH {
        throw std::runtime_error(std::string("Exception while invoking '")
            + mSysInfo->functionInformation(mFuncInfo->oid)->getFullName()
            + "'. Error was:\n" + MADLIB_PG_ERROR_DATA()->message);
    } MADLIB_PG_END_TRY;
    return result;
}

inline
AnyType
FunctionHandle::invoke(AnyType &args) {
    madlib_assert(args.isComposite(), std::logic_error(
        "FunctionHandle::invoke() called with simple type."));

    if (args.numFields() > mFuncInfo->nargs)
        throw std::invalid_argument(std::string("More arguments given than "
            "expected by '")
            + mSysInfo->functionInformation(mFuncInfo->oid)->getFullName()
            + "'.");

    bool hasNulls = false;
    for (uint16_t i = 0; i < args.numFields(); ++i)
        hasNulls |= args[i].isNull();

    // If function is strict, we must not call the function at all
    if (mFuncInfo->isstrict && hasNulls)
        return AnyType();

    if (mFuncInfo->cxx_func &&
        !(mFuncCallOptions & GarbageCollectionAfterCall)) {

        // We have called this function before, so we can now do a shortcut:
        // Directly call the C++ function without any detour through the backend
        AnyType::LazyConversionToDatumOverride raii(true);
        return mFuncInfo->cxx_func(args);
    }

    MemoryContext oldContext = NULL;
    MemoryContext callContext = NULL;
    if (mFuncCallOptions & GarbageCollectionAfterCall) {
        // In order to do garbage collection, we need to create a new
        // memory context
        callContext = AllocSetContextCreate(CurrentMemoryContext,
            "C++ AL / FunctionHandle::invoke memory context",
            ALLOCSET_DEFAULT_MINSIZE,
            ALLOCSET_DEFAULT_INITSIZE,
            ALLOCSET_DEFAULT_MAXSIZE);
        oldContext = MemoryContextSwitchTo(callContext);
    }

#if PG_VERSION_NUM >= 120000
    FunctionCallInfoBaseData funcPtrCallInfo;
    int nargs = args.numFields();
    MemSet(&funcPtrCallInfo, 0, SizeForFunctionCallInfo(nargs));
#else
    FunctionCallInfoData funcPtrCallInfo;
#endif
    // Initializes all the fields of a FunctionCallInfoData except for the arg[]
    // and argnull[] arrays
    madlib_InitFunctionCallInfoData(
        funcPtrCallInfo,

        // FmgrInfo *flinfo -- ptr to lookup info used for this call
        mFuncInfo->getFuncMgrInfo(),

        // short nargs -- # arguments actually passed
        args.numFields(),

        // Oid fncollation -- collation for function to use
        mSysInfo->collationOID,

        // fmNodePtr context -- pass info about context of call
        NULL,

        // fmNodePtr resultinfo -- pass or return extra info about result
        NULL
    );

#if PG_VERSION_NUM >= 120000
    for (uint16_t i = 0; i < funcPtrCallInfo.nargs; ++i) {

        funcPtrCallInfo.args[i].value = args[i].getAsDatum(&funcPtrCallInfo,
            mFuncInfo->getArgumentType(i));
        funcPtrCallInfo.args[i].isnull = args[i].isNull();
        elog(WARNING, "funcPtrCallInfo.args[i].value %d funcPtrCallInfo.args[i].isnull %d", funcPtrCallInfo.args[i].value, funcPtrCallInfo.args[i].isnull);
    }
#else
    for (uint16_t i = 0; i < funcPtrCallInfo.nargs; ++i) {
        funcPtrCallInfo.arg[i]= args[i].getAsDatum(&funcPtrCallInfo,
            mFuncInfo->getArgumentType(i));
        funcPtrCallInfo.argnull[i] = args[i].isNull();
    }
#endif


    Datum result = internalInvoke(&funcPtrCallInfo);

    if (oldContext) {
        MemoryContextSwitchTo(oldContext);
        TypeInformation* typeInfo
            = mSysInfo->typeInformation(mFuncInfo->rettype);
        result = datumCopy(result, typeInfo->isByValue(), typeInfo->getLen());
        MemoryContextDelete(callContext);
    }

    return funcPtrCallInfo.isnull
        ? AnyType()
        : AnyType(mSysInfo, result, mFuncInfo->rettype, /* isMutable */ true);
}

inline
AnyType
FunctionHandle::operator()() {
    AnyType nil;
    return invoke(nil);
}

// In the following, we define
// AnyType operator()(FunctionHandle::Argument& inArgs1, ...,
//     FunctionHandle::Argument& inArgsN)
// using Boost.Preprocessor.
#define MADLIB_APPEND_ARG(z, n, data) \
    << data ## n
#define MADLIB_OPERATOR_DEF(z, n, _ignored) \
    inline \
    AnyType \
    FunctionHandle::operator()( \
        BOOST_PP_ENUM_PARAMS_Z(z, BOOST_PP_INC(n), \
            FunctionHandle::Argument inArg) \
    ) { \
        AnyType args; \
        args BOOST_PP_REPEAT(BOOST_PP_INC(n), MADLIB_APPEND_ARG, inArg); \
        return invoke(args); \
    }
BOOST_PP_REPEAT(MADLIB_FUNC_MAX_ARGS, MADLIB_OPERATOR_DEF, 0 /* ignored */)
#undef MADLIB_OPERATOR_DEF
#undef MADLIB_APPEND_ARG

inline
SystemInformation*
FunctionHandle::getSysInfo() const {
    return mSysInfo;
}

} // namespace postgres

} // namespace dbconnector

} // namespace madlib

#endif // defined(MADLIB_POSTGRES_FUNCTIONHANDLE_IMPL_HPP)
