/*
 *  SPDX-FileCopyrightText: 2011 Dmitry Kazakov <dimula73@gmail.com>
 *
 *  SPDX-License-Identifier: GPL-2.0-or-later
 */

#include "kis_saved_commands.h"

#include <QVector>

#include "kis_image_interfaces.h"
#include "kis_stroke_strategy_undo_command_based.h"
#include <KisAsynchronouslyMergeableCommandInterface.h>


KisSavedCommandBase::KisSavedCommandBase(const KUndo2MagicString &name,
                                         KisStrokesFacade *strokesFacade)
    : KUndo2Command(name),
      m_strokesFacade(strokesFacade),
      m_skipOneRedo(true)
{
}

KisSavedCommandBase::~KisSavedCommandBase()
{
}

KisStrokesFacade* KisSavedCommandBase::strokesFacade()
{
    return m_strokesFacade;
}

void KisSavedCommandBase::runStroke(bool undo)
{
    KisStrokeStrategyUndoCommandBased *strategy =
        new KisStrokeStrategyUndoCommandBased(text(), undo, 0);
    strategy->setUsedWhileUndoRedo(true);

    KisStrokeId id = m_strokesFacade->startStroke(strategy);
    addCommands(id, undo);
    m_strokesFacade->endStroke(id);
}

void KisSavedCommandBase::undo()
{

    runStroke(true);
}



void KisSavedCommandBase::redo()
{
    /**
     * All the commands are first executed in the stroke and then
     * added to the undo stack. It means that the first redo should be
     * skipped
     */

    if(m_skipOneRedo) {
        m_skipOneRedo = false;
        return;
    }

    runStroke(false);
}


KisSavedCommand::KisSavedCommand(KUndo2CommandSP command,
                                 KisStrokesFacade *strokesFacade)
    : KisSavedCommandBase(command->text(), strokesFacade),
      m_command(command)
{
}

int KisSavedCommand::id() const
{
    return m_command->id();
}

bool KisSavedCommand::mergeWith(const KUndo2Command* command)
{
    return unwrap(command, [this] (const KUndo2Command *cmd) {
        return m_command->mergeWith(cmd);
    });
}

bool KisSavedCommand::canAnnihilateWith(const KUndo2Command *command) const
{
    return unwrap(command, [this] (const KUndo2Command *cmd) {
        return m_command->canAnnihilateWith(cmd);
    });
}

void KisSavedCommand::addCommands(KisStrokeId id, bool undo)
{
    strokesFacade()->
        addJob(id, new KisStrokeStrategyUndoCommandBased::Data(m_command, undo));
}
int KisSavedCommand::timedId() const
{
    return m_command->timedId();
}
void KisSavedCommand::setTimedID(int timedID)
{
    m_command->setTimedID(timedID);
}

bool KisSavedCommand::timedMergeWith(KUndo2Command *other)
{
    /// Since we are saving the actual command inside another
    /// command, so we cannot unwrap the command here. Otherwise
    /// the shared pointer in `other` will destroy the merged
    /// command some time later.

    return m_command->timedMergeWith(other);
}
QVector<KUndo2Command*> KisSavedCommand::mergeCommandsVector() const
{
    return m_command->mergeCommandsVector();
}
void KisSavedCommand::setTime(const QTime &time)
{
    m_command->setTime(time);
}

QTime KisSavedCommand::time() const
{
    return m_command->time();
}
void KisSavedCommand::setEndTime(const QTime &time)
{
    m_command->setEndTime(time);
}

QTime KisSavedCommand::endTime() const
{
    return m_command->endTime();
}
bool KisSavedCommand::isMerged() const
{
    return m_command->isMerged();
}



struct KisSavedMacroCommand::Private
{
    struct SavedCommand {
        KUndo2CommandSP command;
        KisStrokeJobData::Sequentiality sequentiality;
        KisStrokeJobData::Exclusivity exclusivity;
    };

    QVector<SavedCommand> commands;
    int macroId = -1;

    const KisSavedMacroCommand *overriddenCommand = 0;
    QVector<const KUndo2Command*> skipWhenOverride;
};

KisSavedMacroCommand::KisSavedMacroCommand(const KUndo2MagicString &name,
                                           KisStrokesFacade *strokesFacade)
    : KisSavedCommandBase(name, strokesFacade),
      m_d(new Private())
{
}

KisSavedMacroCommand::~KisSavedMacroCommand()
{
    delete m_d;
}

void KisSavedMacroCommand::setMacroId(int value)
{
    m_d->macroId = value;
}

int KisSavedMacroCommand::id() const
{
    return m_d->macroId;
}

bool KisSavedMacroCommand::mergeWith(const KUndo2Command* command)
{
    const KisSavedMacroCommand *other =
        dynamic_cast<const KisSavedMacroCommand*>(command);

    if (!other || other->id() != id() || id() < 0 || other->id() < 0) return false;

    QVector<Private::SavedCommand> &otherCommands = other->m_d->commands;

    if (other->m_d->overriddenCommand == this) {
        m_d->commands.clear();

        Q_FOREACH (Private::SavedCommand cmd, other->m_d->commands) {
            if (!other->m_d->skipWhenOverride.contains(cmd.command.data())) {
                m_d->commands.append(cmd);
            }
        }

        if (other->extraData()) {
            setExtraData(other->extraData()->clone());
        } else {
            setExtraData(0);
        }
        return true;
    }

    if (m_d->commands.size() != otherCommands.size()) return false;

    auto it = m_d->commands.constBegin();
    auto end = m_d->commands.constEnd();

    auto otherIt = otherCommands.constBegin();
    auto otherEnd = otherCommands.constEnd();

    bool sameCommands = true;
    while (it != end && otherIt != otherEnd) {
        KisAsynchronouslyMergeableCommandInterface *iface1 =
            dynamic_cast<KisAsynchronouslyMergeableCommandInterface*>(it->command.data());

        if (!iface1 || !iface1->canMergeWith(otherIt->command.data()) ||
            it->command->id() < 0 || otherIt->command->id() < 0 ||
            it->command->id() != otherIt->command->id() ||
            it->sequentiality != otherIt->sequentiality ||
            it->exclusivity != otherIt->exclusivity) {

            sameCommands = false;
            break;
        }
        ++it;
        ++otherIt;
    }

    if (!sameCommands) return false;

    it = m_d->commands.constBegin();
    otherIt = otherCommands.constBegin();

    while (it != end && otherIt != otherEnd) {
        if (it->command->id() != -1) {
            bool result = it->command->mergeWith(otherIt->command.data());
            KIS_ASSERT_RECOVER(result) { return false; }
        }
        ++it;
        ++otherIt;
    }

    if (other->extraData()) {
        setExtraData(other->extraData()->clone());
    } else {
        setExtraData(0);
    }

    return true;
}

bool KisSavedMacroCommand::canAnnihilateWith(const KUndo2Command* command) const
{
    const KisSavedMacroCommand *other =
        dynamic_cast<const KisSavedMacroCommand*>(command);

    if (!other || other->id() != id() || id() < 0 || other->id() < 0) return false;

    QVector<Private::SavedCommand> &otherCommands = other->m_d->commands;

    if (other->m_d->overriddenCommand) return false;
    if (m_d->commands.size() != otherCommands.size()) return false;

    auto it = m_d->commands.constBegin();
    auto end = m_d->commands.constEnd();

    auto otherIt = otherCommands.constBegin();
    auto otherEnd = otherCommands.constEnd();

    bool sameCommands = true;
    while (it != end && otherIt != otherEnd) {

        if (!it->command->canAnnihilateWith(otherIt->command.data()) ||
            it->command->id() < 0 || otherIt->command->id() < 0 ||
            it->command->id() != otherIt->command->id() ||
            it->sequentiality != otherIt->sequentiality ||
            it->exclusivity != otherIt->exclusivity) {

            sameCommands = false;
            break;
        }
        ++it;
        ++otherIt;
    }

    if (!sameCommands) return false;

    return true;
}

void KisSavedMacroCommand::addCommand(KUndo2CommandSP command,
                                      KisStrokeJobData::Sequentiality sequentiality,
                                      KisStrokeJobData::Exclusivity exclusivity)
{
    Private::SavedCommand item;
    item.command = command;
    item.sequentiality = sequentiality;
    item.exclusivity = exclusivity;

    m_d->commands.append(item);
}

void KisSavedMacroCommand::getCommandExecutionJobs(QVector<KisStrokeJobData *> *jobs, bool undo, bool shouldGoToHistory) const
{
    QVector<Private::SavedCommand>::iterator it;

    if(!undo) {
        for(it = m_d->commands.begin(); it != m_d->commands.end(); it++) {
            *jobs << new KisStrokeStrategyUndoCommandBased::
                       Data(it->command,
                            undo,
                            it->sequentiality,
                            it->exclusivity,
                            shouldGoToHistory);
        }
    }
    else {
        for(it = m_d->commands.end(); it != m_d->commands.begin();) {
            --it;

            *jobs << new KisStrokeStrategyUndoCommandBased::
                     Data(it->command,
                          undo,
                          it->sequentiality,
                          it->exclusivity,
                          shouldGoToHistory);
        }
    }
}

void KisSavedMacroCommand::setOverrideInfo(const KisSavedMacroCommand *overriddenCommand, const QVector<const KUndo2Command*> &skipWhileOverride)
{
    m_d->overriddenCommand = overriddenCommand;
    m_d->skipWhenOverride = skipWhileOverride;
}

void KisSavedMacroCommand::addCommands(KisStrokeId id, bool undo)
{
    QVector<KisStrokeJobData *> jobs;
    getCommandExecutionJobs(&jobs, undo);

    Q_FOREACH (KisStrokeJobData *job, jobs) {
        strokesFacade()->addJob(id, job);
    }
}
