/*******************************************************************************
 *
 * SPDX-FileCopyrightText: 2023-2025 Open Mobile Platform LLC <community@omp.ru>
 * SPDX-License-Identifier: MIT
 *
 ******************************************************************************/

#ifndef RM_LIB_RUNTIME_DISPATCHER_H
#define RM_LIB_RUNTIME_DISPATCHER_H

#include "error.h"
#include "global.h"

#include <QObject>
#include <functional>

class QJsonObject;
/**
 * @brief API of the Runtime Manager
 * @namespace RuntimeManager
 */
namespace RuntimeManager {

class RuntimeDispatcherPrivate;

/**
 * @brief Dispatcher of application's tasks and intents.
 * @class RuntimeDispatcher runtime_dispatcher.h <RuntimeManager/RuntimeDispatcher>
 *
 * The RuntimeDispatcher is a singleton class which applications can use to
 * register themselves as handlers for specific activities or tasks (which in
 * the API as referred to as "intents"). These actions can be requested by
 * other applications, and provide a way for applications to exchange data
 * between each other.
 *
 * The system defines a set of standard intents, such as "Start" and "OpenURI".
 * More information on the standard intents can be found in
 * @ref standard-intents "Standard intents".
 *
 * Applications can register arbitrary intents as long as their name starts
 * with an "X-".
 *
 * If an application is able to handle intents, this should be declared in the
 * manifest file.  Here's how it would be specified in a .desktop file
 * [X-Application] section:
 *
 *      Intents=<intent-name>[/<detail>]:<flags-separated by "|">;<intent-name>...
 *      # Example:
 *      Intents=X-TextToSpeech;X-SpeechToText
 *
 * Intent registration example:
 *
 * @code
 *  auto *dispatcher = RuntimeManager::RuntimeDispatcher::instance();
 *  dispatcher->registerIntent(QStringLiteral("X-MyIntent"),
 *                             [&dispatcher](const QJsonObject &params,
 *                                           const RuntimeDispatcher::HandlerCb &reply) {
 *      // Insert handler logic here
 *      QJsonObject response = { ... };
 *      reply(response);
 *  });
 *
 * @endcode
 *
 * Now, after all important intent registration stuff done, DBus services can be registered.
 *
 * @code
 *  Error error = handler->startService();
 *  if (error)
 *      return EXIT_FAILURE;
 * @endcode
 */
class RM_EXPORT RuntimeDispatcher: public QObject
{
    Q_OBJECT

public:
    static RuntimeDispatcher *instance();

    /**
     * @brief Get the application's ID
     *
     * The application ID is the same for all processes (including different
     * instances and background tasks) of the application, and is constant
     * across all invocations of the application. It's typically built in the
     * form "<organization-name>.<application-name>", though developers are
     * invited not to assume any specific formatting.
     *
     * @return The calling process's application ID
     */
    QString applicationId() const;

    /**
     * @brief Get the application's instance ID
     *
     * The application instance ID identifies a specific instance of the
     * application. It's not constant across different invocations of the
     * application.
     *
     * @return The calling process's application instance ID, or an empty
     *         string if called from a background task.
     */
    QString applicationInstanceId() const;

    /**
     * @brief Get the background task's ID
     *
     * The task ID is an identifier for a background task, constant across
     * different invocations (note that only a single instance of a background
     * task can be running at any given time).
     *
     * @return the task ID, or an empty string if not called from a task.
     */
    QString taskId() const;

    /**
     * @brief Reply callback
     *
     * The intent handler receives this callback as a parameter, and it must
     * invoke it when it has finished processing the intent. If there is no
     * data to be sent back, pass an empty QJsonObject.
     */
    using HandlerCb = std::function<void(const QJsonObject &reply)>;

    /**
     * @brief Type for function that handles the intent.
     *
     * This handler will be invoked when another applications invokes the
     * intent. The handler will be invoked in the main application thread; note
     * that the application must process the event loop in order for this
     * callback to be invoked.
     *
     * @param params A JSON object with the data to be processed. Its semantics
     *        are specific to the intent being invoked.
     * @param callback A function to be called when the handler has finished
     *        processing the intent.
     */
    using CustomHandler = std::function<void(const QJsonObject &params,
                                             const HandlerCb &callback)>;
    /**
     * @brief Register an intent handler
     *
     * @param intentName The name of the intent
     * @param customHandler Function to execute on intent invocation
     */
    void registerIntent(const QString &intentName,
                        const CustomHandler &customHandler);

    /**
     * @brief Unregister an intent handler
     *
     * @param intentName The name of the intent to be unregistered
     */
    void unregisterIntent(const QString &intentName);

    /**
     * @brief Allows an intent to return file objects
     *
     * This method lets applications send files in intent replies: it registers
     * two sets of rules. \a readFilePaths and \a writeFilePaths, to identify
     * which elements in the JSON reply are file paths that should be made
     * available to the caller for reading and for writing, respectively.
     *
     * This method should be called at the time an intent gets registered.
     * Failing to do so will still allow the intent caller to receive the
     * replies, but then the file paths mentioned in the reply will be passed
     * as-is, without actually ensuring that these files are made available to
     * the receiving process.
     *
     * @param intentName The name of the intent
     * @param readFilePaths List of JSON paths identifying files that should be
     *        made available in read-only mode.
     * @param writeFilePaths List of JSON paths identifying files that should
     *        be made available in read-write mode.
     *
     * @note Supported JSON paths are a small subset of those described in the
     *       JSONPath RFC 9535 (https://datatracker.ietf.org/doc/html/rfc9535):
     *       "key", "key.subkey", "array[index]", "key.*", "array[*]" and
     *       trivial combinations of these.
     */
    void registerIntentOutputFiles(const QString &intentName,
                                   const QStringList &readFilePaths,
                                   const QStringList &writeFilePaths);

    /**
     * @brief Start the intent handler service
     *
     * Completes the initialization of the RuntimeDispatcher and make this
     * application ready to receive intent requests.
     */
    Q_INVOKABLE Error startService();

    /**
     * @brief Stop the intent handler service
     *
     * Stops processing intent requests. Normally it's not necessary to call
     * this method when the application terminates, because in that case the
     * service is automatically unregistered.
     */
    Q_INVOKABLE Error stopService();

    /**
     * @brief Startup callback
     *
     * This function should contain the code to be execute when the application
     * is started.
     *
     * @sa onApplicationStarted
     */
    using ApplicationStartedCb = std::function<void()>;
    /**
     * @brief Register a startup callback
     *
     * Register a function to be called when the application process is started
     * as an ordinary applications (and not as a background task). Using this
     * method can be useful when the same binary executable is used to run both
     * the application and its background tasks.
     * @param callback The code to be executed when the application is run.
     *
     * @note The callback might be invoked immediately, even before this method
     *       returns.
     *
     * @sa onTaskStarted
     */
    void onApplicationStarted(const ApplicationStartedCb &callback);

    /**
     * @brief Background task startup callback
     *
     * This function should contain the code to be executed by the background
     * task.
     * @param taskId The ID of the task being executed.
     *
     * @sa onTaskStarted
     */
    using TaskStartedCb = std::function<void(const QString &taskId)>;
    /**
     * @brief Register a task startup callback
     *
     * Register a function to be called when the application process is started
     * as a background task (and not as an ordinary application). Using this
     * method can be useful when the same binary executable is used to run both
     * the application and its background tasks.
     *
     * @param taskId The ID of the task. A different callback can be specified
     *        for each task.
     * @param callback The code to be executed when the task is run.
     *
     * @note The callback might be invoked immediately, even before this method
     *       returns.
     *
     * @sa onApplicationStarted
     */
    void onTaskStarted(const QString &taskId, const TaskStartedCb &callback);

    /**
     * @brief Get the socket descriptor of the task
     *
     * This method can only be called from a background task, and returns the
     * file descriptor that can be used to communicate from the application (if
     * the application requested one via Task::withIpcSocket()). While
     * developers are free to use this socket using QLocalSocket or other APIs,
     * it's recommended to use it to construct a TaskMessaging object:
     *
     * @code
     *  using namespace RuntimeManager;
     *  auto *dispatcher = RuntimeDispatcher::instance();
     *  TaskMessaging ipc(dispatcher->localServerSocket());
     *
     *  // Use the TaskMessaging object...
     *  QObject::connect(&ipc, &TaskMessaging::messageReceived,
     *                   [](const QJsonMessage &message) {
     *      qDebug() << "Received message from the app:" << message;
     *  }
     *
     * @endcode
     *
     */
    qintptr localServerSocket() const;

protected:
    RuntimeDispatcher();
    ~RuntimeDispatcher() override;

private:
    Q_DECLARE_PRIVATE(RuntimeDispatcher)
    QScopedPointer<RuntimeDispatcherPrivate> d_ptr;
};

} // namespace RuntimeManager

#endif // RM_LIB_RUNTIME_DISPATCHER_H
