/***************************************************************************
                          finddialog.cpp  -  K Desktop Planetarium
                             -------------------
    begin                : Wed Jul 4 2001
    copyright            : (C) 2001 by Jason Harris
    email                : jharris@30doradus.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 "finddialog.h"

#include "kstarsdata.h"
#include "Options.h"
#include "detaildialog.h"
#include "skyobjects/skyobject.h"
#include "skyobjects/deepskyobject.h"
#include "skycomponents/starcomponent.h"
#include "skycomponents/syncedcatalogcomponent.h"
#include "skycomponents/skymapcomposite.h"
#include "tools/nameresolver.h"
#include "skyobjectlistmodel.h"

#include <KMessageBox>

#include <QSortFilterProxyModel>
#include <QStringListModel>
#include <QTimer>

FindDialogUI::FindDialogUI( QWidget *parent ) : QFrame( parent ) {
    setupUi( this );

    FilterType->addItem( i18n ("Any") );
    FilterType->addItem( i18n ("Stars") );
    FilterType->addItem( i18n ("Solar System") );
    FilterType->addItem( i18n ("Open Clusters") );
    FilterType->addItem( i18n ("Globular Clusters") );
    FilterType->addItem( i18n ("Gaseous Nebulae") );
    FilterType->addItem( i18n ("Planetary Nebulae") );
    FilterType->addItem( i18n ("Galaxies") );
    FilterType->addItem( i18n ("Comets") );
    FilterType->addItem( i18n ("Asteroids") );
    FilterType->addItem( i18n ("Constellations") );
    FilterType->addItem( i18n ("Supernovae") );
    FilterType->addItem( i18n ("Satellites") );

    SearchList->setMinimumWidth( 256 );
    SearchList->setMinimumHeight( 320 );
}

FindDialog::FindDialog( QWidget* parent ) :
    QDialog( parent ),
    timer(0),
    m_targetObject( 0 )
{
#ifdef Q_OS_OSX
        setWindowFlags(Qt::Tool| Qt::WindowStaysOnTopHint);
#endif
    ui = new FindDialogUI( this );

    setWindowTitle( i18n( "Find Object" ) );

    QVBoxLayout *mainLayout = new QVBoxLayout;
    mainLayout->addWidget(ui);
    setLayout(mainLayout);

    QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel);
    mainLayout->addWidget(buttonBox);
    connect(buttonBox, SIGNAL(accepted()), this, SLOT(slotOk()));
    connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));

    okB = buttonBox->button(QDialogButtonBox::Ok);

    QPushButton *detailB = new QPushButton(i18n("Details..."));
    buttonBox->addButton(detailB, QDialogButtonBox::ActionRole);
    connect(detailB, SIGNAL(clicked()), this, SLOT(slotDetails()));

    ui->InternetSearchButton->setVisible( Options::resolveNamesOnline() );
    ui->InternetSearchButton->setEnabled( false );
    connect( ui->InternetSearchButton, SIGNAL( clicked() ), this, SLOT( slotResolve() ) );

    ui->FilterType->setCurrentIndex(0);  // show all types of objects

    fModel = new SkyObjectListModel( this );
    sortModel = new QSortFilterProxyModel(ui->SearchList);
    sortModel->setFilterCaseSensitivity( Qt::CaseInsensitive );
    sortModel->setSourceModel( fModel );
    sortModel->setSortRole(Qt::DisplayRole);
    sortModel->setFilterRole(Qt::DisplayRole);
    sortModel->setDynamicSortFilter(true);
    sortModel->sort( 0 );

    ui->SearchList->setModel( sortModel );

    // Connect signals to slots
    connect( ui->SearchBox, SIGNAL( textChanged( const QString & ) ), SLOT( enqueueSearch() ) );
    connect( ui->SearchBox, SIGNAL( returnPressed() ), SLOT( slotOk() ) );
    connect( ui->FilterType, SIGNAL( activated( int ) ), this, SLOT( enqueueSearch() ) );
    connect( ui->SearchList, SIGNAL( doubleClicked( const QModelIndex & ) ), SLOT( slotOk() ) );

    // Set focus to object name edit
    ui->SearchBox->setFocus();

    // First create and paint dialog and then load list
    QTimer::singleShot(0, this, SLOT( init() ));

    listFiltered = false;
}

FindDialog::~FindDialog() { }

void FindDialog::init() {
    ui->SearchBox->clear();
    filterByType();
    sortModel->sort( 0 );
    initSelection();
    m_targetObject = 0;
}

void FindDialog::initSelection() {
    if ( sortModel->rowCount() <= 0 )
    {
        okB->setEnabled( false );
        return;
    }

    if ( ui->SearchBox->text().isEmpty() ) {
        //Pre-select the first item
        QModelIndex selectItem = sortModel->index( 0, sortModel->filterKeyColumn(), QModelIndex() );
        switch ( ui->FilterType->currentIndex() ) {
        case 0: //All objects, choose Andromeda galaxy
            {
                QModelIndex qmi = fModel->index( fModel->indexOf( i18n("Andromeda Galaxy") ) );
                selectItem = sortModel->mapFromSource( qmi );
                break;
            }
        case 1: //Stars, choose Aldebaran
            {
                QModelIndex qmi = fModel->index( fModel->indexOf( i18n("Aldebaran") ) );
                selectItem = sortModel->mapFromSource( qmi );
                break;
            }
        case 2: //Solar system or Asteroids, choose Aaltje
        case 9:
            {
                QModelIndex qmi = fModel->index( fModel->indexOf( i18n("Aaltje") ) );
                selectItem = sortModel->mapFromSource( qmi );
                break;
            }
        case 8: //Comets, choose 'Aarseth-Brewington (1989 W1)'
            {
                QModelIndex qmi = fModel->index( fModel->indexOf( i18n("Aarseth-Brewington (1989 W1)") ) );
                selectItem = sortModel->mapFromSource( qmi );
                break;
            }

        }

        if ( selectItem.isValid() ) {
            ui->SearchList->selectionModel()->select( selectItem, QItemSelectionModel::ClearAndSelect );
            ui->SearchList->scrollTo( selectItem );
            ui->SearchList->setCurrentIndex( selectItem );

            okB->setEnabled(true);
        }
    }

    listFiltered = true;
}

void FindDialog::filterByType() {
    KStarsData *data = KStarsData::Instance();

    switch ( ui->FilterType->currentIndex() ) {
    case 0: // All object types
    {
        QVector<QPair<QString, const SkyObject *>> allObjects;
        foreach( int type, data->skyComposite()->objectLists().keys() ) {
            allObjects.append(data->skyComposite()->objectLists(SkyObject::TYPE(type)));
        }
        fModel->setSkyObjectsList( allObjects );
        break;
    }
    case 1: //Stars
    {
        QVector<QPair<QString, const SkyObject *>> starObjects;
        starObjects.append(data->skyComposite()->objectLists(SkyObject::STAR));
        starObjects.append(data->skyComposite()->objectLists(SkyObject::CATALOG_STAR));
        fModel->setSkyObjectsList( starObjects );
        break;
    }
    case 2: //Solar system
    {
        QVector<QPair<QString, const SkyObject *>> ssObjects;
        ssObjects.append(data->skyComposite()->objectLists(SkyObject::PLANET));
        ssObjects.append(data->skyComposite()->objectLists(SkyObject::COMET));
        ssObjects.append(data->skyComposite()->objectLists(SkyObject::ASTEROID));
        ssObjects.append(data->skyComposite()->objectLists(SkyObject::MOON));

        fModel->setSkyObjectsList(ssObjects);
        break;
    }
    case 3: //Open Clusters
        fModel->setSkyObjectsList( data->skyComposite()->objectLists( SkyObject::OPEN_CLUSTER ) );
        break;
    case 4: //Globular Clusters
        fModel->setSkyObjectsList( data->skyComposite()->objectLists( SkyObject::GLOBULAR_CLUSTER ) );
        break;
    case 5: //Gaseous nebulae
        fModel->setSkyObjectsList( data->skyComposite()->objectLists( SkyObject::GASEOUS_NEBULA ) );
        break;
    case 6: //Planetary nebula
        fModel->setSkyObjectsList( data->skyComposite()->objectLists( SkyObject::PLANETARY_NEBULA ) );
        break;
    case 7: //Galaxies
        fModel->setSkyObjectsList( data->skyComposite()->objectLists( SkyObject::GALAXY ) );
        break;
    case 8: //Comets
        fModel->setSkyObjectsList( data->skyComposite()->objectLists( SkyObject::COMET ) );
        break;
    case 9: //Asteroids
        fModel->setSkyObjectsList( data->skyComposite()->objectLists( SkyObject::ASTEROID ) );
        break;
    case 10: //Constellations
        fModel->setSkyObjectsList( data->skyComposite()->objectLists( SkyObject::CONSTELLATION ) );
        break;
    case 11: //Supernovae
        fModel->setSkyObjectsList( data->skyComposite()->objectLists( SkyObject::SUPERNOVA ) );
        break;
    case 12: //Satellites
        fModel->setSkyObjectsList( data->skyComposite()->objectLists( SkyObject::SATELLITE ) );
        break;
    }
}

void FindDialog::filterList() {
    QString SearchText = processSearchText();
    sortModel->setFilterFixedString( SearchText );
    ui->InternetSearchButton->setText( i18n( "or search the internet for %1", SearchText ) );
    filterByType();
    initSelection();

    //Select the first item in the list that begins with the filter string
    if ( !SearchText.isEmpty() ) {
        QStringList mItems = fModel->filter( QRegExp( '^'+SearchText, Qt::CaseInsensitive ) );
        mItems.sort();

        if ( mItems.size() ) {
            QModelIndex qmi = fModel->index( fModel->indexOf( mItems[0] ) );
            QModelIndex selectItem = sortModel->mapFromSource( qmi );

            if ( selectItem.isValid() ) {
                ui->SearchList->selectionModel()->select( selectItem, QItemSelectionModel::ClearAndSelect );
                ui->SearchList->scrollTo( selectItem );
                ui->SearchList->setCurrentIndex( selectItem );

                okB->setEnabled(true);
            }
        }
        ui->InternetSearchButton->setEnabled( ! mItems.contains( SearchText ) ); // Disable searching the internet when an exact match for SearchText exists in KStars
    }
    else
        ui->InternetSearchButton->setEnabled( false );

    listFiltered = true;
}

SkyObject* FindDialog::selectedObject() const {
    QModelIndex i = ui->SearchList->currentIndex();
    QVariant sObj = sortModel->data(sortModel->index(i.row(), 0), SkyObjectListModel::SkyObjectRole);
    return (SkyObject *) sObj.value<void *>();
}

void FindDialog::enqueueSearch() {
    listFiltered = false;
    if ( timer ) {
        timer->stop();
    } else {
        timer = new QTimer( this );
        timer->setSingleShot( true );
        connect( timer, SIGNAL( timeout() ), this, SLOT( filterList() ) );
    }
    timer->start( 500 );
}

// Process the search box text to replace equivalent names like "m93" with "m 93"
QString FindDialog::processSearchText() {
    QRegExp re;
    QString searchtext = ui->SearchBox->text();

    re.setCaseSensitivity( Qt::CaseInsensitive );

    // If it is an NGC/IC/M catalog number, as in "M 76" or "NGC 5139", check for absence of the space
    re.setPattern("^(m|ngc|ic)\\s*\\d*$");
    if(ui->SearchBox->text().contains(re)) {
        re.setPattern("\\s*(\\d+)");
        searchtext.replace( re, " \\1" );
        re.setPattern("\\s*$");
        searchtext.remove( re );
        re.setPattern("^\\s*");
        searchtext.remove( re );
    }

    // TODO after KDE 4.1 release:
    // If it is a IAU standard three letter abbreviation for a constellation, then go to that constellation
    // Check for genetive names of stars. Example: alp CMa must go to alpha Canis Majoris

    return searchtext;
}

void FindDialog::slotOk() {
    //If no valid object selected, show a sorry-box.  Otherwise, emit accept()
    SkyObject *selObj;
    if(!listFiltered) {
        filterList();
    }
    selObj = selectedObject();
    finishProcessing( selObj, Options::resolveNamesOnline() );
}

void FindDialog::slotResolve() {
    finishProcessing( 0, true );
}

void FindDialog::finishProcessing( SkyObject *selObj, bool resolve ) {
    if( ! selObj && resolve ) {
        CatalogEntryData cedata;
        cedata = NameResolver::resolveName( processSearchText() );
        DeepSkyObject *dso = 0;
        if( ! std::isnan( cedata.ra ) && ! std::isnan( cedata.dec ) ) {
            dso = KStarsData::Instance()->skyComposite()->internetResolvedComponent()->addObject( cedata );
            if( dso )
                qDebug() << dso->ra0().toHMSString() << ";" << dso->dec0().toDMSString();
            selObj = dso;
        }
    }
    m_targetObject = selObj;
    if ( selObj == 0 ) {
        QString message = i18n( "No object named %1 found.", ui->SearchBox->text() );
        KMessageBox::sorry( 0, message, i18n( "Bad object name" ) );
    } else {
        selObj->updateCoordsNow(KStarsData::Instance()->updateNum());
        accept();
    }
}
void FindDialog::keyPressEvent( QKeyEvent *e ) {
    switch( e->key() ) {
    case Qt::Key_Escape :
        reject();
        break;
    case Qt::Key_Up :
    {
        int currentRow = ui->SearchList->currentIndex().row();
        if ( currentRow > 0 ) {
            QModelIndex selectItem = sortModel->index( currentRow-1, sortModel->filterKeyColumn(), QModelIndex() );
            ui->SearchList->selectionModel()->setCurrentIndex( selectItem, QItemSelectionModel::SelectCurrent );
        }
        break;
    }
    case Qt::Key_Down :
    {
        int currentRow = ui->SearchList->currentIndex().row();
        if ( currentRow < sortModel->rowCount()-1 ) {
            QModelIndex selectItem = sortModel->index( currentRow+1, sortModel->filterKeyColumn(), QModelIndex() );
            ui->SearchList->selectionModel()->setCurrentIndex( selectItem, QItemSelectionModel::SelectCurrent );
        }
        break;
    }
    }
}

void FindDialog::slotDetails()
{
    if ( selectedObject() ) {
        QPointer<DetailDialog> dd = new DetailDialog( selectedObject(), KStarsData::Instance()->ut(), KStarsData::Instance()->geo(), KStars::Instance());
        dd->exec();
        delete dd;
    }
}


