/*
 *  Copyright (C) GridGain Systems. All Rights Reserved.
 *  _________        _____ __________________        _____
 *  __  ____/___________(_)______  /__  ____/______ ____(_)_______
 *  _  / __  __  ___/__  / _  __  / _  / __  _  __ `/__  / __  __ \
 *  / /_/ /  _  /    _  /  / /_/ /  / /_/ /  / /_/ / _  /  _  / / /
 *  \____/   /_/     /_/   \_,__/   \____/   \__,_/  /_/   /_/ /_/
 */

#pragma once

#include "ignite/client/compute/job_state.h"
#include "ignite/client/network/cluster_node.h"
#include "ignite/common/detail/config.h"
#include "ignite/common/ignite_result.h"
#include "ignite/common/binary_object.h"
#include "ignite/common/uuid.h"

#include <memory>

namespace ignite {

namespace detail {
class job_execution_impl;
}

/**
 * Job control object, provides information about the job execution process and result, allows cancelling the job.
 */
class job_execution {
public:
    /**
     * Job operation result.
     */
    enum class operation_result {
        /// The job operation was successfully complete.
        SUCCESS,

        /// The job has already finished.
        INVALID_STATE,

        /// The job was not found (no longer exists due to exceeding the retention time limit).
        NOT_FOUND
    };

    // Default
    job_execution() = default;

    /**
     * Constructor.
     *
     * @param impl Implementation.
     */
    explicit job_execution(std::shared_ptr<detail::job_execution_impl> impl)
        : m_impl(std::move(impl)) {}

    /**
     * Gets the job ID.
     *
     * @return Job ID.
     */
    [[nodiscard]] IGNITE_API uuid get_id() const;

    /**
     * Gets the cluster node.
     *
     * @return Cluster node.
     */
    [[nodiscard]] IGNITE_API const cluster_node &get_node() const;

    /**
     * Gets the job execution state asynchronously. Can be @c nullopt if the job state no longer exists due to
     * exceeding the retention time limit.
     *
     * @param callback Callback to be called when the operation is complete. Called with the job state.
     *  It Can be @c nullopt if the job state no longer exists due to exceeding the retention time limit.
     */
    IGNITE_API void get_state_async(ignite_callback<std::optional<job_state>> callback);

    /**
     * Gets the job execution state. Can be @c nullopt if the job state no longer exists due to exceeding the
     * retention time limit.
     *
     * @return The job state. Can be @c nullopt if the job state no longer exists due to exceeding the retention
     *  time limit.
     */
    IGNITE_API std::optional<job_state> get_state() {
        return sync<std::optional<job_state>>(
            [this](auto callback) mutable { get_state_async(std::move(callback)); });
    }

    /**
     * Gets the job execution result asynchronously.
     *
     * Only one callback can be submitted for this operation at a time, which means you cannot call this method in
     * parallel.
     * @param callback Callback to be called when the operation is complete. Called with the job execution result.
     */
    IGNITE_API void get_result_async(ignite_callback<std::optional<binary_object>> callback);

    /**
     * Gets the job execution result.
     *
     * Only one thread can wait for a result at a time, which means you cannot call this method in parallel from
     * multiple threads.
     * @return The job execution result.
     */
    IGNITE_API std::optional<binary_object> get_result() {
        return sync<std::optional<binary_object>>(
            [this](auto callback) mutable { get_result_async(std::move(callback)); });
    }

    /**
     * Cancels the job execution asynchronously.
     *
     * @param callback Callback to be called when the operation is complete. Called with the cancel result.
     */
    IGNITE_API void cancel_async(ignite_callback<operation_result> callback);

    /**
     * Cancels the job execution.
     *
     * @return Result of the cancel operation.
     */
    IGNITE_API operation_result cancel() {
        return sync<operation_result>([this](auto callback) mutable { cancel_async(std::move(callback)); });
    }

    /**
     * Changes the job priority asynchronously. After priority change, the job will be the last in the queue of jobs
     * with the same priority.
     *
     * @param priority New priority.
     * @param callback Callback to be called when the operation is complete. Called with the operation result.
     */
    IGNITE_API void change_priority_async(std::int32_t priority, ignite_callback<operation_result> callback);

    /**
     * Changes the job priority. After priority change, the job will be the last in the queue of jobs with the same
     * priority.
     *
     * @param priority New priority.
     * @return Result of the operation.
     */
    IGNITE_API operation_result change_priority(std::int32_t priority) {
        return sync<operation_result>(
            [this, priority](auto callback) mutable { change_priority_async(priority, std::move(callback)); });
    }

private:
    /** Implementation. */
    std::shared_ptr<detail::job_execution_impl> m_impl{};
};

} // namespace ignite
