/***************************************************************************
    kwave/FileContext.h  -  Context of a Loaded File
                             -------------------
    begin                : 2009-12-31
    copyright            : (C) 2009 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 KWAVE_FILE_CONTEXT_H
#define KWAVE_FILE_CONTEXT_H

#include "config.h"

#include <QtGlobal>
#include <QAtomicInt>
#include <QElapsedTimer>
#include <QList>
#include <QObject>
#include <QPointer>
#include <QString>
#include <QTimer>
#include <QUrl>

#include "libkwave/MetaDataList.h"
#include "libkwave/Sample.h"

class QApplication;
class QMdiSubWindow;
class QSize;
class QTextStream;
class QWidget;

class QUrl;

namespace Kwave
{

    class App;
    class MainWidget;
    class Parser;
    class PluginManager;
    class SignalManager;
    class TopWidget;
    class Zoomable;

    class Q_DECL_EXPORT FileContext: public QObject
    {
        Q_OBJECT
    public:
        /**
         * Constructor. Creates a new toplevel window, signal manager,
         * plugin manager and so on.
         * @param app reference to the Kwave application
         */
        explicit FileContext(Kwave::App &app);

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

        /**
         * initializes the instance
         * @param top_widget pointer to the toplevel widget
         * @return true if successful
         */
        bool init(Kwave::TopWidget *top_widget);

        /**
         * create a main widget, within the MDI area
         * or toplevel widget in case of SDI interface
         * @param preferred_size preferred size of the main widget
         * @return true if successful, false if failed
         */
        bool createMainWidget(const QSize &preferred_size);

        /**
         * migrate this context to a different toplevel widget
         * @param top_widget pointer to the new toplevel widget
         */
        void setParent(Kwave::TopWidget *top_widget);

        /** returns a reference to the application instance */
        Kwave::App           &app() const { return m_application; }

        /**
         * returns a pointer to the main widget of this context
         */
        QWidget              *mainWidget() const;

        /** returns a pointer to the signal manager of this context */
        Kwave::SignalManager *signalManager() const;

        /** returns a pointer to the plugin manager of this context */
        Kwave::PluginManager *pluginManager() const;

        /**
         * Returns a pointer to a GUI element that receives zoom info
         * (the MainWidget)
         */
        Kwave::Zoomable *zoomable() const;

        /**
         * Returns whether this context is empty (has a main widget) or not.
         * @retval true if the context is empty
         * @retval false if the context has a main widget
         */
        inline bool isEmpty() const { return m_main_widget.isNull(); }

        /**
         * Returns whether this context is active or not.
         * @retval true if the context is active
         * @retval false if the context is inactive
         */
        inline bool isActive() const { return m_active; }

        /**
         * Returns true it this context has a signal (file is loaded)
         * or the context is executing a script
         */
        bool isInUse() const;

        /** returns the name of the signal */
        QString signalName() const;

        /** returns the instance of the loaded file or -1 */
        inline int instanceNr() const { return m_instance_nr; }

        /**
         * returns a string suitable as window caption
         * @param with_modified if true, include the "modified" state
         */
        QString windowCaption(bool with_modified) const;

        /**
         * Loads a batch file into memory, parses and executes
         * all commands in it.
         * @param url URL of the macro (batch file) to be loaded
         */
        int loadBatch(const QUrl &url);

        /**
         * Saves the current file.
         * @return zero if succeeded, non-zero if failed
         */
        int saveFile();

        /**
         * Opens a dialog for saving the current file.
         * @param filename the name of the new file
         *                 or empty string to open the File/SaveAs dialog
         * @param selection if set to true, only the current selection
         *        will be saved
         * @return zero if succeeded, non-zero if failed
         */
        int saveFileAs(const QString &filename, bool selection = false);

        /**
         * Closes the current file.
         * If the file has been modified and the user wanted to cancel
         * the close operation, the file will not get closed and the
         * function returns with false.
         * @return true if closing is allowed, false if canceled
         */
        bool closeFile();

    protected:
        friend class App;
        friend class TopWidget;
        friend class UsageGuard;

        /**
         * increments the usage count of this context, prevents it from
         * being deleted
         */
        void use();

        /**
         * decrements the usage count of this context, and if it has reached
         * zero this instance will be deleted (delayed)
         */
        void release();

    signals:

        /**
         * emitted when there is a status bar message to show
         * @param message the status bar message, already localized
         * @param ms the time in milliseconds to show the message
         */
        void sigStatusBarMessage(const QString &message, unsigned int ms);

        /**
         * emitted when the zoom factor of the corresponding main widget
         * has changed
         * @param context contains "this"
         * @param zoom new zoom factor
         */
        void sigZoomChanged(Kwave::FileContext *context, double zoom);

        /**
         * emitted when the meta data of the current signal has changed
         * @param meta_data the new meta data, after the change
         */
        void sigMetaDataChanged(Kwave::MetaDataList meta_data);

        /**
         * emits a change in the selected range.
         * @param offset index of the first selected items
         * @param length number of selected items
         */
        void sigSelectionChanged(sample_index_t offset, sample_index_t length);

        /**
         * Emitted if the state or description of undo/redo has changed. If
         * undo or redo is unavailable the description will be zero.
         */
        void sigUndoRedoInfo(const QString &undo, const QString &redo);

        /** emitted when the visible range has changed */
        void sigVisibleRangeChanged(sample_index_t offset,
                                    sample_index_t visible,
                                    sample_index_t total);

        /**
         * Emitted if the signal changes from non-modified to modified
         * state or vice-versa.
         */
        void sigModified();

        /**
         * emitted when the context is about to be destroyed
         * (in the context of it's destructor)
         */
        void destroyed(Kwave::FileContext *context);

    public slots:

        /**
         * Execute a Kwave text command
         * @param command a text command
         * @return zero if succeeded or negative error code if failed
         */
        int executeCommand(const QString &command);

    private slots:

        /**
         * called when the current file context has changed
         * @param context the new file context (can be "this")
         */
        void contextSwitched(Kwave::FileContext *context);

        /**
         * emits a sigZoomChanged(this, zoom) when the zoom has changed
         * in the m_main_widget
         */
        void forwardZoomChanged(double zoom);

        /**
         * Called when the playback position has changed
         * @param offset the current playback position [samples]
         */
        void updatePlaybackPos(sample_index_t offset);

        /**
         * Called when the meta data of the current signal has changed
         * @param meta_data the new meta data, after the change
         */
        void metaDataChanged(Kwave::MetaDataList meta_data);

        /**
         * Called when the number of selected samples has changed.
         * @param offset index of the first selected sample
         * @param length number of selected samples
         */
        void selectionChanged(sample_index_t offset, sample_index_t length);

        /**
         * Called when the undo or redo action has changed.
         * @param undo description of the last undo action
         * @param redo description of the last redo action
         */
        void setUndoRedoInfo(const QString &undo, const QString &redo);

        /**
         * Called after changes of the currently visible view range
         * @param offset index of the first visible sample
         * @param visible number of visible samples
         * @param total length of the whole signal
         */
        void visibleRangeChanged(sample_index_t offset,
                                 sample_index_t visible,
                                 sample_index_t total);

        /**
         * called if the signal now or no longer is modified
         */
        void modifiedChanged();

        /** process the next delayed command from m_delayed_command_queue */
        void processDelayedCommand();

    private:

        class UsageGuard
        {
        public:
            /**
             * constructor, increments use count
             * @param context the file context to use
             */
            explicit UsageGuard(Kwave::FileContext *context)
                :m_context(context)
            {
                if (m_context) m_context->use();
            }

            /** destructor, decrements use count of the context */
            virtual ~UsageGuard()
            {
                if (m_context) m_context->release();
                m_context = Q_NULLPTR;
            }

        private:
            QPointer<Kwave::FileContext> m_context;
        };

    private:

        /**
         * should be called when this context got active, to update
         * the status bar, toolbar etc.
         */
        void activated();

        /**
         * Show a message in the status bar
         * @param msg the status bar message, already localized
         * @param ms the time in milliseconds to show the message
         */
        void statusBarMessage(const QString &msg, unsigned int ms);

        /**
         * Parses a text stream line by line and executes each line
         * as a command until all commands are done or the first one fails.
         * @param stream a QTextStream to read from
         * @return zero if successful, non-zero error code if a command failed
         */
        int parseCommands(QTextStream &stream);

        /**
         * enqueues a command for later execution
         * @param delay milliseconds to wait before execution
         * @param command the command to execute
         */
        void enqueueCommand(unsigned int delay, const QString &command);

        /**
         * Discards all changes to the current file and loads
         * it again.
         * @return zero if succeeded or error code
         */
        int revert();

        /**
         * delegate a command to a plugin
         * @param plugin name of a plugin to delegate the command to
         * @param parser the parser with the parts of the command
         * @param param_count required number of parameters
         */
        int delegateCommand(const char *plugin,
                            Kwave::Parser &parser,
                            unsigned int param_count);

    private:

        /** usage counter [0...n] */
        QAtomicInt m_use_count;

        /** reference to the global Kwave application object */
        Kwave::App &m_application;

        /** instance of our toplevel window */
        QPointer<Kwave::TopWidget> m_top_widget;

        /** instance of our main widget */
        QPointer<Kwave::MainWidget> m_main_widget;

        /** instance of our signal manager */
        QPointer<Kwave::SignalManager> m_signal_manager;

        /** instance of our plugin manager */
        QPointer<Kwave::PluginManager> m_plugin_manager;

        /** if true, this context is active, otherwise it is inactive */
        bool m_active;

        /** last zoom factor */
        double m_last_zoom;

        /** last playback position, only valid if playback is running */
        sample_index_t m_last_playback_pos;

        /** last status bar message */
        QString m_last_status_message_text;

        /** time when the last status message has been shown */
        QElapsedTimer m_last_status_message_timer;

        /** number of milliseconds the status message should be shown */
        unsigned int m_last_status_message_ms;

        /** name of the last undo action */
        QString m_last_undo;

        /** name of the last redo action */
        QString m_last_redo;

        /** instance of the loaded file or -1 */
        int m_instance_nr;

        /** timer for delayed commands */
        QTimer m_delayed_command_timer;

        /** queue for delayed execution of commands */
        QList< QPair<unsigned int, QString> > m_delayed_command_queue;

    };

}

#endif /* KWAVE_FILE_CONTEXT_H */

//***************************************************************************
//***************************************************************************
