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

#include "find_replace_tool.hpp"
#include "main_window.hpp"
#include "code_editor/editor.hpp"
#include "../core/main.hpp"
#include "../core/settings/manager.hpp"

#include <QApplication>
#include <QTextBlock>
#include <QToolButton>
#include <QStyle>
#include <QHBoxLayout>

namespace ScIDE {

TextFindReplacePanel::TextFindReplacePanel(QWidget* parent):
    QWidget(parent),
    mMode((Mode)0), // a hack so that first setMode() works
    mEditor(0),
    mSearchPosition(-1) {
    mFindField = new QLineEdit;
    mReplaceField = new QLineEdit;

    mNextBtn = new QToolButton();
    mNextBtn->setIcon(style()->standardIcon(QStyle::SP_ArrowForward));
    mNextBtn->setToolTip(tr("Find Next"));

    mPrevBtn = new QToolButton();
    mPrevBtn->setIcon(style()->standardIcon(QStyle::SP_ArrowBack));
    mPrevBtn->setToolTip(tr("Find Previous"));

    mReplaceBtn = new QToolButton();
    mReplaceBtn->setText(tr("Replace"));
    mReplaceAllBtn = new QToolButton();
    mReplaceAllBtn->setText(tr("Replace All"));

    mOptionsBtn = new QToolButton();
    mOptionsBtn->setText(tr("Options"));
    mOptionsBtn->setIcon(QIcon::fromTheme("preferences-other"));
    mOptionsBtn->setPopupMode(QToolButton::InstantPopup);

    QMenu* optMenu = new QMenu(this);
    mMatchCaseAction = optMenu->addAction(tr("Match Case"));
    mMatchCaseAction->setCheckable(true);
    mRegExpAction = optMenu->addAction(tr("Regular Expression"));
    mRegExpAction->setCheckable(true);
    mWholeWordAction = optMenu->addAction(tr("Whole Words"));
    mWholeWordAction->setCheckable(true);
    mOptionsBtn->setMenu(optMenu);

    mFindLabel = new QLabel(tr("Find:"));
    mFindLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
    mReplaceLabel = new QLabel(tr("Replace:"));
    mReplaceLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);

    mGrid = new QGridLayout();
    mGrid->setContentsMargins(0, 0, 0, 0);
    mGrid->setSpacing(2);

    QHBoxLayout* findBtnLayout = new QHBoxLayout();
    findBtnLayout->setContentsMargins(0, 0, 0, 0);
    findBtnLayout->setSpacing(1);
    findBtnLayout->addWidget(mPrevBtn);
    findBtnLayout->addWidget(mNextBtn);
    findBtnLayout->addStretch(0);
    findBtnLayout->addWidget(mOptionsBtn);

    mGrid->addWidget(mFindLabel, 0, 0);
    mGrid->addWidget(mFindField, 0, 1);
    mGrid->addLayout(findBtnLayout, 0, 2);

    QHBoxLayout* replaceBtnLayout = new QHBoxLayout();
    replaceBtnLayout->setContentsMargins(0, 0, 0, 0);
    replaceBtnLayout->setSpacing(1);
    replaceBtnLayout->addWidget(mReplaceBtn);
    replaceBtnLayout->addWidget(mReplaceAllBtn);
    replaceBtnLayout->addStretch(0);

    mGrid->addWidget(mReplaceLabel, 1, 0);
    mGrid->addWidget(mReplaceField, 1, 1);
    mGrid->addLayout(replaceBtnLayout, 1, 2);

    mGrid->setColumnStretch(1, 1);

    setLayout(mGrid);

    setMode(Find);

    setFocusProxy(mFindField);
    QWidget::setTabOrder(mFindField, mReplaceField);
    mFindField->installEventFilter(this);

    connect(mNextBtn, SIGNAL(clicked()), this, SLOT(findNext()));
    connect(mPrevBtn, SIGNAL(clicked()), this, SLOT(findPrevious()));
    connect(mReplaceBtn, SIGNAL(clicked()), this, SLOT(replace()));
    connect(mReplaceAllBtn, SIGNAL(clicked()), this, SLOT(replaceAll()));
    connect(mFindField, SIGNAL(returnPressed()), this, SLOT(onFindFieldReturn()));
    connect(mFindField, SIGNAL(textChanged(QString)), this, SLOT(onFindFieldTextChanged()));
    connect(mReplaceField, SIGNAL(returnPressed()), this, SLOT(replace()));
    // Update search results when options change:
    connect(optMenu, SIGNAL(triggered(QAction*)), this, SLOT(findAll()));

    Settings::Manager* settings = Main::settings();
    QAction* action;

    action = mActions[FindNext] = new QAction(tr("Find Next"), this);
    action->setShortcut(tr("Ctrl+G", "Find Next"));
    connect(action, SIGNAL(triggered()), this, SLOT(findNext()));
    settings->addAction(action, "editor-find-next", tr("Text Editor"));

    action = mActions[FindPrevious] = new QAction(tr("Find Previous"), this);
    action->setShortcut(tr("Ctrl+Shift+G", "Find Previous"));
    connect(action, SIGNAL(triggered()), this, SLOT(findPrevious()));
    settings->addAction(action, "editor-find-previous", tr("Text Editor"));
}

void TextFindReplacePanel::setMode(Mode mode) {
    if (mode == mMode)
        return;

    mMode = mode;

    bool visible = mMode == Replace;
    mReplaceLabel->setVisible(visible);
    mReplaceField->setVisible(visible);
    mReplaceBtn->setVisible(visible);
    mReplaceAllBtn->setVisible(visible);
}

void TextFindReplacePanel::initiate() {
    mSearchPosition = -1;

    if (mEditor) {
        QTextCursor c(mEditor->textCursor());
        if (c.hasSelection()
            && c.document()->findBlock(c.selectionStart()) == c.document()->findBlock(c.selectionEnd())) {
            mFindField->setText(c.selectedText());
            mReplaceField->clear();
        }
    }

    mFindField->selectAll();
    findAll();
}

QRegularExpression TextFindReplacePanel::regexp() {
    QString pattern = findString();
    QRegularExpression::PatternOptions opt = QRegularExpression::PatternOption::NoPatternOption;
    QRegularExpression expr;

    if (asRegExp()) {
        expr = QRegularExpression(pattern);
    } else {
        expr = QRegularExpression(QRegularExpression::escape(pattern));
        opt |= QRegularExpression::PatternOption::DontCaptureOption;
    }
    if (!matchCase())
        opt |= QRegularExpression::PatternOption::CaseInsensitiveOption;

    expr.setPatternOptions(opt);
    return expr;
}

QTextDocument::FindFlags TextFindReplacePanel::flags() {
    QTextDocument::FindFlags f;
    if (wholeWords())
        f |= QTextDocument::FindWholeWords;
    return f;
}

void TextFindReplacePanel::findNext() { find(false); }

void TextFindReplacePanel::findPrevious() { find(true); }

void TextFindReplacePanel::onFindFieldReturn() { find(QApplication::keyboardModifiers() & Qt::ShiftModifier); }

void TextFindReplacePanel::onFindFieldTextChanged() {
    // Incremental search

    if (!mEditor)
        return;

    QRegularExpression expr(regexp());
    QTextDocument::FindFlags flagz(flags());

    if (mSearchPosition == -1)
        mSearchPosition = mEditor->textCursor().selectionStart();

    int count = mEditor->findAll(expr, flagz);
    if (!expr.pattern().isEmpty())
        reportFoundOccurrencies(count);

    QTextCursor searchCursor(mEditor->textDocument());
    searchCursor.setPosition(mSearchPosition);

    if (expr.pattern().isEmpty()) {
        mEditor->setTextCursor(searchCursor);
    } else if (count) {
        mEditor->setTextCursor(searchCursor);
        mEditor->find(expr, flagz);
    }
}

bool TextFindReplacePanel::eventFilter(QObject* obj, QEvent* ev) {
    if (obj == mFindField && ev->type() == QEvent::FocusOut)
        mSearchPosition = -1;

    return QWidget::eventFilter(obj, ev);
}

void TextFindReplacePanel::find(bool backwards) {
    // Non incremental search!

    if (!mEditor)
        return;

    QRegularExpression expr = regexp();
    if (expr.pattern().isEmpty())
        return;

    QTextDocument::FindFlags opt = flags();
    if (backwards)
        opt |= QTextDocument::FindBackward;

    mEditor->find(expr, opt);

    // This was not incremental search, so reset search position
    mSearchPosition = -1;
}

void TextFindReplacePanel::findAll() {
    if (!mEditor)
        return;

    QRegularExpression expr = regexp();

    // NOTE: empty expression removes any search highlighting
    int count = mEditor->findAll(expr, flags());

    if (!expr.pattern().isEmpty())
        reportFoundOccurrencies(count);
}

void TextFindReplacePanel::replace() {
    if (!mEditor)
        return;

    QRegularExpression expr = regexp();
    if (expr.pattern().isEmpty())
        return;

    mEditor->replace(expr, replaceString(), flags());

    mSearchPosition = -1;
}

void TextFindReplacePanel::replaceAll() {
    if (!mEditor)
        return;

    QRegularExpression expr = regexp();
    if (expr.pattern().isEmpty())
        return;

    QTextDocument::FindFlags opt = flags();

    int count = mEditor->replaceAll(expr, replaceString(), opt);

    reportReplacedOccurrencies(count);

    mSearchPosition = -1;
}

void TextFindReplacePanel::reportFoundOccurrencies(int count) {
    QString message = tr("%n matches found.", "Find text in document...", count);
    MainWindow::instance()->showStatusMessage(message);
}

void TextFindReplacePanel::reportReplacedOccurrencies(int count) {
    QString message = tr("%n matches replaced.", "Find/replace text in document...", count);
    MainWindow::instance()->showStatusMessage(message);
}

} // namespace ScIDE
