/***************************************************************************
 *   Copyright (C) 2004-2006 by Pere Constans
 *   constans@molspaces.com
 *   cb2Bib version 0.6.1
 *   See LICENSE file that comes with this distribution
 *
 *   The LyX pipe procedure in citeEntry has been adapted from Tellico
 *   (Tellico (C) 2003-2005 by Robby Stephenson)
 ***************************************************************************/
#include "referencelist.h"
#include "cb2bib_par.h"
#include "c2btextedit.h"
#include "c2butils.h"

#include <qaction.h>
#include <qapplication.h>
#include <qclipboard.h>
#include <qcombobox.h>
#include <qcursor.h>
#include <qdir.h>
#include <qfiledialog.h>
#include <qfile.h>
#include <qlistview.h>
#include <qmessagebox.h>
#include <qpopupmenu.h>
#include <qregexp.h>
#include <qsettings.h>
#include <qtimer.h>

#ifndef Q_WS_WIN
#include <fcntl.h>     // fifo to lyx
#include <unistd.h>
#endif



ReferenceList::ReferenceList(c2bTextEdit* ed,
                             QWidget* parent, const char* name, WFlags fl)
        : ReferenceListBase(parent,name,fl)
{
    edPtr = ed;
    // Columns 1 and 2 contain DOI and file; its only use is for article browsing
    listView->setColumnWidthMode(1, QListView::Manual);
    listView->setColumnWidth(1, 0);
    listView->setColumnWidthMode(2, QListView::Manual);
    listView->setColumnWidth(2, 0);
    // To avoid Authors and Journal to be unnecessarily large
    listView->setColumnWidthMode(3, QListView::Manual);
    listView->setColumnWidthMode(5, QListView::Manual);

    ref_list_m = new QPopupMenu();
    Q_CHECK_PTR( ref_list_m );
    connect( listView, SIGNAL(contextMenuRequested( QListViewItem*, const QPoint&, int )),
             this, SLOT( showMenu( QListViewItem*, const QPoint & ) ) );

#ifndef Q_WS_WIN

    QSettings settings;
    settings.insertSearchPath( QSettings::Windows, WINDOWS_REGISTRY );
    lyxpipe = QDir::convertSeparators(settings.readEntry( APP_KEY + "lyxpipe",
                                      APP_LYX_PIPE ));
    if ( QDir::isRelativePath( lyxpipe ) )
        lyxpipe = QDir::homeDirPath() + QDir::separator() + lyxpipe;

    citeEntryAction = new QAction( this, "citeEntryAction" );
    citeEntryAction->setAccel( tr( "Alt+E" ) );
    citeEntryAction->setMenuText( tr( "Cite Selected &Entries" ) );
    citeEntryAction->setText( tr( "Cite Selected Entries -- LyX pipe" ) );
    connect( citeEntryAction, SIGNAL( activated() ),
             this, SLOT( citeEntry() ) );
    citeEntryAction->addTo( ref_list_m );
#endif

    citeEntry2CbAction = new QAction( this, "citeEntry2CbAction" );
    citeEntry2CbAction->setMenuText( tr( "Cite to Clipboard" ) );
    citeEntry2CbAction->setText( tr( "Cite Selected Entries to Clipboard" ) );
    connect( citeEntry2CbAction, SIGNAL( activated() ),
             this, SLOT( citeEntry2Cb() ) );
    citeEntry2CbAction->addTo( ref_list_m );
    ref_list_m->insertSeparator();
    openFileAction = new QAction( this, "openFileAction" );
    openFileAction->setMenuText( tr( "Open Article File" ) );
    openFileAction->setText( tr( "Open Article File" ) );
    connect( openFileAction, SIGNAL( activated() ),
             this, SLOT( openFile() ) );
    openFileAction->addTo( ref_list_m );
    openDOIAction = new QAction( this, "openDOIAction" );
    openDOIAction->setMenuText( tr( "Browse by DOI" ) );
    openDOIAction->setText( tr( "Browse by DOI" ) );
    connect( openDOIAction, SIGNAL( activated() ),
             this, SLOT( openDOI() ) );
    openDOIAction->addTo( ref_list_m );
    openWebSearchAction = new QAction( this, "openWebSearchAction" );
    openWebSearchAction->setMenuText( tr( "Web Search by Title" ) );
    openWebSearchAction->setText( tr( "Web Search by Title" ) );
    connect( openWebSearchAction, SIGNAL( activated() ),
             this, SLOT( openWebSearch() ) );
    openWebSearchAction->addTo( ref_list_m );
    ref_list_m->insertSeparator();
    refreshListAction = new QAction( this, "refreshListAction" );
    refreshListAction->setMenuText( tr( "Refresh List" ) );
    refreshListAction->setText( tr( "Refresh List" ) );
    connect( refreshListAction, SIGNAL( activated() ),
             this, SLOT( refreshList() ) );
    refreshListAction->addTo( ref_list_m );

    connect( listView, SIGNAL(doubleClicked( QListViewItem*, const QPoint&, int )),
             this, SLOT( goToEntry( QListViewItem* ) ) );
    connect( listView, SIGNAL(returnPressed( QListViewItem* )),
             this, SLOT( goToEntry( QListViewItem* ) ) );
    connect( comboFind, SIGNAL( textChanged(const QString&) ),
             this, SLOT( blockComboFindSignals() ) );
}

ReferenceList::~ReferenceList()
{
    listView->clear();
    delete listView;
    delete ref_list_m;
}


void ReferenceList::loadList( const QString& txt )
{
    QApplication::setOverrideCursor( QCursor(Qt::WaitCursor) );
    QRegExp rx_ref( "\\n(@\\w+[\\{\"])([\\w:\\.]+),(.+)(\\n@|\\n%|$)" );
    rx_ref.setMinimal(TRUE);
    QRegExp rx_author( "\\bauthor\\s*=\\s*[\\{\"](.*)[\\}\"]\\,", FALSE );
    rx_author.setMinimal(TRUE);
    QRegExp rx_title( "\\btitle\\s*=\\s*[\\{\"](.*)[\\}\"]\\,", FALSE );
    rx_title.setMinimal(TRUE);
    QRegExp rx_journal( "\\bjournal\\s*=\\s*[\\{\"](.*)[\\}\"]\\,", FALSE );
    rx_journal.setMinimal(TRUE);
    QRegExp rx_year( "\\byear\\s*=\\s*[\\{\"](.*)[\\}\"]\\,", FALSE );
    rx_year.setMinimal(TRUE);
    QRegExp rx_doi( "\\bdoi\\s*=\\s*[\\{\"](.*)[\\}\"]\\,", FALSE );
    rx_doi.setMinimal(TRUE);
    QRegExp rx_file( "\\bfile\\s*=\\s*[\\{\"](.*)[\\}\"]\\,", FALSE );
    rx_file.setMinimal(TRUE);

    int pos = 0;
    while ( pos >= 0 )
    {
        QString ref = "";
        QString key = "";
        QString author = "";
        QString year = "";
        QString title = "";
        QString journal = "";
        QString doi = "";
        QString file = "";
        pos = rx_ref.search( txt, pos );
        if ( pos > -1 )
        {
            pos  += rx_ref.matchedLength() - 3;
            ref = rx_ref.cap(3);
            ref = ref.simplifyWhiteSpace();
            ref.replace( QRegExp( "[\\}\"]\\s\\}$" ), "},}" );
            key = rx_ref.cap(2);
            if( rx_author.search( ref ) > -1 )
            {
                author = Bibtoc2b( rx_author.cap(1) );
                author.remove( QRegExp( "\\b\\w\\b" ) );
                author.remove( QRegExp( "[^\\w\\s]" ) );
                author.replace( " and ", ", " );
                author = author.simplifyWhiteSpace();
            }
            if( rx_title.search( ref ) > -1 )
            {
                title = Bibtoc2b( rx_title.cap(1) );
                title.remove( QRegExp( "^[\\{\\s]+" ) );
                title.remove( QRegExp( "[\\}\\s]+$" ) );
            }
            if( rx_journal.search( ref ) > -1 )
                journal = Bibtoc2b( rx_journal.cap(1).stripWhiteSpace() );
            if( rx_year.search( ref ) > -1 )
                year = rx_year.cap(1).stripWhiteSpace();
            if( rx_doi.search( ref ) > -1 )
                doi = rx_doi.cap(1).stripWhiteSpace();
            if( rx_file.search( ref ) > -1 )
                file = rx_file.cap(1).stripWhiteSpace();
            new QListViewItem( listView, key, doi, file, author, year, journal, title );
        }
    }
    int w = listView->columnWidth(0);
    listView->setColumnWidth(1, 0);
    listView->setColumnWidth(2, 0);
    listView->setColumnWidth( 3, 2*w );
    listView->setColumnWidth( 5, int(1.5*w) );
    QApplication::restoreOverrideCursor();
}

void ReferenceList::showMenu( QListViewItem* ref, const QPoint& p )
{
    if ( ref == 0 )
        return;
    ref_list_m->exec( p );
}

void ReferenceList::citeEntry()
{
#ifndef Q_WS_WIN
    // This procedure has been adapted from Tellico
    // Tellico (C) 2003-2005 by Robby Stephenson
    QCString pipe = QFile::encodeName(lyxpipe);
    QString errorStr = tr("Unable to write to the server pipe at '%1'.").arg(pipe);

    if(!QFile::exists(pipe))
    {
        QMessageBox::information(
            this, tr("cb2Bib Info"), errorStr, tr("&Continue") );
        return;
    }

    int pipeFd = ::open(pipe, O_WRONLY);
    QFile file(QString::fromUtf8(pipe));
    if(file.open(IO_WriteOnly, pipeFd))
    {
        QString refs;
        QListViewItemIterator it( listView );
        while ( it.current() )
        {
            QListViewItem *item = it.current();
            if ( item->isSelected() && item->isVisible() && (!item->text(0).isEmpty()) )
                refs += item->text(0) + " ";
            ++it;
        }
        refs = refs.stripWhiteSpace();
        // pybliographer uses comma-space, and pyblink expects the space there
        refs.replace( " ", ", ");
        QTextStream st( &file );
        st << QString::fromLatin1("LYXCMD:cb2bib:citation-insert:%1\n").arg(refs).latin1();
        file.flush();
        file.close();
    }
    else
        QMessageBox::information(
            this, tr("cb2Bib Info"), errorStr, tr("&Continue") );
    ::close(pipeFd);
#endif
}

void ReferenceList::citeEntry2Cb()
{
    QString refs;
    QListViewItemIterator it( listView );
    while ( it.current() )
    {
        QListViewItem *item = it.current();
        if ( item->isSelected() && item->isVisible() && (!item->text(0).isEmpty()) )
            refs += item->text(0) + " ";
        ++it;
    }
    refs = refs.stripWhiteSpace();
    refs.replace( " ", ", ");
    refs = "\\cite{" + refs + "}";
    QClipboard *cb = QApplication::clipboard();
    cb->setText( refs, QClipboard::Clipboard );
}

void ReferenceList::openDOI()
{
    bool zdoi = FALSE;
    QListViewItemIterator it( listView );
    while ( it.current() )
    {
        QListViewItem *item = it.current();
        if ( item->isSelected() && item->isVisible() && (!item->text(1).isEmpty()) )
        {
            zdoi = TRUE;
            QString doi = item->text(1);
            doi = "http://dx.doi.org/" + doi;
            emit openFileRequested( doi );
        }
        ++it;
    }
    if ( !zdoi )
        emit statusMessage( tr("No DOI entry among selected references.") );
}

void ReferenceList::openFile()
{
    bool zfile = FALSE;
    QListViewItemIterator it( listView );
    while ( it.current() )
    {
        QListViewItem *item = it.current();
        if ( item->isSelected() && item->isVisible() && (!item->text(2).isEmpty()) )
        {
            zfile = TRUE;
            emit openFileRequested( item->text(2) );
        }
        ++it;
    }
    if ( !zfile )
        emit statusMessage( tr("No file entry among selected references.") );
}

void ReferenceList::openWebSearch()
{
    bool zwebs = FALSE;
    QListViewItemIterator it( listView );
    while ( it.current() )
    {
        QListViewItem *item = it.current();
        if ( item->isSelected() && item->isVisible() && (!item->text(6).isEmpty()) )
        {
            zwebs = TRUE;
            QString webs = item->text(6);
            webs = "http://www.google.com/search?q=" + webs;
            emit openFileRequested( webs );
        }
        ++it;
    }
    if ( !zwebs )
        emit statusMessage( tr("No title entry among selected references.") );
}

void ReferenceList::refreshList()
{
    listView->clear();
    comboFind->clearEdit();
    loadList( edPtr->text() );
}

void ReferenceList::goToEntry( QListViewItem* ref )
{
    if ( ref == 0 )
        return;
    QString entry = ref->text(0);
    if ( entry.isEmpty() )
        return;
    int para = 1;
    int index = 1;
    edPtr->setCursorPosition( para, index );
    entry += ',';
    if ( edPtr->find( entry, TRUE, TRUE, TRUE, &para, &index ) )
    {
        edPtr->scrollBy( 0, edPtr->visibleHeight() );
        edPtr->ensureCursorVisible();
    }
}

void ReferenceList::blockComboFindSignals()
{
    comboFind->blockSignals( TRUE );
    QTimer::singleShot(300, this, SLOT(searchInList()));
}

void ReferenceList::searchInList()
{
    QString txt = comboFind->currentText();
    QListViewItemIterator it( listView );
    while ( it.current() )
    {
        QListViewItem *item = it.current();
        if ( item->text(3).contains( txt, FALSE ) || item->text(6).contains( txt, FALSE ) )
            item->setVisible( TRUE );
        else
            item->setVisible( FALSE );
        ++it;
    }
    comboFind->blockSignals( FALSE );
}

void ReferenceList::setLyxPipe()
{
    QString new_pn = QFileDialog::getSaveFileName( lyxpipe, "All (*)", 0, "save file dialog",
                     "Select a LyX pipe" );
    if( ! new_pn.isEmpty() )
    {
        lyxpipe = new_pn;
        QSettings settings;
        settings.insertSearchPath( QSettings::Windows, WINDOWS_REGISTRY );
        settings.writeEntry( APP_KEY + "lyxpipe", lyxpipe );
    }
}
