/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */

/*
    Sonic Visualiser
    An audio file viewer and annotation editor.
    Centre for Digital Music, Queen Mary, University of London.
    This file copyright 2006 Chris Cannam.
    
    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License as
    published by the Free Software Foundation; either version 2 of the
    License, or (at your option) any later version.  See the file
    COPYING included with this distribution for more information.
*/

#ifndef SV_EVENT_COMMANDS_H
#define SV_EVENT_COMMANDS_H

#include "base/Event.h"
#include "base/Command.h"

/**
 * Interface for classes that can be modified through these commands
 */
class EventEditable
{
public:
    virtual void add(Event e) = 0;
    virtual void remove(Event e) = 0;
};

/**
 * Command to add an event to an editable containing events, with undo.
 */
class AddEventCommand : public Command
{
public:
    AddEventCommand(EventEditable *editable, const Event &e, QString name) :
        m_editable(editable), m_event(e), m_name(name) { }

    QString getName() const override { return m_name; }
    Event getEvent() const { return m_event; }

    void execute() override { m_editable->add(m_event); }
    void unexecute() override { m_editable->remove(m_event); }

private:
    EventEditable *m_editable;
    Event m_event;
    QString m_name;
};

/**
 * Command to remove an event from an editable containing events, with
 * undo.
 */
class RemoveEventCommand : public Command
{
public:
    RemoveEventCommand(EventEditable *editable, const Event &e, QString name) :
        m_editable(editable), m_event(e), m_name(name) { }

    QString getName() const override { return m_name; }
    Event getEvent() const { return m_event; }

    void execute() override { m_editable->remove(m_event); }
    void unexecute() override { m_editable->add(m_event); }

private:
    EventEditable *m_editable;
    Event m_event;
    QString m_name;
};

/**
 * Command to add or remove a series of events to or from an editable,
 * with undo. Creates and immediately executes a sub-command for each
 * add/remove requested. Consecutive add/remove pairs for the same
 * point are collapsed.
 */
class ChangeEventsCommand : public MacroCommand
{
public:
    ChangeEventsCommand(EventEditable *editable, QString name) :
        MacroCommand(name), m_editable(editable) { }

    void add(Event e) {
        addCommand(new AddEventCommand(m_editable, e, getName()), true);
    }
    void remove(Event e) {
        addCommand(new RemoveEventCommand(m_editable, e, getName()), true);
    }

    /**
     * Stack an arbitrary other command in the same sequence.
     */
    void addCommand(Command *command) override { addCommand(command, true); }

    /**
     * If any points have been added or deleted, return this
     * command (so the caller can add it to the command history).
     * Otherwise delete the command and return NULL.
     */
    ChangeEventsCommand *finish() {
        if (!m_commands.empty()) {
            return this;
        } else {
            delete this;
            return nullptr;
        }
    }

protected:
    virtual void addCommand(Command *command, bool executeFirst) {
        
        if (executeFirst) command->execute();

        if (m_commands.empty()) {
            MacroCommand::addCommand(command);
            return;
        }
        
        RemoveEventCommand *r =
            dynamic_cast<RemoveEventCommand *>(command);
        AddEventCommand *a =
            dynamic_cast<AddEventCommand *>(*m_commands.rbegin());
        if (r && a) {
            if (a->getEvent() == r->getEvent()) {
                deleteCommand(a);
                return;
            }
        }
        
        MacroCommand::addCommand(command);
    }

    EventEditable *m_editable;
};

#endif
