/* -*- mode: c++; indent-tabs-mode: nil -*- */
/*
    ql_object.qpp

    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/Qore.h>
#include "qore/intern/ql_object.h"
#include "qore/intern/qore_program_private.h"
#include "qore/intern/QoreClassIntern.h"

QoreValue q_call_static_method_args(QoreProgram* pgm, const QoreStringNode* class_name, const QoreStringNode* method, const QoreListNode* args, ExceptionSink* xsink) {
    TempEncodingHelper cls_path(class_name, QCS_DEFAULT, xsink);
    if (!cls_path) {
        assert(*xsink);
        return QoreValue();
    }
    TempEncodingHelper method_name(method, QCS_DEFAULT, xsink);
    if (!method_name) {
        assert(*xsink);
        return QoreValue();
    }
    // we do not need to grab the thread lock, as we are already executing in the current Program object
    const QoreClass* cls = pgm->findClass(cls_path->c_str(), xsink);
    if (!cls) {
        if (!*xsink) {
            xsink->raiseException("UNKNOWN-CLASS", "cannot resolve class '%s'", cls_path->c_str());
        }
        return QoreValue();
    }

    const QoreMethod* m = cls->findLocalStaticMethod(method_name->c_str());
    if (!m) {
        xsink->raiseException("UNKNOWN-METHOD", "cannot resolve static method '%s::%s()'",
            cls_path->c_str(), method_name->c_str());
        return QoreValue();
    }

    return QoreObject::evalStaticMethod(*m, m->getClass(), args, xsink);
}

static QoreValue call_static_method_args_intern(const QoreStringNode* class_name, const QoreStringNode* method, const QoreListNode* args, ExceptionSink* xsink) {
    return q_call_static_method_args(getProgram(), class_name, method, args, xsink);
}

static QoreObject* create_object_intern(const QoreStringNode* class_name, unsigned arg_offset, const QoreListNode* args, ExceptionSink* xsink) {
    TempEncodingHelper tmp(class_name, QCS_DEFAULT, xsink);
    if (!tmp) {
        assert(*xsink);
        return nullptr;
    }

    const QoreClass* qc = qore_program_private::runtimeFindClass(*getProgram(), tmp->c_str(), xsink);
    if (!qc) {
        if (!*xsink)
            xsink->raiseException("CREATE-OBJECT-ERROR", "cannot resolve class '%s' for instantiation", tmp->c_str());
        return nullptr;
    }

    if (getProgram()->getParseOptions64() & qc->getDomain())
        xsink->raiseException("CREATE-OBJECT-ERROR", "current Program sandboxing restrictions do not allow access to the '%s' class", qc->getName());

    if (qore_class_private::runtimeCheckInstantiateClass(*qc, xsink)) {
        return nullptr;
    }

    const QoreListNode* real_args;
    ReferenceHolder<QoreListNode> tmp_args(xsink);
    if (arg_offset) {
        tmp_args = args->size() > arg_offset ? args->copyListFrom(arg_offset) : nullptr;
        real_args = *tmp_args;
    }
    else {
        real_args = args;
    }

    //printd(5, "create_object_intern() '%s' offset: %d size: %d (a: %d)\n", tmp->c_str(), arg_offset, real_args ? real_args->size() : 0, args ? args->size() : 0);
    return qc->execConstructor(real_args, xsink);
}

/** @defgroup object_functions Object Functions
    Object functions
*/
///@{

//! Returns a list of strings of the names of the methods of the class of the object passed as a parameter
/**
    @param obj an object of the class to get the method list from

    @return a list of strings of the names of the methods of the class of the object passed as a parameter; returns all methods in the class, both private and public but does not return inherited methods

    @deprecated use get_method_list(); camel-case function names were deprecated in %Qore 0.8.12
 */
list getMethodList(object obj) [flags=CONSTANT,DEPRECATED] {
    return obj->getClass()->getMethodList();
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
nothing getMethodList() [flags=RUNTIME_NOOP,DEPRECATED] {
}

//! Returns a list of strings of the names of the methods of the class of the object passed as a parameter
/**
    @param obj an object of the class to get the method list from

    @return a list of strings of the names of the methods of the class of the object passed as a parameter; returns all methods in the class, both private and public but does not return inherited methods

    @par Example:
    @code{.py}
list<string> l = get_method_list(obj);
    @endcode

    @since %Qore 0.8.12 as a replacement for deprecated camel-case getMethodList()
 */
list<string> get_method_list(object obj) [flags=CONSTANT] {
    return obj->getClass()->getMethodList();
}

//! Calls a method of an object, passing the remainder of the arguments to the function as arguments to the method
/**
    @param obj the object to use for the call
    @param method the method to call
    @param ... any additional arguments to the method

    @return the value returned by the method call

    @par Example:
    @code{.py}
auto result = callObjectMethod(obj, "method", arg1, arg2);
    @endcode

    @throw METHOD-DOES-NOT-EXIST The named method does not exist in this class
    @throw ILLEGAL-EXPLICIT-METHOD-CALL The named method may not be called explicitly
    @throw METHOD-IS-PRIVATE The named method is private and therefore can only be called within the class
    @throw BASE-CLASS-IS-PRIVATE The named method is a member of a privately inherited base class

    @note the method called could cause additional exceptions to be thrown

    @deprecated use call_object_method(); camel-case function names were deprecated in %Qore 0.8.12
 */
auto callObjectMethod(object obj, string method, ...) [flags=DEPRECATED] {
    ReferenceHolder<QoreListNode> call_args(xsink);

    // if there are arguments to pass
    if (num_args(args) > 2) {
        // create argument list by copying current list
        call_args = args->copyListFrom(2);
    }

    return obj->evalMethod(method, *call_args, xsink);
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
nothing callObjectMethod() [flags=RUNTIME_NOOP,DEPRECATED] {
}

//! Calls a method of an object, passing the remainder of the arguments to the function as arguments to the method
/**
    @param obj the object to use for the call
    @param method the method to call
    @param ... any additional arguments to the method

    @return the value returned by the method call

    @par Example:
    @code{.py}
auto result = call_object_method(obj, "method", arg1, arg2);
    @endcode

    @throw METHOD-DOES-NOT-EXIST The named method does not exist in this class
    @throw ILLEGAL-EXPLICIT-METHOD-CALL The named method may not be called explicitly
    @throw METHOD-IS-PRIVATE The named method is private and therefore can only be called within the class
    @throw BASE-CLASS-IS-PRIVATE The named method is a member of a privately inherited base class

    @note the method called could cause additional exceptions to be thrown

    @since %Qore 0.8.12 as a replacement for deprecated camel-case callObjectMethod()

    @see
    - call_object_method_args()
    - call_static_method()
    - call_static_method_args()
*/
auto call_object_method(object obj, string method, ...) {
    ReferenceHolder<QoreListNode> call_args(xsink);

    // if there are arguments to pass
    if (num_args(args) > 2) {
        // create argument list by copying current list
        call_args = args->copyListFrom(2);
    }

    return obj->evalMethod(method, *call_args, xsink);
}

//! Calls a method of an object, using the optional third argument as the argument list to the method
/**
    @param obj the object to use for the call
    @param method the method to call
    @param varg any additional arguments to the method

    @return the value returned by the method call

    @throw METHOD-DOES-NOT-EXIST The named method does not exist in this class
    @throw ILLEGAL-EXPLICIT-METHOD-CALL The named method may not be called explicitly
    @throw METHOD-IS-PRIVATE The named method is private and therefore can only be called within the class
    @throw BASE-CLASS-IS-PRIVATE The named method is a member of a privately inherited base class

    @note the method called could cause additional exceptions to be thrown

    @deprecated use call_object_method_args(); camel-case function names were deprecated in %Qore 0.8.12
 */
auto callObjectMethodArgs(object obj, string method, *softlist<auto> varg) [flags=DEPRECATED] {
    return obj->evalMethod(method, varg, xsink);
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
nothing callObjectMethodArgs() [flags=RUNTIME_NOOP,DEPRECATED] {
}

//! Calls a method of an object, using the optional third argument as the argument list to the method
/**
    @param obj the object to use for the call
    @param method the method to call
    @param varg any additional arguments to the method

    @return the value returned by the method call

    @par Example:
    @code{.py}
auto result = call_object_method_args(obj, "method", arglist);
    @endcode

    @throw METHOD-DOES-NOT-EXIST The named method does not exist in this class
    @throw ILLEGAL-EXPLICIT-METHOD-CALL The named method may not be called explicitly
    @throw METHOD-IS-PRIVATE The named method is private and therefore can only be called within the class
    @throw BASE-CLASS-IS-PRIVATE The named method is a member of a privately inherited base class

    @note the method called could cause additional exceptions to be thrown

    @since %Qore 0.8.12 as a replacement for deprecated camel-case callObjectMethodArgs()

    @see
    - call_object_method()
    - call_static_method()
    - call_static_method_args()
*/
auto call_object_method_args(object obj, string method, *softlist<auto> varg) {
    return obj->evalMethod(method, varg, xsink);
}

//! Calls a static method of an object, passing the arguments to the function as arguments to the method
/** @par Example:
    @code{.py}
auto result = call_static_method("Namespace::Class::method", arg1, arg2);
    @endcode

    @param class_name the with an optional namespace prefix
    @param method the static method to call
    @param ... any additional arguments to the method

    @return the value returned by the method call

    @throw METHOD-DOES-NOT-EXIST The named method does not exist in this class
    @throw ILLEGAL-EXPLICIT-METHOD-CALL The named method may not be called explicitly
    @throw METHOD-IS-PRIVATE The named method is private and therefore can only be called within the class
    @throw BASE-CLASS-IS-PRIVATE The named method is a member of a privately inherited base class

    @note the method called could cause additional exceptions to be thrown

    @see
    - call_static_method_args()
    - call_object_method()
    - call_object_method_args()
    - @ref Qore::Program::callStaticMethod() "Program::callStaticMethod()"
    - @ref Qore::Program::callStaticMethodArgs() "Program::callStaticMethodArgs()"
*/
auto call_static_method(string class_name, string method, ...) {
    ReferenceHolder<QoreListNode> call_args(xsink);
    // if there are arguments to pass
    if (num_args(args) > 2) {
        // create argument list by copying current list
        call_args = args->copyListFrom(2);
    }
    return call_static_method_args_intern(class_name, method, *call_args, xsink);
}

//! Calls a static method of an object, passing the arguments to the function as arguments to the method
/** @par Example:
    @code{.py}
auto result = call_static_method_args("Namespace::Class::method", (arg1, arg2));
    @endcode

    @param class_name the with an optional namespace prefix
    @param method the static method to call
    @param call_args any additional arguments to the method

    @return the value returned by the method call

    @throw METHOD-DOES-NOT-EXIST The named method does not exist in this class
    @throw ILLEGAL-EXPLICIT-METHOD-CALL The named method may not be called explicitly
    @throw METHOD-IS-PRIVATE The named method is private and therefore can only be called within the class
    @throw BASE-CLASS-IS-PRIVATE The named method is a member of a privately inherited base class

    @note the method called could cause additional exceptions to be thrown

    @see
    - call_static_method()
    - call_object_method()
    - call_object_method_args()
    - @ref Qore::Program::callStaticMethod() "Program::callStaticMethod()"
    - @ref Qore::Program::callStaticMethodArgs() "Program::callStaticMethodArgs()"
*/
auto call_static_method_args(string class_name, string method, *softlist<auto> call_args) {
    return call_static_method_args_intern(class_name, method, call_args, xsink);
}

//! calls a pseudo-method on the given value
/** @param val the value to call the pseudo-method on
    @param meth the string method name of the pseudo-method to call
    @param ... any other arguments to the method

    @return the return value of the pseudo-method

    @see Qore::zzz8valuezzz9 for the class hierarchy of pseudo-classes

    @note this function can be used for security to ensure that a given pseudo-method of the Qore::zzz8objectzzz9 class is called,
    as by default if a method of the same name is implemented by the object's class, the class method will be called instead.

    @since %Qore 0.8.5
 */
 auto call_pseudo(any val, string meth, ...) {
    ReferenceHolder<QoreListNode> nargs(xsink);
    if (args->size() > 2)
        nargs = args->copyListFrom(2);
    return pseudo_classes_eval(val, meth->getBuffer(), *nargs, xsink);
}

//! calls a pseudo-method on the given value with arguments given as a list
/** @param val the value to call the pseudo-method on
    @param meth the string method name of the pseudo-method to call
    @param argv any other arguments to the method

    @return the return value of the pseudo-method

    @see Qore::zzz8valuezzz9 for the class hierarchy of pseudo-classes

    @note this function can be used for security to ensure that a given pseudo-method of the Qore::zzz8objectzzz9 class is called,
    as by default if a method of the same name is implemented by the object's class, the class method will be called instead.

    @since %Qore 0.8.8
 */
auto call_pseudo_args(any val, string meth, *softlist<auto> argv) {
    return pseudo_classes_eval(val, meth->getBuffer(), argv, xsink);
}

//! creates an object from the class name (which can include a namespace path) and optionally any arguments for the constructor
/** @par Example
    @code{.py}
object obj = create_object(class_name, arg1, arg2);
    @endcode

    @param class_name the name of the class to instantiate (can be prefixed with a namespace path)
    @param ... any other arguments to the constructor

    @return the object created

    @throw CREATE-OBJECT-ERROR unknown class, class implements features restricted by current parse options / sandboxing restrictions
    @throw CONSTRUCTOR-IS-PRIVATE attempt to instantiate an object with a private constructor from outside the class

    @note see the class documentation for the class to be instantiated for a list of additional exceptions that can be thrown

    @see create_object_args()

    @since %Qore 0.8.12
 */
object create_object(string class_name, ...) {
   return create_object_intern(class_name, 1, args, xsink);
}

//! creates an object from the class name (which can include a namespace path) and optionally any arguments for the constructor
/** @par Example
    @code{.py}
object obj = create_object_args(class_name, arg_list);
    @endcode

    @param class_name the name of the class to instantiate (can be prefixed with a namespace path)
    @param argv any other arguments to the constructor

    @return the object created

    @throw CREATE-OBJECT-ERROR unknown class, class implements features restricted by current parse options / sandboxing restrictions
    @throw CONSTRUCTOR-IS-PRIVATE attempt to instantiate an object with a private constructor from outside the class

    @note see the class documentation for the class to be instantiated for a list of additional exceptions that can be thrown

    @see create_object()

    @since %Qore 0.8.12
 */
object create_object_args(string class_name, *softlist<auto> argv) {
    return create_object_intern(class_name, 0, argv, xsink);
}
///@}
