/* -*- c++ -*-
 *
 * searchcore.cpp
 *
 * Copyright (C) 2003-2004 Petter Stokke <ummo@hellokitty.com>
 * Copyright (C) 2003-2004 Sebastian Sauer <mail@dipe.org>
 * Copyright (C) 2006      Christian Muehlhaeuser <chris@chris.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.
 *
 * 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 Steet, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */

#include <qcheckbox.h>
#include <qgroupbox.h>
#include <qlabel.h>
#include <qlayout.h>
#include <qregexp.h>
#include <qscrollview.h>
#include <qvbox.h>

#include <kcombobox.h>
#include <kconfig.h>
#include <kdebug.h>
#include <klineedit.h>
#include <klocale.h>
#include <knuminput.h>
#include <kpushbutton.h>

#include "kmldonkey.h"
#include "search.h"
#include "searchcore.h"
#include "searchcorewidgets.h"

#include <network.h>
#include <searchinfo.h>
#include <searchquery.h>

long int
filesizeStr2Int( QString filesize )
{
    QString s = filesize.lower().stripWhiteSpace();
    if ( s.isEmpty() )
        return 0;

    long int result;
    bool ok;

    if ( s.endsWith( "mb" ) || s.endsWith( "kb" ) )
        s.remove( s.length() - 1, 1 );

    if ( s.endsWith("m") ) // megabytes
        result = s.left( s.length() - 1 ).stripWhiteSpace().toLong() * 1048576;
    else
        if ( s.endsWith( "k" ) ) // kilobytes
            result = s.left( filesize.length() - 1 ).stripWhiteSpace().toLong() * 1024;
        else
            if ( s.endsWith( "b" ) ) // bytes
                result = s.left( filesize.length() - 1 ).stripWhiteSpace().toLong();
            else
            {
                // only numbers means bytes as well
                result = s.toLong( &ok );
                if ( !ok )
                    result = 0;
            }

    return ( result < 0 ? 0 : result );
}


SearchCore::SearchCore( QWidget *parent, SearchPage *page )
    : QVBox( parent, "searchCore" )
    , m_page( page )
    , m_form( 0 )
{
    searchNum = 0;
    maxHits = 500;
    searchType = 0;

    setMargin( 6 );
    setSpacing( 6 );

    // Form selector. The different forms are defined in
    // DonkeyProtocol::definedSearches(). So, they are core-depended
    // and configurable that way.
    m_formcombo = new KComboBox( this );
    connect( m_formcombo, SIGNAL( activated( int ) ), this, SLOT( activateFormCombo( int ) ) );

    // The scrollview is used to display the formwidgets.
    m_formscroll = new QScrollView( this );
    m_formscroll->setFrameStyle( QFrame::NoFrame );
    m_formscroll->setResizePolicy( QScrollView::AutoOneFit );
    m_formscroll->viewport()->setBackgroundMode( PaletteBackground );
    setStretchFactor( m_formscroll, 1 );

    // Following widgets are form independed. They are
    // always around.
    QVBox* box = new QVBox( this );
    box->setSpacing( 2 );

    // Max Hits
    QHBox *mhbox = new QHBox( box );
    mhbox->setSpacing( 2 );
    new QLabel( i18n( "Max hits:" ), mhbox );
    maxhitsEdit = new KIntNumInput( mhbox );
    mhbox->setStretchFactor( maxhitsEdit, 1 );
    maxhitsEdit->setRange( 1, 10000, 50, false );
    maxhitsEdit->setValue( maxHits );

    // Type
    QHBox *typebox = new QHBox( box );
    new QLabel(i18n( "search type", "Type:" ), typebox );
    searchtypeCombo = new KComboBox( typebox );
    typebox->setStretchFactor( searchtypeCombo, 1 );
    searchtypeCombo->insertItem(i18n( "remote search", "Remote" ), 0 );
    searchtypeCombo->insertItem(i18n( "local search", "Local" ), 1 );
    searchtypeCombo->insertItem(i18n( "subscription search", "Subscribe" ), 2 );
    searchtypeCombo->setCurrentItem( searchType );

    // Network
    QHBox *netbox = new QHBox( box );
    new QLabel( i18n( "Network:" ), netbox );
    searchnetCombo = new KComboBox( netbox );
    netbox->setStretchFactor( searchnetCombo, 1 );
    searchnetCombo->insertItem( i18n( "All Networks" ) );
    searchnetCombo->setCurrentItem( 0 );
    connect( KMLDonkey::App->donkey, SIGNAL( networkUpdated( int ) ), this, SLOT( setNetworks( int ) ) );

    // Start search button
    startButton = new KPushButton( i18n( "start a search", "Search" ), box );
    startButton->setEnabled( false );
    connect( startButton, SIGNAL( clicked() ), this, SLOT( startSearch() ) );

    // Update form
    if ( KMLDonkey::App->donkey->isConnected() )
        updatePages();

    connect( KMLDonkey::App->donkey, SIGNAL( definedSearchesUpdated() ), this, SLOT( updatePages() ) );
}


void SearchCore::restoreState( KConfig* conf )
{
    activateDef = conf->readEntry( "DefinedSearch", activateDef );
    maxHits = conf->readNumEntry( "maxHits", maxHits );
    maxhitsEdit->setValue( maxHits );

    int i = conf->readNumEntry( "Type", searchType );
    if ( i >= 0 && i <= 2 )
    {
        searchType = i;
        searchtypeCombo->setCurrentItem( searchType );
    }

    searchNetwork = conf->readEntry( "Network", QString::null );
}


void SearchCore::saveState(KConfig* conf)
{
    conf->writeEntry( "DefinedSearch", activateDef );
    conf->writeEntry( "maxHits", maxhitsEdit->value() );
    conf->writeEntry( "Type", searchtypeCombo->currentItem() );
    conf->writeEntry( "Network", searchnetCombo->currentText() );
}


void SearchCore::clear()
{
    clearFormWidget();
    clearFormQueries();
    searchnetCombo->setCurrentItem( 0 );

    for ( int i = searchnetCombo->count() - 1; i > 0; i-- )
        searchnetCombo->removeItem( i );
}


void SearchCore::activateActions()
{
    startButton->setEnabled( true );
}


void SearchCore::deactivateActions()
{
    startButton->setEnabled( false );
}


void SearchCore::clearFormWidget()
{
    delete m_form;
    m_form = 0;
}


void SearchCore::clearFormQueries()
{
    m_formqueries.clear();
    m_formcombo->clear();
}


void SearchCore::setNetworks( int no )
{
    Network *net = KMLDonkey::App->donkey->findNetworkNo( no );
    if ( !net )
        return;

    for ( int i = searchnetCombo->count() - 1; i > 0; i-- )
        if ( searchnetCombo->text( i ) == net->networkName() )
        {
            // already on the list?
            if ( !net->networkEnabled() )
                searchnetCombo->removeItem( i );
            return;
        }

    if ( !net->networkEnabled() )
        return;
    if ( !( net->networkFlags() & Network::NetworkHasSearch ) )
        return;

    searchnetCombo->insertItem( net->networkName() );
    if ( searchNetwork == net->networkName() )
        searchnetCombo->setCurrentItem( searchnetCombo->count() - 1 );
}


void SearchCore::updatePages()
{
    clearFormWidget();
    clearFormQueries();

    int defitem = -1;
    QString defsearch = KMLDonkey::App->donkey->definedSearch();
    QMap<QString, SearchQuery*> searches = KMLDonkey::App->donkey->definedSearches();

    for ( QMap<QString, SearchQuery*>::iterator it = searches.begin(); it != searches.end(); ++it )
    {
        m_formcombo->insertItem( it.key() );
        m_formqueries.replace( it.key(), it.data() );
        if ( it.key() == activateDef || (it.key() == defsearch && defitem < 0 ) )
            defitem = m_formcombo->count() - 1;
    }

    if ( defitem >= 0 )
    {
        m_formcombo->setCurrentItem( defitem );
        activateFormCombo( defitem );
    }
}


void SearchCore::activateFormCombo( int index )
{
    if ( m_formcombo->currentItem() != index )
        return;

    clearFormWidget();
    SearchQuery *query = m_formqueries[ m_formcombo->currentText() ];
    if ( !query )
        return;

    activateDef = m_formcombo->currentText();
    m_form = new QVBox( m_formscroll->viewport() );
    m_form->setSpacing( 2 );
    m_formscroll->addChild( m_form );

    // Cause we don't handle SearchQuery->operation() at this time we just
    // have to create a temp SearchCoreWidget and delete it after the job
    // is done (means after the SearchCoreWidget-structur gots build).
    // The parent-QWidget m_form gots passed through the temp widget to it's
    // first valid child. So, we don't have to worry about this one ;)
    SearchCoreWidget* tmpwidget = new SearchCoreWidget( this, m_form, 0 );
    m_formwidget = tmpwidget->setQuery( query );
    delete tmpwidget;
    tmpwidget = 0;

    // It's possible that m_formwidget contains NULL here. That would mean
    // there isn't any valid SearchCoreWidget-child under m_formwidget and
    // the SearchQuery is just invalid.
    if ( !m_formwidget )
    {
        clearFormWidget();
        return;
    }

    m_form->setStretchFactor( new QWidget( m_form ), 1 );
    m_form->show();
}


void SearchCore::startSearch()
{
    if ( !KMLDonkey::App->donkey->isConnected() || !m_form )
        return;

    SearchQuery *query = m_formwidget->getQuery();
    if ( !query )
    {
        kdDebug() << "SearchCore::startSearch() Invalid SearchQuery!" << endl;
        return;
    }
    kdDebug() << "SearchCore::startSearch() " << query->getQuerystring() << endl;

    // Max hits
    maxHits = maxhitsEdit->value();

    // Network
    int network = 0;
    if ( searchnetCombo->currentItem() > 0 )
    {
        QIntDictIterator<Network> it( KMLDonkey::App->donkey->availableNetworks() );
        for ( ; it.current(); ++it )
            if ( it.current()->networkName() == searchnetCombo->currentText() )
            {
                network = it.current()->networkNo();
                break;
            }
    }

    // Type
    DonkeyProtocol::SearchType type;
    switch ( searchtypeCombo->currentItem() )
    {
        case 1:
            type = DonkeyProtocol::LocalSearch;
            break;

        case 2:
            type = DonkeyProtocol::SubscribeSearch;
            break;

        default:
            type = DonkeyProtocol::RemoteSearch;
            break;
    }

    // Start the search only if searchtermins like keywords or artist got
    // defined cause to search e.g. for filesize only doesn't make sense.
    QString label = m_formwidget->getDescription();
    if ( !label.isEmpty() )
    {
        // Get a free new searchNum
        QIntDict<SearchInfo> searches = KMLDonkey::App->donkey->activeSearches();
        do { searchNum++; }
        while ( searches.find( searchNum ) != 0 );

        // Add a new searchtabresult
        m_page->addSearchResult( searchNum, query, label );
        // Let's start the search now
        KMLDonkey::App->donkey->startSearch( searchNum, query, maxHits, type, network );
    }

    // free the temp SearchQuery created above
    delete query;
}

/*  TODO
    // Keywords
    QString keywords = keywordsEdit->text().stripWhiteSpace();
    if(! keywords.isEmpty()) {
        QRegExp rxkeywords("([\\s]+|^)(\\-|\\+)[\\s]*");
        keywords.replace(rxkeywords, "\\1\\2");
        QStringList keywordlist = QStringList::split(" ", keywords);
        for(QStringList::Iterator it = keywordlist.begin(); it != keywordlist.end(); ++it) {
            if((*it).startsWith("-"))
                query = new QueryAndNot(query ? query : qand, new QueryKeywords((QString&)QString::null, (*it).mid(1)));
            else
                qand->append(new QueryKeywords((QString&)QString::null, (*it).startsWith("+") ? (*it).mid(1) : (*it)));
        }
        tablabel = keywords;
    }
*/

#include "searchcore.moc"
