/* -*- mode: c++; indent-tabs-mode: nil -*- */
/** @file QC_AbstractReflectionFunction.cpp AbstractReflectionFunction class definition */
/*
    Qore Programming Language

    Copyright (C) 2003 - 2023 Qore Technologies, s.r.o.

    Permission is hereby granted, free of charge, to any person obtaining a
    copy of this software and associated documentation files (the "Software"),
    to deal in the Software without restriction, including without limitation
    the rights to use, copy, modify, merge, publish, distribute, sublicense,
    and/or sell copies of the Software, and to permit persons to whom the
    Software is furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in
    all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    DEALINGS IN THE SOFTWARE.

    Note that the Qore library is released under a choice of three open-source
    licenses: MIT (as above), LGPL 2+, or GPL 2+; see README-LICENSE for more
    information.
*/

#include "qore_reflection.h"
#include "QC_AbstractReflectionFunction.h"
#include "QC_AbstractMethod.h"
#include "QC_AbstractVariant.h"
#include "QC_NormalMethodVariant.h"
#include "QC_StaticMethodVariant.h"
#include "QC_ConstructorMethodVariant.h"
#include "QC_DestructorMethodVariant.h"
#include "QC_CopyMethodVariant.h"
#include "QC_PseudoMethodVariant.h"
#include "QC_FunctionVariant.h"
#include "QC_AbstractClass.h"
#include "QC_Type.h"
#include "QC_Function.h"

QoreObject* QoreReflectionFunction::getFunctionObject(ExceptionSink* xsink) const {
    const QoreClass* c = f->getClass();
    if (c) {
        const QoreExternalMethodFunction* mf = static_cast<const QoreExternalMethodFunction*>(f);
        // must find the QoreMethod corresponding to this function by searching the class by name by method type
        const QoreMethod* m = mf->getMethod();
        ReferenceHolder<QoreReflectionMethod> meth(new QoreReflectionMethod(pgm, m), xsink);
        return get_method_object(meth, xsink);
    }
    return new QoreObject(QC_FUNCTION, pgm, new QoreReflectionFunction(pgm, f));
}

QoreObject* find_function_variant(QoreProgram* pgm, const QoreExternalFunction* func, const QoreListNode* args, size_t offset, method_type_e mtype, ExceptionSink* xsink) {
    // create argument list
    type_vec_t type_vec;

    for (size_t i = offset; i < (args ? args->size() : 0); ++i) {
        const QoreValue v = args->retrieveEntry(i);
        if (v.getType() != NT_OBJECT) {
            xsink->raiseException("GETVARIANT-ERROR", "expecting a Type argument describing the parameter type; got type '%s' instead", v.getTypeName());
            return nullptr;
        }

        ReferenceHolder<QoreType> t(static_cast<QoreType*>(v.get<const QoreObject>()->tryGetReferencedPrivateData(CID_TYPE, xsink)), xsink);
        if (!t) {
            if (!*xsink) {
                xsink->raiseException("GETVARIANT-ERROR", "expecting a Type argument describing the parameter type; got object of class '%s' instead", v.get<const QoreObject>()->getClassName());
            }
            return nullptr;
        }
        type_vec.push_back(t->typeInfo);
    }

    const QoreExternalVariant* v = func->findVariant(type_vec, xsink);

    if (*xsink) {
        return nullptr;
    }
    assert(v);

    return get_variant_object(pgm, func, mtype, v, xsink);
}

QoreObject* get_variant_object(QoreProgram* pgm, const QoreExternalFunction* f, method_type_e mtype, const QoreExternalVariant* v, ExceptionSink* xsink) {
    switch (mtype) {
        case MT_None:
            return new QoreObject(QC_FUNCTIONVARIANT, pgm, new QoreReflectionVariant(pgm, f, v));
        case MT_Static:
            return new QoreObject(QC_STATICMETHODVARIANT, pgm, new QoreReflectionVariant(pgm, f, v));
        case MT_Normal:
            return new QoreObject(QC_NORMALMETHODVARIANT, pgm, new QoreReflectionVariant(pgm, f, v));
        case MT_Constructor:
            return new QoreObject(QC_CONSTRUCTORMETHODVARIANT, pgm, new QoreReflectionVariant(pgm, f, v));
        case MT_Destructor:
            return new QoreObject(QC_DESTRUCTORMETHODVARIANT, pgm, new QoreReflectionVariant(pgm, f, v));
        case MT_Copy:
            return new QoreObject(QC_COPYMETHODVARIANT, pgm, new QoreReflectionVariant(pgm, f, v));
        case MT_Pseudo:
            return new QoreObject(QC_PSEUDOMETHODVARIANT, pgm, new QoreReflectionVariant(pgm, f, v));
        default:
            break;
    }

    assert(false);
    return nullptr;
}

void append_variant_objects(QoreListNode& l, QoreProgram* pgm, const QoreExternalFunction* f, method_type_e mtype, ExceptionSink* xsink) {
    QoreExternalFunctionIterator i(*f);
    while (i.next()) {
        l.push(get_variant_object(pgm, f, mtype, i.getVariant(), xsink), xsink);
    }
}

//! The abstract base class for %Qore functions and class methods
/**
    This class cannot be instantiated directly and also cannot be directly inherited by user-defined classes.

    @since %Qore 0.9.0
 */
qclass AbstractReflectionFunction [ns=Qore::Reflection; arg=QoreReflectionFunction* f; dom=REFLECTION];

//! Throws an exception if called directly; this class can only be instantiated by builtin subclasses
/** @throw ABSTRACTREFLECTIONFUNCTION-CONSTRUCTOR-ERROR this exception is thrown if this class is constructed directly (also if directly inherited by user classes)
 */
AbstractReflectionFunction::constructor() {
    xsink->raiseException("ABSTRACTREFLECTIONFUNCTION-CONSTRUCTOR-ERROR", "this class is an abstract class and cannot be constructed directly or inherited directly by a user-defined class");
}

//! Returns @ref True if the function or method object passed as an argument is equal to the current object; @ref False if not
/** @par Example:
    @code{.py}
bool b = f1.isEqual(f2);
    @endcode

    @param func the function or method object to check with the current object for equality

    @return @ref True if the object passed as an argument is equal to the current object; @ref False if not

    @note if this method is called on user functions or methods created from the same source code but in different
    @ref Qore::Program "Program" containers, it will return @ref False
*/
bool AbstractReflectionFunction::isEqual(AbstractReflectionFunction[QoreReflectionFunction] func) [flags=CONSTANT] {
    ReferenceHolder<QoreReflectionFunction> holder(func, xsink);

    return f->f == func->f;
}

//! returns the function or method name
/** @par Example:
    @code{.py}
string name = f.getName();
    @endcode

    @return the function or method name

    @throw PROGRAM-ERROR thrown if the @ref Qore::Program "Program" object holding the function or method has been destroyed
 */
string AbstractReflectionFunction::getName() [flags=RET_VALUE_ONLY] {
    // get read access to program object
    QoreExternalProgramContextHelper tch(xsink, f->pgm);
    if (*xsink) {
        return QoreValue();
    }

    // the function ptr must be valid
    return new QoreStringNode(f->f->getName());
}

//! Returns the module name providing the function or method or @ref nothing if the function or method was not provided by a module
/** @par Example:
    @code{.py}
*string str = f.getModuleName();
    @endcode

    @return the module name providing the function or method or @ref nothing if the function or method was not provided by a module

    @since %Qore 0.9.5
*/
*string AbstractReflectionFunction::getModuleName() [flags=RET_VALUE_ONLY] {
    QoreExternalProgramContextHelper tch(xsink, f->pgm);
    if (*xsink) {
        return QoreValue();
    }

    // the function ptr must be valid
    const char* mod = f->f->getModuleName();
    return mod ? new QoreStringNode(mod) : QoreValue();
}

//! returns @ref True if the function or method is a builtin function or method
/** @par Example:
    @code{.py}
bool b = f.isBuiltin();
    @endcode

    @return @ref True if the function or method is a builtin function or method

    @throw PROGRAM-ERROR thrown if the @ref Qore::Program "Program" object holding the function or method has been destroyed

    @note complementary to @ref isUser()
*/
bool AbstractReflectionFunction::isBuiltin() [flags=RET_VALUE_ONLY] {
    // get read access to program object
    QoreExternalProgramContextHelper tch(xsink, f->pgm);
    if (*xsink) {
        return QoreValue();
    }

    // the function ptr must be valid
    return f->f->isBuiltin();
}

//! returns @ref True if the function or method is a user function or method
/** @par Example:
    @code{.py}
bool b = f.isUser();
    @endcode

    @return @ref True if the function or method is a user function or method

    @throw PROGRAM-ERROR thrown if the @ref Qore::Program "Program" object holding the function or method has been destroyed

    @note complementary to @ref isBuiltin()
*/
bool AbstractReflectionFunction::isUser() [flags=RET_VALUE_ONLY] {
    // get read access to program object
    QoreExternalProgramContextHelper tch(xsink, f->pgm);
    if (*xsink) {
        return QoreValue();
    }

    // the function ptr must be valid
    return !f->f->isBuiltin();
}

//! returns @ref True if the function or method has been injected as a dependency injection
/** @par Example:
    @code{.py}
bool b = f.isInjected();
    @endcode

    @return @ref True if the function or method has been injected as a dependency injection

    @throw PROGRAM-ERROR thrown if the @ref Qore::Program "Program" object holding the function or method has been destroyed
*/
bool AbstractReflectionFunction::isInjected() [flags=RET_VALUE_ONLY] {
    // get read access to program object
    QoreExternalProgramContextHelper tch(xsink, f->pgm);
    if (*xsink) {
        return QoreValue();
    }

    // the function ptr must be valid
    return f->f->isInjected();
}

//! returns @ref True if the object is a method but not a pseudo-method
/** @par Example:
    @code{.py}
bool b = f.isMethod();
    @endcode

    @return @ref True if the object is a method but not a pseudo-method

    @throw PROGRAM-ERROR thrown if the @ref Qore::Program "Program" object holding the function or method has been destroyed
*/
bool AbstractReflectionFunction::isMethod() [flags=RET_VALUE_ONLY] {
    // get read access to program object
    QoreExternalProgramContextHelper tch(xsink, f->pgm);
    if (*xsink) {
        return QoreValue();
    }

    // the function ptr must be valid
    return (bool)(f->f->getClass() && !f->f->getClass()->isPseudoClass());
}

//! returns @ref True if the object is a pseudo-method
/** @par Example:
    @code{.py}
bool b = f.isPseudoMethod();
    @endcode

    @return @ref True if the object is a pseudo-method

    @throw PROGRAM-ERROR thrown if the @ref Qore::Program "Program" object holding the function or method has been destroyed
*/
bool AbstractReflectionFunction::isPseudoMethod() [flags=RET_VALUE_ONLY] {
    // get read access to program object
    QoreExternalProgramContextHelper tch(xsink, f->pgm);
    if (*xsink) {
        return QoreValue();
    }

    // the function ptr must be valid
    return (bool)(f->f->getClass() && f->f->getClass()->isPseudoClass());
}

//! returns the number of variants implemented by the function or method
/** @par Example:
    @code{.py}
int n = f.numVariants();
    @endcode

    @return the number of variants implemented by the function or method

    @throw PROGRAM-ERROR thrown if the @ref Qore::Program "Program" object holding the function or method has been destroyed
*/
int AbstractReflectionFunction::numVariants() [flags=RET_VALUE_ONLY] {
    // get read access to program object
    QoreExternalProgramContextHelper tch(xsink, f->pgm);
    if (*xsink) {
        return QoreValue();
    }

    // the function ptr must be valid
    return f->f->numVariants();
}

//! returns a bitfield of @ref code_flag_constants "code flags" common to all variants for this function
/** @par Example:
    @code{.py}
int i = f.getCodeFlags();
    @endcode

    @return a bitfield of @ref code_flag_constants "code flags" common to all variants for this function

    @throw PROGRAM-ERROR thrown if the @ref Qore::Program "Program" object holding the function or method has been destroyed

    @see getCodeFlagList()

    @since %Qore 0.9.5
*/
int AbstractReflectionFunction::getCodeFlags() [flags=CONSTANT] {
    // get read access to program object
    QoreExternalProgramContextHelper tch(xsink, f->pgm);
    if (*xsink) {
        return QoreValue();
    }

    // the function ptr must be valid
    return f->f->getCodeFlags();
}

//! returns a list of strings of code flags common to all variants for this function
/** @par Example:
    @code{.py}
list<string> l = f.getCodeFlagList();
    @endcode

    @return list of strings of code flags common to all variants for this function; possible values are:
    - \c "NOOP": the variant has the @ref Qore::Reflection::CF_NOOP "CF_NOOP" flag set
    - \c "USES_EXTRA_ARGS": the variant has the @ref Qore::Reflection::CF_USES_EXTRA_ARGS "CF_USES_EXTRA_ARGS" flag set
    - \c "CONSTANT": the variant has the @ref Qore::Reflection::CF_CONSTANT "CF_CONSTANT" flag set
    - \c "DEPRECATED": the variant has the @ref Qore::Reflection::CF_DEPRECATED "CF_DEPRECATED" flag set
    - \c "RET_VALUE_ONLY": the variant has the @ref Qore::Reflection::CF_RET_VALUE_ONLY "CF_RET_VALUE_ONLY" flag set
    - \c "RUNTIME_NOOP": the variant has the @ref Qore::Reflection::CF_RUNTIME_NOOP "CF_RUNTIME_NOOP" flag set

    @throw PROGRAM-ERROR thrown if the @ref Qore::Program "Program" object holding the function or method has been destroyed

    @see getCodeFlags()

    @since %Qore 0.9.5
*/
list<string> AbstractReflectionFunction::getCodeFlagList() [flags=RET_VALUE_ONLY] {
    // get read access to program object
    QoreExternalProgramContextHelper tch(xsink, f->pgm);
    if (*xsink) {
        return QoreValue();
    }

    // the function ptr must be valid
    return get_code_flag_list(f->f->getCodeFlags());
}

//! returns the functional domain common to all variants for this function (for builtin functions only) as a bitfield of @ref functional_domain_constants
/** @par Example:
    @code{.py}
int i = f.getDomain();
    @endcode

    @return the functional domain common to all variants for this function (for builtin functions only) as a bitfield of @ref functional_domain_constants

    @throw PROGRAM-ERROR thrown if the @ref Qore::Program "Program" object holding the function or method has been destroyed

    @note user functions always return 0 here; a value is only returned by builtin functions

    @since %Qore 0.9.5
*/
int AbstractReflectionFunction::getDomain() [flags=RET_VALUE_ONLY] {
    // get read access to program object
    QoreExternalProgramContextHelper tch(xsink, f->pgm);
    if (*xsink) {
        return QoreValue();
    }

    // the function ptr must be valid
    return f->f->getDomain();
}

//! returns a list of functional domain strings common to all variants for this function (for builtin functions only); an empty list is returned for user functions
/** @par Example:
    @code{.py}
list<string> l = f.getDomainStringList();
    @endcode

    @return a list of functional domain strings common to all variants for this function (for builtin functions only)

    @throw PROGRAM-ERROR thrown if the @ref Qore::Program "Program" object holding the function or method has been destroyed

    @note user functions always return an empty list here

    @since %Qore 0.9.5
*/
list<string> AbstractReflectionFunction::getDomainStringList() [flags=RET_VALUE_ONLY] {
    // get read access to program object
    QoreExternalProgramContextHelper tch(xsink, f->pgm);
    if (*xsink) {
        return QoreValue();
    }

    // the function ptr must be valid
    return domain_bitfield_to_string_list(f->f->getDomain(), xsink);
}

