/***************************************************************************
                       findbugdlg.cpp  -  find bug report by title, in pkg list
                             -------------------
    copyright            : (C) 2002 by David Faure
    email                : david@mandrakesoft.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 version 2.                               *
 *                                                                         *
 ***************************************************************************/
#include "findbugdlg.h"
#include "findbugdlg.moc"


#include <qcheckbox.h>
#include <qcursor.h>
#include <qgroupbox.h>
#include <qlabel.h>
#include <qlayout.h>
#include <qpopupmenu.h>
#include <qpushbutton.h>
#include <qregexp.h>
#include <kapplication.h>
#include <kdebug.h>
#include <kcombobox.h>
#include <klocale.h>
#include <kmessagebox.h>

FindBugDlg::FindBugDlg(QWidget *parent, const char *name, long options, const QStringList &findStrings, bool hasSelection) :
    KDialogBase(parent, name, true, i18n("Find Text"), Ok | Cancel, Ok)
{
    init(false, findStrings, hasSelection);
    setOptions(options);
}

FindBugDlg::~FindBugDlg()
{
}

/*QWidget *FindBugDlg::findExtension()
{
    return m_findExtension;
    }*/

QStringList FindBugDlg::findHistory() const
{
    return m_find->historyItems();
}

void FindBugDlg::init(bool forReplace, const QStringList &findStrings, bool hasSelection)
{
    QVBoxLayout *topLayout;
    QGridLayout *optionsLayout;

    // Create common parts of dialog.
    QWidget *page = new QWidget(this);
    setMainWidget(page);

    topLayout = new QVBoxLayout(page);
    topLayout->setSpacing( KDialog::spacingHint() );
    topLayout->setMargin( KDialog::marginHint() );

    m_findGrp = new QGroupBox(0, Qt::Vertical, i18n("Find"), page);
    m_findGrp->layout()->setSpacing(KDialog::spacingHint());
    m_findGrp->layout()->setMargin(KDialog::marginHint());
    m_findLayout = new QGridLayout(m_findGrp->layout());
    m_findLayout->setSpacing( KDialog::spacingHint() );
    m_findLayout->setMargin( KDialog::marginHint() );

    m_findLabel = new QLabel(i18n("&Text To Find"), m_findGrp);
    m_find = new KHistoryCombo(true, m_findGrp);
    m_find->setMaxCount(10);
    m_find->setDuplicatesEnabled(false);
    m_regExp = new QCheckBox(i18n("Use Patterns"), m_findGrp);
    //m_regExpItem = new QPushButton(i18n("Insert Pattern"), m_findGrp);
    //m_regExpItem->setEnabled(false);
    //m_findExtension = new QWidget(m_findGrp);

    m_findLayout->addWidget(m_findLabel, 0, 0);
    m_findLayout->addMultiCellWidget(m_find, 1, 1, 0, 1);
    m_findLayout->addWidget(m_regExp, 2, 0);
    //m_findLayout->addWidget(m_regExpItem, 2, 1);
    //m_findLayout->addMultiCellWidget(m_findExtension, 3, 3, 0, 1);
    topLayout->addWidget(m_findGrp);

    /*
    m_replaceGrp = new QGroupBox(0, Qt::Vertical, i18n("Replace With"), page);
    m_replaceGrp->layout()->setSpacing(KDialog::spacingHint());
    m_replaceGrp->layout()->setMargin(KDialog::marginHint());
    m_replaceLayout = new QGridLayout(m_replaceGrp->layout());
    m_replaceLayout->setSpacing( KDialog::spacingHint() );
    m_replaceLayout->setMargin( KDialog::marginHint() );

    m_replaceLabel = new QLabel(i18n("&Replacement Text"), m_replaceGrp);
    m_replace = new KHistoryCombo(true, m_replaceGrp);
    m_replace->setMaxCount(10);
    m_replace->setDuplicatesEnabled(false);
    m_backRef = new QCheckBox(i18n("&Use Placeholders"), m_replaceGrp);
    m_backRefItem = new QPushButton(i18n("Insert Placeholder"), m_replaceGrp);
    m_backRefItem->setEnabled(false);
    m_replaceExtension = new QWidget(m_replaceGrp);

    m_replaceLayout->addWidget(m_replaceLabel, 0, 0);
    m_replaceLayout->addMultiCellWidget(m_replace, 1, 1, 0, 1);
    m_replaceLayout->addWidget(m_backRef, 2, 0);
    m_replaceLayout->addWidget(m_backRefItem, 2, 1);
    m_replaceLayout->addMultiCellWidget(m_replaceExtension, 3, 3, 0, 1);
    topLayout->addWidget(m_replaceGrp);
    */

    m_optionGrp = new QGroupBox(0, Qt::Vertical, i18n("Options"), page);
    m_optionGrp->layout()->setSpacing(KDialog::spacingHint());
    m_optionGrp->layout()->setMargin(KDialog::marginHint());
    optionsLayout = new QGridLayout(m_optionGrp->layout());
    optionsLayout->setSpacing( KDialog::spacingHint() );
    optionsLayout->setMargin( KDialog::marginHint() );

    m_caseSensitive = new QCheckBox(i18n("C&ase Sensitive"), m_optionGrp);
    m_wholeWordsOnly = new QCheckBox(i18n("&Whole Words Only"), m_optionGrp);
    m_fromCursor = new QCheckBox(i18n("&From Cursor"), m_optionGrp);
    m_findBackwards = new QCheckBox(i18n("Find &Backwards"), m_optionGrp);
    m_selectedText = new QCheckBox(i18n("&Selected Text"), m_optionGrp);
    setHasSelection( hasSelection );
    //m_promptOnReplace = new QCheckBox(i18n("&Prompt On Replace"), m_optionGrp);
    //m_promptOnReplace->setChecked( true );

    optionsLayout->addWidget(m_caseSensitive, 0, 0);
    optionsLayout->addWidget(m_wholeWordsOnly, 1, 0);
    optionsLayout->addWidget(m_fromCursor, 2, 0);
    optionsLayout->addWidget(m_findBackwards, 0, 1);
    optionsLayout->addWidget(m_selectedText, 1, 1);
    //optionsLayout->addWidget(m_promptOnReplace, 2, 1);
    topLayout->addWidget(m_optionGrp);

    // We delay creation of these until needed.
    m_patterns = 0L;
    //m_placeholders = 0L;

    // signals and slots connections
    connect(m_selectedText, SIGNAL(toggled(bool)), this, SLOT(slotSelectedTextToggled(bool)));
    //connect(m_regExp, SIGNAL(toggled(bool)), m_regExpItem, SLOT(setEnabled(bool)));
    //connect(m_backRef, SIGNAL(toggled(bool)), m_backRefItem, SLOT(setEnabled(bool)));
    //connect(m_regExpItem, SIGNAL(pressed()), this, SLOT(showPatterns()));
    //connect(m_backRefItem, SIGNAL(pressed()), this, SLOT(showPlaceholders()));

    connect(m_find, SIGNAL(textChanged ( const QString & )),this, SLOT(textSearchChanged( const QString & )));

    // tab order
    setTabOrder(m_find, m_regExp);
    //setTabOrder(m_regExp, m_regExpItem);
    //setTabOrder(m_regExpItem, m_replace);
    //setTabOrder(m_replace, m_backRef);
    //setTabOrder(m_backRef, m_backRefItem);
    //setTabOrder(m_backRefItem, m_caseSensitive);
    setTabOrder(m_caseSensitive, m_wholeWordsOnly);
    setTabOrder(m_wholeWordsOnly, m_fromCursor);
    setTabOrder(m_fromCursor, m_findBackwards);
    setTabOrder(m_findBackwards, m_selectedText);
    //setTabOrder(m_selectedText, m_promptOnReplace);

    // buddies
    m_findLabel->setBuddy(m_find);
    //m_replaceLabel->setBuddy(m_replace);

    if (!forReplace)
    {
        //m_promptOnReplace->hide();
        //m_replaceGrp->hide();
    }
    setFindHistory(findStrings);
    m_find->setFocus();
    enableButtonOK( !pattern().isEmpty() );
}

void FindBugDlg::textSearchChanged( const QString & text)
{
    enableButtonOK( !text.isEmpty() );
}

long FindBugDlg::options() const
{
    long options = 0;

    if (m_caseSensitive->isChecked())
        options |= CaseSensitive;
    if (m_wholeWordsOnly->isChecked())
        options |= WholeWordsOnly;
    if (m_fromCursor->isChecked())
        options |= FromCursor;
    if (m_findBackwards->isChecked())
        options |= FindBackwards;
    if (m_selectedText->isChecked())
        options |= SelectedText;
    if (m_regExp->isChecked())
        options |= RegularExpression;
    return options;
}

QString FindBugDlg::pattern() const
{
    return m_find->currentText();
}

void FindBugDlg::setFindHistory(const QStringList &strings)
{
    if (strings.count() > 0)
    {
        m_find->setHistoryItems(strings, true);
        m_find->lineEdit()->setText( strings.first() );
        m_find->lineEdit()->selectAll();
    }
    else
        m_find->clearHistory();
}

void FindBugDlg::setHasSelection(bool hasSelection)
{
    m_selectedText->setEnabled( hasSelection );
    // If we have a selection, we make 'find in selection' default
    // and if we don't, then the option has to be unchecked, obviously.
    m_selectedText->setChecked( hasSelection );
    slotSelectedTextToggled( hasSelection );
}

void FindBugDlg::slotSelectedTextToggled(bool selec)
{
    // From cursor doesn't make sense if we have a selection
    m_fromCursor->setEnabled( !selec );
    if ( selec ) // uncheck if disabled
        m_fromCursor->setChecked( false );
}

void FindBugDlg::setOptions(long options)
{
    m_caseSensitive->setChecked(options & CaseSensitive);
    m_wholeWordsOnly->setChecked(options & WholeWordsOnly);
    m_fromCursor->setChecked(options & FromCursor);
    m_findBackwards->setChecked(options & FindBackwards);
    m_selectedText->setChecked(options & SelectedText);
    m_regExp->setChecked(options & RegularExpression);
}


void FindBugDlg::slotOk()
{
    // Nothing to find?
    if (pattern().isEmpty())
    {
        KMessageBox::error(this, i18n("You must enter some text to search for."));
        return;
    }

    if (m_regExp->isChecked())
    {
        // Check for a valid regular expression.
        QRegExp regExp(pattern());

        if (!regExp.isValid())
        {
            KMessageBox::error(this, i18n("Invalid regular expression."));
            return;
        }
    }
    m_find->addToHistory(pattern());
    emit okClicked();
    accept();
}

// Create the dialog.
FindNextBugDlg::FindNextBugDlg(const QString &pattern, long options, QWidget *parent) :
    KDialogBase(parent, __FILE__, false,  // non-modal!
        i18n("Find"),
        User1 | Close,
        User1,
        false,
        i18n("&Yes"))
{
    setMainWidget( new QLabel( i18n("Find next '%1'").arg(pattern), this ) );

    m_cancelled = false;
    m_options = options;
    m_parent = parent;
    m_matches = 0;
    if (m_options & FindBugDlg::RegularExpression)
        m_regExp = new QRegExp(pattern, m_options & FindBugDlg::CaseSensitive);
    else
        m_pattern = pattern;
    resize(minimumSize());
}

FindNextBugDlg::~FindNextBugDlg()
{
    if (!m_matches && !m_cancelled)
        KMessageBox::information(m_parent, i18n("No match was found."));
}

void FindNextBugDlg::slotClose()
{
    m_cancelled = true;
    kapp->exit_loop();
}

void FindNextBugDlg::abort()
{
    slotClose();
}

bool FindNextBugDlg::find(const QString &text, const QRect &expose)
{
    if (m_options & FindBugDlg::FindBackwards)
    {
        m_index = text.length();
    }
    else
    {
        m_index = 0;
    }
    m_text = text;
    m_expose = expose;
    do
    {
        // Find the next match.
        if (m_options & FindBugDlg::RegularExpression)
            m_index = FindNextBugDlg::find(m_text, *m_regExp, m_index, m_options, &m_matchedLength);
        else
            m_index = FindNextBugDlg::find(m_text, m_pattern, m_index, m_options, &m_matchedLength);
        if (m_index != -1)
        {
            // Tell the world about the match we found, in case someone wants to
            // highlight it.
            emit highlight(m_text, m_index, m_matchedLength, m_expose);
            show();
            kapp->enter_loop();
        }
    }
    while ((m_index != -1) && !m_cancelled);

    // Should the user continue?
    return !m_cancelled;
}

int FindNextBugDlg::find(const QString &text, const QString &pattern, int index, long options, int *matchedLength)
{
    // Handle regular expressions in the appropriate way.
    if (options & FindBugDlg::RegularExpression)
    {
        QRegExp regExp(pattern, options & FindBugDlg::CaseSensitive);

        return find(text, regExp, index, options, matchedLength);
    }

    bool caseSensitive = (options & FindBugDlg::CaseSensitive);

    if (options & FindBugDlg::WholeWordsOnly)
    {
        if (options & FindBugDlg::FindBackwards)
        {
            // Backward search, until the beginning of the line...
            while (index >= 0)
            {
                // ...find the next match.
                index = text.findRev(pattern, index, caseSensitive);
                if (index == -1)
                    break;

                // Is the match delimited correctly?
                *matchedLength = pattern.length();
                if (isWholeWords(text, index, *matchedLength))
                    break;
                index--;
            }
        }
        else
        {
            // Forward search, until the end of the line...
            while (index < (int)text.length())
            {
                // ...find the next match.
                index = text.find(pattern, index, caseSensitive);
                if (index == -1)
                    break;

                // Is the match delimited correctly?
                *matchedLength = pattern.length();
                if (isWholeWords(text, index, *matchedLength))
                    break;
                index++;
            }
        }
    }
    else
    {
        // Non-whole-word search.
        if (options & FindBugDlg::FindBackwards)
        {
            index = text.findRev(pattern, index, caseSensitive);
        }
        else
        {
            index = text.find(pattern, index, caseSensitive);
        }
        if (index != -1)
        {
            *matchedLength = pattern.length();
        }
    }
    return index;
}

int FindNextBugDlg::find(const QString &text, const QRegExp &pattern, int index, long options, int *matchedLength)
{
    if (options & FindBugDlg::WholeWordsOnly)
    {
        if (options & FindBugDlg::FindBackwards)
        {
            // Backward search, until the beginning of the line...
            while (index >= 0)
            {
                // ...find the next match.
                index = text.findRev(pattern, index);
                if (index == -1)
                    break;

                // Is the match delimited correctly?
                //pattern.match(text, index, matchedLength, false);
                /*int pos =*/ pattern.search( text.mid(index) );
                *matchedLength = pattern.matchedLength();
                if (isWholeWords(text, index, *matchedLength))
                    break;
                index--;
            }
        }
        else
        {
            // Forward search, until the end of the line...
            while (index < (int)text.length())
            {
                // ...find the next match.
                index = text.find(pattern, index);
                if (index == -1)
                    break;

                // Is the match delimited correctly?
                //pattern.match(text, index, matchedLength, false);
                /*int pos =*/ pattern.search( text.mid(index) );
                *matchedLength = pattern.matchedLength();
                if (isWholeWords(text, index, *matchedLength))
                    break;
                index++;
            }
        }
    }
    else
    {
        // Non-whole-word search.
        if (options & FindBugDlg::FindBackwards)
        {
            index = text.findRev(pattern, index);
        }
        else
        {
            index = text.find(pattern, index);
        }
        if (index != -1)
        {
            //pattern.match(text, index, matchedLength, false);
            /*int pos =*/ pattern.search( text.mid(index) );
            *matchedLength = pattern.matchedLength();
        }
    }
    return index;
}

bool FindNextBugDlg::isInWord(QChar ch)
{
    return ch.isLetter() || ch.isDigit() || ch == '_';
}

bool FindNextBugDlg::isWholeWords(const QString &text, int starts, int matchedLength)
{
    if ((starts == 0) || (!isInWord(text[starts - 1])))
    {
        int ends = starts + matchedLength;

        if ((ends == (int)text.length()) || (!isInWord(text[ends])))
            return true;
    }
    return false;
}

// Yes.
void FindNextBugDlg::slotUser1()
{
    m_matches++;
    if (m_options & FindBugDlg::FindBackwards)
        m_index--;
    else
        m_index++;
    kapp->exit_loop();
}
