#include "projectcommands.h"

#include "uisocket.h"
#include "mainwindow.h"

#include <QDebug>
#include <QUndoStack>

using Xybrid::Data::Project;
using Xybrid::Data::Pattern;
using namespace Xybrid::Editing;

bool ProjectCommand::commit() {
    if (!project->socket || !project->socket->undoStack) return cancel();
    project->socket->undoStack->push(this);
    return true;
}
bool ProjectCommand::cancel() {
    delete this;
    return false;
}

ProjectSequencerDeltaCommand::ProjectSequencerDeltaCommand(const std::shared_ptr<Project>& project_) {
    project = project_;
    oldSeq = project->sequence;
    oldSeqSel = project->socket->window->sequenceSelection();
    seq = oldSeq;
    seqSel = oldSeqSel;
    setText("edit sequence");
}

bool ProjectSequencerDeltaCommand::mergeWith(const QUndoCommand* o_) {
    if (o_->id() != id()) return false;
    auto* o = static_cast<const ProjectSequencerDeltaCommand*>(o_);
    if (o->project != project) return false;
    seq = o->seq;
    seqSel = o->seqSel;
    return true;
}

void ProjectSequencerDeltaCommand::redo() {
    project->sequence = seq;
    emit project->socket->updatePatternLists();
    project->socket->window->sequenceSelection(seqSel);
}

void ProjectSequencerDeltaCommand::undo() {
    project->sequence = oldSeq;
    emit project->socket->updatePatternLists();
    project->socket->window->sequenceSelection(oldSeqSel);
}

ProjectPatternMoveCommand::ProjectPatternMoveCommand(const std::shared_ptr<Project>& project_, int f, int t) {
    project = project_;
    from = f;
    to = t;
    setText("move pattern");
}

bool ProjectPatternMoveCommand::mergeWith(const QUndoCommand* o_) {
    if (o_->id() != id()) return false;
    auto* o = static_cast<const ProjectPatternMoveCommand*>(o_);
    if (o->project != project) return false;
    to = o->to;
    return true;
}

void ProjectPatternMoveCommand::redo() {
    bool move = project->socket->window->patternSelection() == from && project->socket->window->sequenceSelection() == -1;
    auto p = project->patterns[static_cast<size_t>(from)];
    project->patterns.erase(project->patterns.begin() + from);
    project->patterns.insert(project->patterns.begin() + to, p);
    project->updatePatternIndices();
    emit project->socket->updatePatternLists();
    if (move) project->socket->window->patternSelection(to);
}

void ProjectPatternMoveCommand::undo() {
    bool move = project->socket->window->patternSelection() == to && project->socket->window->sequenceSelection() == -1;
    auto p = project->patterns[static_cast<size_t>(to)];
    project->patterns.erase(project->patterns.begin() + to);
    project->patterns.insert(project->patterns.begin() + from, p);
    project->updatePatternIndices();
    emit project->socket->updatePatternLists();
    if (move) project->socket->window->patternSelection(from);
}

ProjectPatternAddCommand::ProjectPatternAddCommand(const std::shared_ptr<Project>& project_, int at, int atSeq, const std::shared_ptr<Pattern>& copyOf) {
    project = project_;
    if (at < 0) at = std::numeric_limits<int>::max();
    this->at = std::max(0, std::min(at, static_cast<int>(project->patterns.size())));
    this->atSeq = atSeq;
    this->copyOf = copyOf;
    if (copyOf) setText("duplicate pattern");
    else setText("add pattern");
}

void ProjectPatternAddCommand::redo() {
    auto np = project->newPattern(static_cast<size_t>(at));
    if (copyOf) *np = *copyOf;
    project->updatePatternIndices();
    if (atSeq > -1) {
        project->sequence.insert(project->sequence.begin() + atSeq, np);
        emit project->socket->updatePatternLists();
        project->socket->window->patternSelection(at);
        project->socket->window->sequenceSelection(atSeq);
    } else {
        emit project->socket->updatePatternLists();
        project->socket->window->patternSelection(at);
    }
}

void ProjectPatternAddCommand::undo() {
    project->removePattern(project->patterns[static_cast<size_t>(at)].get()); // ???
    emit project->socket->updatePatternLists();
    if (atSeq > -1) project->socket->window->sequenceSelection(std::max(0, atSeq - 1));
    else if (copyOf) project->socket->window->patternSelection(static_cast<int>(copyOf->index));
}

ProjectPatternDeleteCommand::ProjectPatternDeleteCommand(const std::shared_ptr<Project>& project_, const std::shared_ptr<Pattern>& pattern_) {
    project = project_;
    pattern = pattern_;
    setText("delete pattern");

    oldSeq = project->sequence;
    oldSeqSel = project->socket->window->sequenceSelection();
}

void ProjectPatternDeleteCommand::redo() {
    int seqSel = oldSeqSel;
    for (int i = 0; i <= oldSeqSel; i++) if (seqSel > 0 && project->sequence[static_cast<size_t>(i)].pattern() == pattern) seqSel--;
    project->sequence.erase(std::remove(project->sequence.begin(), project->sequence.end(), pattern), project->sequence.end());
    project->patterns.erase(project->patterns.begin() + static_cast<ptrdiff_t>(pattern->index));
    project->updatePatternIndices();
    emit project->socket->updatePatternLists();
    project->socket->window->sequenceSelection(seqSel);
}

void ProjectPatternDeleteCommand::undo() {
    project->patterns.insert(project->patterns.begin() + static_cast<ptrdiff_t>(pattern->index), pattern);
    project->updatePatternIndices();
    project->sequence = oldSeq;
    emit project->socket->updatePatternLists();
    project->socket->window->sequenceSelection(oldSeqSel);
}
