/***************************************************************************
     SelectionTracker.h  -  tracker for selection changes
                             -------------------
    begin                : Tue Feb 25 2014
    copyright            : (C) 2014 by Thomas Eschenbacher
    email                : Thomas.Eschenbacher@gmx.de
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

#ifndef SELECTION_TRACKER_H
#define SELECTION_TRACKER_H

#include "config.h"

#include <QtGlobal>
#include <QList>
#include <QObject>
#include <QPointer>
#include <QUuid>

#include "libkwave/Sample.h"
#include "libkwave/SignalManager.h"
#include "libkwave/undo/UndoAction.h"
#include "libkwave/undo/UndoHandler.h"

namespace Kwave
{

    class Track;
    class UndoTransaction;

    class Q_DECL_EXPORT SelectionTracker: public Kwave::UndoHandler
    {
	Q_OBJECT
    public:
	/**
	 * Constructor
	 * @param signal the signal manager to track
	 * @param offset index of the first selected sample
	 * @param length number of selected samples
	 * @param tracks list of selected tracks or null pointer for "all"
	 */
	SelectionTracker(Kwave::SignalManager *signal,
	                 sample_index_t offset,
	                 sample_index_t length,
	                 const QList<unsigned int> *tracks);

	/** Destructor */
	virtual ~SelectionTracker();

	/**
	 * Returns all currently selected tracks
	 */
	QList<QUuid> allTracks();

	/**
	 * changes the selected range covered by the cache
	 * @internal used during undo/redo
	 * @param tracks list of selected tracks
	 * @param offset index of the first selected sample
	 * @param length number of selected samples
	 */
	void selectRange(QList<QUuid> tracks,
	                 sample_index_t offset, sample_index_t length);

	/**
	 * Returns the start of the selected range
	 * @return the first sample index
	 */
	sample_index_t offset() const { return m_offset; }

	/**
	 * Returns the length of the selected range
	 * @return number of selected samples
	 */
	sample_index_t length() const { return m_length; }

	/**
	 * Returns the start of the selected range
	 * @see offset()
	 * @return the first sample index
	 */
	sample_index_t first() const { return m_offset; }

	/**
	 * Returns the end of the selected range
	 * @return the last sample index
	 */
	sample_index_t last() const {
	    return m_offset + ((m_length) ? (m_length - 1) : 0);
	}

    signals:

	/**
	 * Signals that a track has been inserted.
	 * @param uuid unique ID of the track
	 */
	void sigTrackInserted(const QUuid &uuid);

	/**
	 * Signals that a track has been deleted.
	 * @param uuid unique ID of the track
	 */
	void sigTrackDeleted(const QUuid &uuid);

	/**
	 * signals that the offset of the selection has changed
	 * @param offset new index of the selected area [samples]
	 */
	void sigOffsetChanged(sample_index_t offset);

	/**
	 * signals that the length of the selection has changed
	 * @param length new length of the selection [samples]
	 */
	void sigLengthChanged(sample_index_t length);

	/**
	 * signals that a range of samples has become invalid
	 * @param track UUID of the track or null for "all tracks"
	 * @param first index of the first invalidated sample
	 * @param last index of the last invalidated sample
	 */
	void sigInvalidated(const QUuid *track,
	                    sample_index_t first,
	                    sample_index_t last);

    private slots:

	/**
	 * Connected to the signal's sigTrackInserted.
	 * @param index the index [0...tracks()-1] of the inserted track
	 * @param track pointer to the track instance
	 * @see Signal::sigTrackInserted
	 * @internal
	 */
	void slotTrackInserted(unsigned int index, Kwave::Track *track);

	/**
	 * Connected to the signal's sigTrackInserted.
	 * @param index the index of the inserted track
	 * @param track pointer to the track instance
	 * @see Signal::sigTrackDeleted
	 * @internal
	 */
	void slotTrackDeleted(unsigned int index, Kwave::Track *track);

	/**
	 * Connected to the signal's sigSamplesInserted.
	 * @param track index of the source track [0...tracks-1]
	 * @param offset position from which the data was inserted
	 * @param length number of samples inserted
	 * @see Signal::sigSamplesInserted
	 * @internal
	 */
	void slotSamplesInserted(unsigned int track, sample_index_t offset,
	                         sample_index_t length);

	/**
	 * Connected to the signal's sigSamplesDeleted.
	 * @param track index of the source track [0...tracks-1]
	 * @param offset position from which the data was removed
	 * @param length number of samples deleted
	 * @see Signal::sigSamplesDeleted
	 * @internal
	 */
	void slotSamplesDeleted(unsigned int track, sample_index_t offset,
	                        sample_index_t length);

	/**
	 * Connected to the signal's sigSamplesModified
	 * @param track index of the source track [0...tracks-1]
	 * @param offset position from which the data was modified
	 * @param length number of samples modified
	 * @see Signal::sigSamplesModified
	 * @internal
	 */
	void slotSamplesModified(unsigned int track, sample_index_t offset,
	                         sample_index_t length);

    protected:

	/**
	 * Called by an undo manager to notify us that it is time to
	 * save data for undo.
	 *
	 * @param undo an undo transaction to append some undo data
	 * @retval true if successful
	 * @retval false if saving undo data failed, e.g. out of memory
	 *               or aborted
	 */
	virtual bool saveUndoData(Kwave::UndoTransaction &undo);

    private:

	/**
	 * Undo action for tracking selection changes
	 */
	class Q_DECL_EXPORT Undo: public Kwave::UndoAction
	{
	public:

	    /**
	     * Constructor
	     * @param selection pointer to the corresponding selection tracker
	     */
	    explicit Undo(Kwave::SelectionTracker *selection);

	    /** Destructor */
	    virtual ~Undo();

	    /**
	     * Returns a verbose short description of the action.
	     */
	    virtual QString description();

	    /**
	     * Returns the required amount of memory that is needed for storing
	     * undo data for the operation. This will be called to determine the
	     * free memory to be reserved.
	     * @note this is the first step (after the constructor)
	     */
	    virtual qint64 undoSize();

	    /**
	     * Returns the difference of needed memory that is needed for
	     * redo.
	     */
	    virtual qint64 redoSize();

	    /**
	     * Stores the data needed for undo.
	     * @param manager the SignalManager for modifying the signal
	     * @note this is the second step, after size() has been called
	     * @return true if successful, false if failed (e.g. out of memory)
	     */
	    virtual bool store(Kwave::SignalManager &manager);

	    /**
	     * Takes back an action by creating a new undo action (for further
	     * redo) and restoring the previous state.
	     * @param manager the SignalManager for modifying the signal
	     * @param with_redo if true a UndoAction for redo will be created
	     * @note The return value is allowed to be the same object. This
	     *       is useful for objects that can re-use their data for
	     *       undo/redo. You have to check for this when deleting an
	     *       UndoAction object after undo.
	     */
	    virtual Kwave::UndoAction *undo(Kwave::SignalManager &manager,
	                                    bool with_redo);

	    /**
	     * This undo action does not contribute to the modification
	     * of the signal.
	     * @return true always
	     */
	    virtual bool containsModification() const { return false; }

	    /** dump, for debugging purposes */
	    virtual void dump(const QString &indent) {
		qDebug("%s%s", DBG(indent), DBG(description()));
	    }

	private:

	    /** pointer to the overview cache */
	    QPointer<Kwave::SelectionTracker> m_tracker;

	    /** list of selected tracks */
	    QList<QUuid> m_tracks;

	    /** start of the selection, first sample */
	    sample_index_t m_offset;

	    /** number of selected samples */
	    sample_index_t m_length;

	};

    private:

	/** signal with the data to be shown */
	QPointer<Kwave::SignalManager> m_signal;

	/** first sample index in the source */
	sample_index_t m_offset;

	/** length of the source in samples, or zero for "whole signal" */
	sample_index_t m_length;

	/** list of currently selected source tracks */
	QList<QUuid> m_tracks;

	/** if true, track the selection only, otherwise the whole signal */
	bool m_selection_only;

	/** mutex for threadsafe access to the selection */
	QMutex m_lock;
    };
}

#endif /* SELECTION_TRACKER_H */
