/***************************************************************************
 *   Copyright (C) 2004 by Enrico Ros <eros.kde@email.it>                  *
 *   Copyright (C) 2007, 2009-2010 by Pino Toscano <pino@kde.org>          *
 *                                                                         *
 *   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.                                   *
 ***************************************************************************/

#include "searchlineedit.h"

// local includes
#include "core/document.h"

// qt/kde includes
#include <qapplication.h>
#include <qlayout.h>
#include <qtimer.h>
#include <kcolorscheme.h>
#include <kiconloader.h>
#include <kpixmapsequence.h>
#include <kpixmapsequencewidget.h>
#include <kmessagebox.h>
#include <klocalizedstring.h>

SearchLineEdit::SearchLineEdit( QWidget * parent, Okular::Document * document )
    : KLineEdit( parent ), m_document( document ), m_minLength( 0 ),
      m_caseSensitivity( Qt::CaseInsensitive ),
      m_searchType( Okular::Document::AllDocument ), m_id( -1 ),
      m_moveViewport( false ), m_changed( false ), m_fromStart( true ),
      m_findAsYouType( true ), m_searchRunning( false )
{
    setObjectName( QStringLiteral( "SearchLineEdit" ) );
    setClearButtonShown( true );

    // a timer to ensure that we don't flood the document with requests to search
    m_inputDelayTimer = new QTimer(this);
    m_inputDelayTimer->setSingleShot(true);
    connect(m_inputDelayTimer, &QTimer::timeout, this, &SearchLineEdit::startSearch);

    connect(this, &SearchLineEdit::textChanged, this, &SearchLineEdit::slotTextChanged);
    connect(document, &Okular::Document::searchFinished, this, &SearchLineEdit::searchFinished);
}

void SearchLineEdit::clearText()
{
    clear();
}

void SearchLineEdit::setSearchCaseSensitivity( Qt::CaseSensitivity cs )
{
    m_caseSensitivity = cs;
    m_changed = true;
}

void SearchLineEdit::setSearchMinimumLength( int length )
{
    m_minLength = length;
    m_changed = true;
}

void SearchLineEdit::setSearchType( Okular::Document::SearchType type )
{
    if ( type == m_searchType )
        return;

    disconnect(this, &SearchLineEdit::returnPressed, this, &SearchLineEdit::slotReturnPressed);

    m_searchType = type;

    // Only connect Enter for next/prev searches, the rest of searches are document global so
    // next/prev serach does not make sense for them
    if (m_searchType == Okular::Document::NextMatch || m_searchType == Okular::Document::PreviousMatch) {
        connect(this, &SearchLineEdit::returnPressed, this, &SearchLineEdit::slotReturnPressed);
    }

    if ( !m_changed )
        m_changed = ( m_searchType != Okular::Document::NextMatch && m_searchType != Okular::Document::PreviousMatch );
}

void SearchLineEdit::setSearchId( int id )
{
    m_id = id;
    m_changed = true;
}

void SearchLineEdit::setSearchColor( const QColor &color )
{
    m_color = color;
    m_changed = true;
}

void SearchLineEdit::setSearchMoveViewport( bool move )
{
    m_moveViewport = move;
}

void SearchLineEdit::setSearchFromStart( bool fromStart )
{
    m_fromStart = fromStart;
}

void SearchLineEdit::setFindAsYouType( bool findAsYouType )
{
    m_findAsYouType = findAsYouType;
}

void SearchLineEdit::resetSearch()
{
    // Stop the currently running search, if any
    stopSearch();

    // Clear highlights
    if ( m_id != -1 )
        m_document->resetSearch( m_id );

    // Make sure that the search will be reset at the next one
    m_changed = true;

    // Reset input box color
    prepareLineEditForSearch();
}

bool SearchLineEdit::isSearchRunning() const
{
    return m_searchRunning;
}

void SearchLineEdit::restartSearch()
{
    m_inputDelayTimer->stop();
    m_inputDelayTimer->start( 700 );
    m_changed = true;
}

void SearchLineEdit::stopSearch()
{
    if ( m_id == -1 || !m_searchRunning )
        return;

    m_inputDelayTimer->stop();
    // ### this should just cancel the search with id m_id, not all of them
    m_document->cancelSearch();
    // flagging as "changed" so the search will be reset at the next one
    m_changed = true;
}

void SearchLineEdit::findNext()
{
    if ( m_id == -1 || m_searchType != Okular::Document::NextMatch )
        return;

    if ( !m_changed )
    {
        emit searchStarted();
        m_searchRunning = true;
        m_document->continueSearch( m_id, m_searchType );
    }
    else
        startSearch();
}

void SearchLineEdit::findPrev()
{
    if ( m_id == -1 || m_searchType != Okular::Document::PreviousMatch )
        return;

    if ( !m_changed )
    {
        emit searchStarted();
        m_searchRunning = true;
        m_document->continueSearch( m_id, m_searchType );
    }
    else
        startSearch();
}

void SearchLineEdit::slotTextChanged( const QString & text )
{
    Q_UNUSED(text);

    prepareLineEditForSearch();

    if ( m_findAsYouType )
        restartSearch();
    else
        m_changed = true;
}

void SearchLineEdit::prepareLineEditForSearch()
{
    QPalette pal = palette();
    const int textLength = text().length();
    if ( textLength > 0 && textLength < m_minLength )
    {
        const KColorScheme scheme( QPalette::Active, KColorScheme::View );
        pal.setBrush( QPalette::Base, scheme.background( KColorScheme::NegativeBackground ) );
        pal.setBrush( QPalette::Text, scheme.foreground( KColorScheme::NegativeText ) );
    }
    else
    {
        const QPalette qAppPalette = QApplication::palette();
        pal.setColor( QPalette::Base, qAppPalette.color( QPalette::Base ) );
        pal.setColor( QPalette::Text, qAppPalette.color( QPalette::Text ) );
    }
    setPalette( pal );
}

void SearchLineEdit::slotReturnPressed( const QString &text )
{
    Q_UNUSED(text);

    m_inputDelayTimer->stop();
    prepareLineEditForSearch();
    if ( QApplication::keyboardModifiers() == Qt::ShiftModifier )
    {
        m_searchType = Okular::Document::PreviousMatch;
        findPrev();
    }
    else
    {
        m_searchType = Okular::Document::NextMatch;
        findNext();
    }
}

void SearchLineEdit::startSearch()
{
    if ( m_id == -1 || !m_color.isValid() )
        return;

    if ( m_changed && ( m_searchType == Okular::Document::NextMatch || m_searchType == Okular::Document::PreviousMatch ) )
    {
        m_document->resetSearch( m_id );
    }
    m_changed = false;
    // search text if have more than 3 chars or else clear search
    QString thistext = text();
    if ( thistext.length() >= qMax( m_minLength, 1 ) )
    {
        emit searchStarted();
        m_searchRunning = true;
        m_document->searchText( m_id, thistext, m_fromStart, m_caseSensitivity,
                                m_searchType, m_moveViewport, m_color );
    }
    else
        m_document->resetSearch( m_id );
}

void SearchLineEdit::searchFinished( int id, Okular::Document::SearchStatus endStatus )
{
    // ignore the searches not started by this search edit
    if ( id != m_id )
        return;

    // if not found, use warning colors
    if ( endStatus == Okular::Document::NoMatchFound )
    {
        QPalette pal = palette();
        const KColorScheme scheme( QPalette::Active, KColorScheme::View );
        pal.setBrush( QPalette::Base, scheme.background( KColorScheme::NegativeBackground ) );
        pal.setBrush( QPalette::Text, scheme.foreground( KColorScheme::NegativeText ) );
        setPalette( pal );
    }
    else
    {
        QPalette pal = palette();
        const QPalette qAppPalette = QApplication::palette();
        pal.setColor( QPalette::Base, qAppPalette.color( QPalette::Base ) );
        pal.setColor( QPalette::Text, qAppPalette.color( QPalette::Text ) );
        setPalette( pal );
    }

    if ( endStatus == Okular::Document::EndOfDocumentReached ) {
        const bool forward = m_searchType == Okular::Document::NextMatch;
        const QString question = forward ? i18n("End of document reached.\nContinue from the beginning?") : i18n("Beginning of document reached.\nContinue from the bottom?");
        if ( KMessageBox::questionYesNo(window(), question, QString(), KStandardGuiItem::cont(), KStandardGuiItem::cancel()) == KMessageBox::Yes ) {
            m_document->continueSearch( m_id, m_searchType );
            return;
        }
    }

    m_searchRunning = false;
    emit searchStopped();
}


SearchLineWidget::SearchLineWidget( QWidget * parent, Okular::Document * document )
    : QWidget( parent )
{
    QHBoxLayout *layout = new QHBoxLayout( this );
    layout->setMargin( 0 );

    m_edit = new SearchLineEdit( this, document );
    layout->addWidget( m_edit );

    m_anim = new KPixmapSequenceWidget( this );
    m_anim->setFixedSize( 22, 22 );
    layout->addWidget( m_anim );
    m_anim->hide();

    m_timer = new QTimer( this );
    m_timer->setSingleShot( true );
    connect(m_timer, &QTimer::timeout, this, &SearchLineWidget::slotTimedout);

    connect(m_edit, &SearchLineEdit::searchStarted, this, &SearchLineWidget::slotSearchStarted);
    connect(m_edit, &SearchLineEdit::searchStopped, this, &SearchLineWidget::slotSearchStopped);
}

SearchLineEdit* SearchLineWidget::lineEdit() const
{
    return m_edit;
}

void SearchLineWidget::slotSearchStarted()
{
    m_timer->start( 100 );
}

void SearchLineWidget::slotSearchStopped()
{
    m_timer->stop();
    m_anim->hide();
}

void SearchLineWidget::slotTimedout()
{
    if ( m_anim->sequence().isEmpty() )
    {
        const KPixmapSequence seq = KIconLoader::global()->loadPixmapSequence(QStringLiteral("process-working"), 22);
        if ( seq.frameCount() > 0 )
        {
            m_anim->setInterval( 1000 / seq.frameCount() );
            m_anim->setSequence( seq );
        }
    }
    m_anim->show();
}

#include "moc_searchlineedit.cpp"
