/*
    SuperCollider Qt IDE
    Copyright (c) 2012 Jakob Leben & Tim Blechmann
    http://www.audiosynth.com

    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.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
*/

#pragma once

#include "../widgets/code_editor/editor.hpp"
#include <QDateTime>
#include <QFileSystemWatcher>
#include <QHash>
#include <QList>
#include <QMetaType>
#include <QObject>
#include <QStringList>
#include <QTextDocument>
#include <QPlainTextDocumentLayout>
#include <QUuid>
#include <QTimer>
#include <QStandardItemModel>
#include <QApplication>
#include <QStyle>

#define RESTORE_COAL 100
#define RESTORE_COAL_MSECS 60000

namespace ScIDE {

namespace Settings {
class Manager;
}
class SyntaxHighlighter;

class Main;
class DocumentManager;

class Document : public QObject {
    Q_OBJECT

    friend class DocumentManager;

public:
    Document(bool isPlainText, const QByteArray& id = QByteArray(), const QString& title = QString(),
             const QString& text = QString());

    QTextDocument* textDocument() { return mDoc; }
    const QByteArray& id() { return mId; }
    const QString& filePath() { return mFilePath; }
    const QString& title() { return mTitle; }
    void setTitle(QString& newTitle) {
        mTitle = newTitle;
        mModelItem->setText(mTitle);
    }

    QFont defaultFont() const { return mDoc->defaultFont(); }
    void setDefaultFont(const QFont& font);

    int indentWidth() const { return mIndentWidth; }
    void setIndentWidth(int numSpaces);

    void deleteTrailingSpaces();

    bool isPlainText() const { return mHighlighter == NULL; }
    bool isModified() const { return mDoc->isModified(); }

    QStandardItem* modelItem() { return mModelItem; }

    QString textAsSCArrayOfCharCodes(int start, int range);
    QString titleAsSCArrayOfCharCodes();
    QString pathAsSCArrayOfCharCodes();
    QString bytesToSCArrayOfCharCodes(QByteArray stringBytes);

    void setTextInRange(const QString text, int start, int range);

    bool keyDownActionEnabled() { return mKeyDownActionEnabled; }
    bool keyUpActionEnabled() { return mKeyUpActionEnabled; }
    bool mouseDownActionEnabled() { return mMouseDownActionEnabled; }
    bool mouseUpActionEnabled() { return mMouseUpActionEnabled; }
    bool textChangedActionEnabled() { return mTextChangedActionEnabled; }
    GenericCodeEditor* lastActiveEditor() { return mLastActiveEditor; }
    int initialSelectionStart() { return mInitialSelectionStart; }
    int initialSelectionRange() { return mInitialSelectionRange; }
    bool editable() { return mEditable; }
    bool promptsToSave() { return mPromptsToSave; }

    void setKeyDownActionEnabled(bool enabled) { mKeyDownActionEnabled = enabled; }
    void setKeyUpActionEnabled(bool enabled) { mKeyUpActionEnabled = enabled; }
    void setMouseDownActionEnabled(bool enabled) { mMouseDownActionEnabled = enabled; }
    void setMouseUpActionEnabled(bool enabled) { mMouseUpActionEnabled = enabled; }
    void setTextChangedActionEnabled(bool enabled) { mTextChangedActionEnabled = enabled; }
    void setLastActiveEditor(GenericCodeEditor* lastActive) { mLastActiveEditor = lastActive; }
    void setInitialSelection(int start, int range) {
        mInitialSelectionStart = start;
        mInitialSelectionRange = range;
    }
    void setEditable(bool editable) { mEditable = editable; }
    void setPromptsToSave(bool prompts) { mPromptsToSave = prompts; }

    void removeTmpFile();

public slots:
    void applySettings(Settings::Manager*);
    void resetDefaultFont();
    void storeTmpFile();
    void onTmpCoalUsecs();

    void onModificationChanged(bool changed) {
        if (changed) {
            mModelItem->setIcon(QApplication::style()->standardIcon(QStyle::SP_DialogSaveButton));
        } else {
            mModelItem->setIcon(QIcon());
        }
    }

signals:
    void defaultFontChanged();

private:
    void setPlainText(bool flag);

    QByteArray mId;
    QTextDocument* mDoc;
    QString mFilePath;
    QString mTitle;
    QString mTmpFilePath;
    int mTmpCoalCount;
    QTimer mTmpCoalTimer;
    QDateTime mSaveTime;
    int mIndentWidth;
    SyntaxHighlighter* mHighlighter;
    bool mKeyDownActionEnabled;
    bool mKeyUpActionEnabled;
    bool mMouseDownActionEnabled;
    bool mMouseUpActionEnabled;
    bool mTextChangedActionEnabled;
    GenericCodeEditor* mLastActiveEditor;
    int mInitialSelectionStart, mInitialSelectionRange;
    bool mEditable;
    bool mPromptsToSave;
    QStandardItem* mModelItem;
};

class DocumentManager : public QObject {
    Q_OBJECT

public:
    typedef QList<Document*> DocumentList;

    DocumentManager(Main*, Settings::Manager*);
    QList<Document*> documents() { return mDocHash.values(); }
    QList<QByteArray> documentIDs() { return mDocHash.uniqueKeys(); }

    void create();
    void close(Document*);
    bool save(Document*);
    bool saveAs(Document*, const QString& path);
    bool reload(Document*);
    bool needRestore();
    void restore();
    void deleteRestore();
    const QStringList& recents() const { return mRecent; }
    Document* documentForId(const QByteArray id);
    bool textMirrorEnabled() { return mTextMirrorEnabled; }
    void setActiveDocument(class Document*);
    void sendActiveDocument();
    Document* activeDocument() { return mCurrentDocument; }
    bool globalKeyDownActionEnabled() { return mGlobalKeyDownEnabled; }
    bool globalKeyUpActionEnabled() { return mGlobalKeyUpEnabled; }
    QStandardItemModel* docModel() { return mDocumentModel; }

public slots:
    // initialCursorPosition -1 means "don't change position if already open"
    Document* open(const QString& path, int initialCursorPosition = -1, int selectionLength = 0,
                   bool addToRecent = true, const QByteArray& id = QByteArray(), bool syncLang = true);
    void clearRecents();
    void storeSettings(Settings::Manager*);
    void handleScLangMessage(const QString& selector, const QString& data);

Q_SIGNALS:
    void opened(Document*, int cursorPosition, int selectionLength);
    void closed(Document*);
    void saved(Document*);
    void showRequest(Document*, int pos = -1, int selectionLength = 0);
    void changedExternally(Document*);
    void recentsChanged();
    void titleChanged(Document*);

private slots:
    void onFileChanged(const QString& path);
    void updateCurrentDocContents(int position, int charsRemoved, int charsAdded);

private:
    Document* createDocument(bool isPlainText = false, const QByteArray& id = QByteArray(),
                             const QString& title = QString(), const QString& text = QString());
    bool doSaveAs(Document*, const QString& path);
    void addToRecent(Document*);
    void loadRecentDocuments(Settings::Manager*);
    QStringList tmpFiles();
    void closeSingleUntitledIfUnmodified();
    QString decodeDocument(QByteArray const&);
    void handleDocListScRequest();
    void handleNewDocScRequest(const QString& data);
    void handleOpenFileScRequest(const QString& data);
    void handleGetDocTextScRequest(const QString& data);
    void handleSetDocTextScRequest(const QString& data);
    void handleSetDocSelectionScRequest(const QString& data);
    void handleSetDocEditableScRequest(const QString& data);
    void handleSetDocPromptsToSaveScRequest(const QString& data);
    void handleSetCurrentDocScRequest(const QString& data);
    void handleRemoveDocUndoScRequest(const QString& data);
    void handleCloseDocScRequest(const QString& data);
    void handleSetDocTitleScRequest(const QString& data);

    bool parseActionEnabledRequest(const QString& data, std::string* idString, bool* en);
    void handleEnableKeyDownScRequest(const QString& data);
    void handleEnableKeyUpScRequest(const QString& data);
    void handleEnableGlobalKeyDownScRequest(const QString& data);
    void handleEnableGlobalKeyUpScRequest(const QString& data);
    void handleEnableMouseDownScRequest(const QString& data);
    void handleEnableMouseUpScRequest(const QString& data);
    void handleEnableTextChangedScRequest(const QString& data);
    void handleEnableTextMirrorScRequest(const QString& data);
    void syncLangDocument(Document*);

    typedef QHash<QByteArray, Document*>::iterator DocIterator;

    QHash<QByteArray, Document*> mDocHash;
    QFileSystemWatcher mFsWatcher;

    QStringList mRecent;
    static const int mMaxRecent = 10;

    bool mTextMirrorEnabled;
    QString mCurrentDocumentPath;
    class Document* mCurrentDocument;
    bool mGlobalKeyDownEnabled, mGlobalKeyUpEnabled;
    QStandardItemModel* mDocumentModel;
};

} // namespace ScIDE

Q_DECLARE_METATYPE(ScIDE::Document*)
