// This file was generated automatically by Qleany's generator, edit at your own risk! 
// If you do, be careful to not overwrite it when you run the generator again.
#include "undo_redo/undo_redo_command.h"
#include "QtConcurrent/qtconcurrentrun.h"

#include <QFuture>

using namespace {{ application_cpp_domain_name }}::Controller::UndoRedo;

/*!
 * \brief Constructs an UndoRedoCommand with the specified \a text.
 */
UndoRedoCommand::UndoRedoCommand(const QString &text) : QObject(nullptr), m_text(text), m_status(Status::Waiting)
{
    m_progressTimer = new QTimer(this);
    m_progressTimer->setInterval(100);
    connect(m_progressTimer, &QTimer::timeout, this, &UndoRedoCommand::progressTimerTimeout);

    m_watcher = new QFutureWatcher<Result<void>>(this);
    connect(m_watcher, &QFutureWatcher<Result<void>>::finished, this, &UndoRedoCommand::onFinished);
    connect(m_watcher, &QFutureWatcher<Result<void>>::finished, this, &UndoRedoCommand::progressFinished);
    connect(m_watcher, &QFutureWatcher<Result<void>>::progressRangeChanged, this,
            &UndoRedoCommand::progressRangeChanged);
    connect(m_watcher, &QFutureWatcher<Result<void>>::progressTextChanged, this, &UndoRedoCommand::progressTextChanged);
    connect(m_watcher, &QFutureWatcher<Result<void>>::progressValueChanged, this,
            &UndoRedoCommand::progressValueChanged);
}

void UndoRedoCommand::setUndoFunction(const std::function<Result<void>()> &function)
{
    m_undoFunction = function;
}

void UndoRedoCommand::setRedoFunction(const std::function<void(QPromise<Result<void>> &)> &function)
{
    m_redoFunction = function;
}

void UndoRedoCommand::setMergeWithFunction(const std::function<bool(const UndoRedoCommand *)> &function)
{
    m_mergeWithFunction = function;
}

/*!
 * \brief Constructs an UndoRedoCommand with the specified \a text.
 */
void UndoRedoCommand::asyncUndo()
{
    if (!m_undoFunction)
    {
        throw std::runtime_error("No undo function set");
    }

    m_status = Status::Running;
    Q_EMIT undoing(m_scope, true);
    m_watcher->setFuture(QtConcurrent::run(m_undoFunction));
}

/*!
 * \brief Performs a redo operation asynchronously.
 */
void UndoRedoCommand::asyncRedo()
{
    m_status = Status::Running;
    Q_EMIT redoing(m_scope, true);
    m_watcher->setFuture(QtConcurrent::run(m_redoFunction).onFailed([](const std::exception &e) {
        return Result<void>(QLN_ERROR_2(
            Q_FUNC_INFO, Error::Critical, "redo-error",
            QString(QString::fromLatin1("Redo failed: ") + QString::fromStdString(e.what())).toLatin1().constData()));
    }));

    // start timer to update progress
    m_startTime = QDateTime::currentDateTime();
    m_progressTimer->start();
}

/*!
 * \brief Returns true if the command is currently running, otherwise false.
 */
bool UndoRedoCommand::isRunning() const
{
    return m_status == Status::Running;
}

bool UndoRedoCommand::isWaiting() const
{
    return m_status == Status::Waiting;
}

bool UndoRedoCommand::isFinished() const
{
    return m_status == Status::Finished;
}

/*!
 * \brief Handles the finished signal from the asynchronous undo or redo
 * operation.
 */
void UndoRedoCommand::onFinished()
{
    m_progressTimer->stop();

    Result<void> result = m_watcher->result();

    if (result.hasError())
    {
        this->setObsolete(true);

        if (result.error().status() == Error::Warning)
        {
            Q_EMIT warningSent(result.error());
        }
        else
        {
            Q_EMIT errorSent(result.error());
        }
    }
    m_status = Status::Finished;
    Q_EMIT redoing(m_scope, false);
    Q_EMIT undoing(m_scope, false);
    Q_EMIT progressFinished();
    Q_EMIT finished(result.isOk());
}

void UndoRedoCommand::progressTimerTimeout()
{
    if ((m_progressMinimumDuration >= 0) &&
        (m_startTime.msecsTo(QDateTime::currentDateTime()) > m_progressMinimumDuration))
    {
        Q_EMIT progressStarted();
        m_progressTimer->stop();
    }
}

QUuid UndoRedoCommand::stackId() const
{
    return m_stackId;
}

void UndoRedoCommand::setStackId(const QUuid &newStackId)
{
    m_stackId = newStackId;
}

bool UndoRedoCommand::isAlterCommand() const
{
    return m_type == Type::AlterCommand;
}

bool UndoRedoCommand::isQueryCommand() const
{
    return m_type == Type::QueryCommand;
}

/*!
 * \brief Set the minimum duration of the command before the progress bar must
 * be shown.
 * \param minimumDuration The minimum duration in milliseconds. -1 to disable. 0
 * to always show. Default is 500.
 */
void UndoRedoCommand::setProgressMinimumDuration(int minimumDuration)
{
    m_progressMinimumDuration = minimumDuration;
}

/*!
 * \brief UndoRedoCommand::progressMinimumDuration
 * \return
 */
int UndoRedoCommand::progressMinimumDuration() const
{
    return m_progressMinimumDuration;
}

UndoRedoCommand::Type UndoRedoCommand::type() const
{
    return m_type;
}

void UndoRedoCommand::setType(Type newType)
{
    m_type = newType;
}

bool UndoRedoCommand::isSystem() const
{
    return m_isSystem;
}

void UndoRedoCommand::setIsSystem(bool newIsSystem)
{
    m_isSystem = newIsSystem;
}

/*!
 * \brief Returns true if the command is obsolete, otherwise false.
 * The command will then be deleted from the stack.
 */
bool UndoRedoCommand::obsolete() const
{
    return m_obsolete;
}

/*!
 * \brief Sets the obsolete status of the command to \a newObsolete.
 */
void UndoRedoCommand::setObsolete(bool newObsolete)
{
    m_obsolete = newObsolete;
}

/*!
 * \brief Merge with another command. Redo of current command must becomes the
 * equivalent of both redoes. Same for
 * undoes.  Returns true if the command can be merged with \a other, otherwise
 * false. To be implemented if needed.
 */
bool UndoRedoCommand::mergeWith(const UndoRedoCommand *other)
{
    return false;
}

/*!
 * \brief Returns the text of the command.
 */
QString UndoRedoCommand::text() const
{
    return m_text;
}

/*!
 * \brief Sets the text of the command to \a newText.
 */
void UndoRedoCommand::setText(const QString &newText)
{
    m_text = newText;
}

/*!
 * \brief Returns the scope of the command.
 */
Scope UndoRedoCommand::scope() const
{
    return m_scope;
}

/*!
 * \brief Sets the scope of the command to \a newScope.
 */
void UndoRedoCommand::setScope(Scope newScope)
{
    m_scope = newScope;
}
