/** Declare a method that can be called by the extensions. Add this macro
 *  to the private section of a stub.
 *  For example: NQQ_DECLARE_EXTENSION_METHOD(setValue)
 */
#define NQQ_DECLARE_EXTENSION_METHOD(method_name)                                                                      \
    StubReturnValue method_name(const QJsonArray&);                                                                    \
    bool __nqqmeta__##method_name = registerMethod(                                                                    \
        QString(#method_name), [&](const QJsonArray& args) -> Stub::StubReturnValue { return method_name(args); });

#define NQQ_DEFINE_EXTENSION_METHOD(class_name, method_name, args_name)                                                \
    Stub::StubReturnValue class_name::method_name(const QJsonArray& args_name)

/** Set the current stub name. Add this macro to the public section of a stub.
 *  For example: NQQ_STUB_NAME("Editor")
 */
#define NQQ_STUB_NAME(stub_name)                                                                                       \
    inline static QString stubName()                                                                                   \
    { return stub_name; }                                                                                              \
    inline QString stubName_() const                                                                                   \
    { return stubName(); }

#ifndef EXTENSIONS_STUBS_STUB_H
#define EXTENSIONS_STUBS_STUB_H

#include <QHash>
#include <QJsonArray>
#include <QJsonObject>
#include <QJsonValue>
#include <QMetaMethod>
#include <QObject>
#include <QVariant>
#include <QWeakPointer>

#include <functional>

namespace Extensions {

class RuntimeSupport;

namespace Stubs {

class Stub : public QObject {
    Q_OBJECT
public:
    explicit Stub(RuntimeSupport* rts);
    explicit Stub(const QWeakPointer<QObject>& object, RuntimeSupport* rts);
    explicit Stub(const QSharedPointer<QObject>& object, RuntimeSupport* rts);
    explicit Stub(QObject* object, RuntimeSupport* rts);
    virtual ~Stub() = 0;

    enum class ErrorCode {
        NONE = 0,
        INVALID_REQUEST = 1,
        INVALID_ARGUMENT_NUMBER = 2,
        INVALID_ARGUMENT_TYPE = 3,
        OBJECT_DEALLOCATED = 4,
        OBJECT_NOT_FOUND = 5,
        METHOD_NOT_FOUND = 6,
    };

    struct StubReturnValue {
        QJsonValue result;
        ErrorCode error = ErrorCode::NONE;
        QString errorString;

        StubReturnValue() {}
        StubReturnValue(const QJsonValue& _result)
            : result(_result)
        {
        }
        StubReturnValue(const ErrorCode& _error, const QString& _errorString = QString())
            : error(_error)
            , errorString(_errorString)
        {
        }
        StubReturnValue(const QJsonValue& _result, const ErrorCode& _error, const QString& _errorString = QString())
            : result(_result)
            , error(_error)
            , errorString(_errorString)
        {
        }

        QJsonObject toJsonObject()
        {
            QJsonObject ret;
            ret.insert("result", result.isUndefined() ? QJsonValue() : result);
            ret.insert("err", static_cast<int>(error));
            if (error != ErrorCode::NONE) {
                ret.insert("errStr", errorString);
            }
            return ret;
        }
    };

    enum class PointerType { DETACHED, WEAK_POINTER, SHARED_POINTER, UNMANAGED_POINTER };

    /**
     * @brief Call this method to know if the object referenced by this stub is still valid.
     * @return
     */
    virtual bool isAlive();

    /**
     * @brief Invoke a registered method. This method must have been registered with
     *        registerMethod(), or using the NQQ_DECLARE_EXTENSION_METHOD macro.
     * @param method method name
     * @param ret return value of the method
     * @param args arguments for the method
     * @return true if the method has been invoked, false otherwise.
     */
    bool invoke(const QString& method, StubReturnValue& ret, const QJsonArray& args);
    virtual QString stubName_() const = 0;

    QString convertToString(const QJsonValue& value);

    bool operator==(const Stub& other) const;
    bool operator!=(const Stub& other) const;
signals:

public slots:

protected:
    QWeakPointer<QObject> objectWeakPtr();
    QSharedPointer<QObject> objectSharedPtr();
    QObject* objectUnmanagedPtr();
    PointerType pointerType();

    bool registerMethod(const QString& methodName, std::function<StubReturnValue(const QJsonArray&)> method);
    RuntimeSupport* runtimeSupport();

private:
    RuntimeSupport* m_rts;
    PointerType m_pointerType;
    QWeakPointer<QObject> m_weakPointer;
    QSharedPointer<QObject> m_sharedPointer;
    QObject* m_unmanagedPointer = nullptr;
    QHash<QString, std::function<StubReturnValue(const QJsonArray&)>> m_methods;
    QVariant genericCall(QObject* object, QMetaMethod metaMethod, QVariantList args, ErrorCode& error);
    bool invokeOnRealObject(const QString& method, Stub::StubReturnValue& ret, const QJsonArray& args);
};

} // namespace Stubs
} // namespace Extensions

#endif // EXTENSIONS_STUBS_STUB_H
