/***************************************************************************
 *   Copyright (C) 2004-2009 by Pere Constans
 *   constans@molspaces.com
 *   cb2Bib version 1.3.2. Licensed under the GNU GPL version 3.
 *   See the LICENSE file that comes with this distribution.
 ***************************************************************************/
#include "c2bCiter.h"

#include "c2b.h"
#include "c2bCiterModel.h"
#include "c2bCoreCiter.h"
#include "c2bFileDialog.h"
#include "c2bSettings.h"
#include "c2bUtils.h"

#include <QKeyEvent>
#include <QLabel>
#include <QMenu>

#ifdef Q_WS_X11
#include <QX11Info>
#include <X11/Xatom.h>
#include <X11/Xlib.h>
#endif


/** \page c2bciter The cb2Bib Citer

    The cb2Bib Citer is a keyboard based module for inserting citation IDs into
    a working document. Conveniently, the command <tt>c2bciter</tt>, or its
    expansion <tt>'cb2bib --citer'</tt>, can be assigned to a global, desktop
    wide shortcut key. This will provide an easy access to the citer from
    within any text editor. Pressing the shortcut turns on and off the citer
    panel. Once appropriate references are selected, pressing key C sets the
    citations either to the clipboard or to a LyX pipe, closes the citer panel,
    and returns keyboard focus to the editor.

    By default, <tt>c2bciter</tt> loads all references from the current
    directory, specified in the cb2Bib main panel. On the desktop tray, the
    cb2Bib icon indicates that the citer is running. Its context menu offers
    the possibility to load other files or directories, or to toggle full
    screen mode.

    Search, filtering, navigation, and citation are keyword based. Pressing
    keys A, J, T, and Y sorts the references by author, journal, title, and
    year, respectively. Key F initiates filtering, and Esc leaves filtering
    mode. References are selected when pressing enter. Key S toggles the
    current selection display, and Del clears the selection. The combination
    Shift + letter navigates through the rows starting by the letter.


    <p>&nbsp;</p>

\verbatim
   Usage:      cb2bib --citer [dirname1 [dirname2 ... ]]
               cb2bib --citer [filename1.bib [filename2.bib ... ]]

\endverbatim

\verbatim
   Display Keys
      A        author - journal - year - title
      J        journal - year - author
      T        title
      Y        year - author - journal - title

\endverbatim

\verbatim
   Action Keys
      C        Cite selected citations and close citer window
      Del      Unselect all citations
      E        Edit current citation's source
      Enter    Select current citation
      Esc      Exit filter mode or close citer window
      F        Enter filter mode
      O        Open current citation's file
      S        Display the set of selected citations
      U        Open current citation's URL

\endverbatim

\verbatim
   Other Shortcuts
      Alt+O    Open BibTeX files
      Ctrl+F   Search In Files
      Ctrl+O   Open BibTeX directory
      Shift+   Keyboard search naviagation

\endverbatim

    <p>&nbsp;</p>

    See also \ref relnotes130, and
    \htmlonly
    <a href="cb2bib/annote.tex.html" target="_blank">The cb2Bib Annote</a>.
    \endhtmlonly

*/
c2bCiter::c2bCiter(QWidget* parentw) : QWidget(parentw)
{
    ui.setupUi(this);
    citationFilterIcon = new QLabel(ui.statusBar);
    citationFilterIcon->setPixmap(QPixmap(QString::fromUtf8(":/icons/icons/filter.png")));
    citationFilterIcon->setVisible(false);
    ui.statusBar->insertPermanentWidget(0, citationFilterIcon);
    _citerP = new c2bCoreCiter(this);
    _tray_icon = 0;
    setActions();

    _settingsP = c2bSettingsP;
    resize(_settingsP->value("c2bCiter/size", size()).toSize());
    move(_settingsP->value("c2bCiter/position", pos()).toPoint());

    _filter_mode = false;
    _view_selected = false;
    _citations = new c2bCiterModel(this);

    connect(_citations, SIGNAL(statusMessage(const QString&)), this, SLOT(showMessage(const QString&)));
    connect(ui.citationsView, SIGNAL(activated(const QModelIndex&)), _citations, SLOT(selectCitation(const QModelIndex&)));
}

c2bCiter::~c2bCiter()
{
    if (!isFullScreen())
    {
        _settingsP->setValue("c2bCiter/position", pos());
        _settingsP->setValue("c2bCiter/size", size());
    }
}


void c2bCiter::load()
{
    _bibtex_files.clear();
    if (_settingsP->cl_citer_filenames.count() == 0)
        addFiles(QFileInfo(_settingsP->fileName("cb2Bib/BibTeXFile")).absolutePath());
    else
        for (int i = 0; i < _settingsP->cl_citer_filenames.count(); ++i)
            addFiles(_settingsP->cl_citer_filenames.at(i));
    reload();
}

void c2bCiter::addFiles(const QString& dir)
{
    QFileInfo fi(dir);
    if (!fi.exists())
        return;
    if (fi.isDir())
    {
        QDir bibdir(dir);
        QFileInfoList bibs = bibdir.entryInfoList(QStringList() << "*.bib");
        for (int i = 0; i < bibs.count(); ++i)
            _bibtex_files.append(bibs.at(i).absoluteFilePath());
    }
    else if (fi.isFile())
        _bibtex_files.append(fi.absoluteFilePath());
}

void c2bCiter::reload()
{
    QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
    _filter_mode = false;
    _filter_string.clear();
    _view_selected = false;
    _citations->loadCitations(_bibtex_files);
    if (_bibtex_files.count() == 1)
        setWindowTitle(tr("%1 - cb2Bib").arg(QDir::toNativeSeparators(QFileInfo(_bibtex_files.at(0)).absoluteFilePath())));
    else if (_bibtex_files.count() > 1)
        setWindowTitle(tr("%1 - cb2Bib").arg(QDir::toNativeSeparators(QFileInfo(_bibtex_files.at(0)).absolutePath())));
    else
        setWindowTitle(tr("Citer - cb2Bib"));
    ui.citationsView->setModel(_citations);
    QApplication::restoreOverrideCursor();
}

void c2bCiter::switchShowHide()
{
    if (isVisible())
        hide();
    else
        show();
}

void c2bCiter::show()
{
#ifdef Q_WS_X11
    updateUserTimestamp();
#endif
    if (isHidden())
        QWidget::show();
    raise();
    activateWindow();
    ui.citationsView->setFocus();
}

void c2bCiter::updateUserTimestamp()
{
#ifdef Q_WS_X11
    // Bypass focus stealing prevention
    // Adapted from KDE libraries:
    // Copyright (C) 1997 Matthias Kalle Dalheimer (kalle@kde.org)
    // Copyright (C) 1998, 1999, 2000 KDE Team
    // http://api.kde.org/4.x-api/kdelibs-apidocs/kdeui/html/kapplication_8cpp-source.html
    Window w = XCreateSimpleWindow(QX11Info::display(), QX11Info::appRootWindow(), 0, 0, 1, 1, 0, 0, 0);
    XSelectInput(QX11Info::display(), w, PropertyChangeMask);
    unsigned char cdata[ 1 ];
    XChangeProperty(QX11Info::display(), w, XA_ATOM, XA_ATOM, 8, PropModeAppend, cdata, 1);
    XEvent ev;
    XWindowEvent(QX11Info::display(), w, PropertyChangeMask, &ev);
    const unsigned long time = ev.xproperty.time;
    XDestroyWindow(QX11Info::display(), w);
    QX11Info::setAppUserTime(time);
    QX11Info::setAppTime(time);
#endif
}

void c2bCiter::setTrayIcon()
{
    _tray_icon = new QSystemTrayIcon(this);
    _tray_icon->setIcon(QIcon(":/icons/icons/cb2bib.png"));
    _tray_icon->setToolTip("c2bBib Citer");
    QMenu* tray_icon_menu = new QMenu(this);
    foreach(QAction* a, actions())
    {
        tray_icon_menu->addAction(a);
    }
    _tray_icon->setContextMenu(tray_icon_menu);
    _tray_icon->show();
    connect(_tray_icon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
            this, SLOT(trayIconActivated(QSystemTrayIcon::ActivationReason)));
}

void c2bCiter::trayIconActivated(QSystemTrayIcon::ActivationReason r)
{
    if (r == QSystemTrayIcon::Context)
        return;
    show();
}

void c2bCiter::setActions()
{
    QAction* a;
    a = new QAction(this);
    a->setText(tr("Citer Help"));
    a->setShortcut(QKeySequence(QKeySequence::HelpContents));
    addAction(a);
    connect(a, SIGNAL(triggered()), this, SLOT(help()));
    c2bUtils::addSeparator(this);

    a = new QAction(this);
    a->setText(tr("Open directory"));
    a->setShortcut(QKeySequence(QKeySequence::Open));
    addAction(a);
    connect(a, SIGNAL(triggered()), this, SLOT(openDirectory()));
    a = new QAction(this);
    a->setText(tr("Open files"));
    a->setShortcut(QKeySequence(Qt::ALT + Qt::Key_O));
    addAction(a);
    connect(a, SIGNAL(triggered()), this, SLOT(openFiles()));
    c2bUtils::addSeparator(this);

    a = new QAction(this);
    a->setText(tr("Refresh"));
    a->setShortcut(QKeySequence(QKeySequence::Refresh));
    addAction(a);
    connect(a, SIGNAL(triggered()), this, SLOT(reload()));
    c2bUtils::addSeparator(this);

    a = new QAction(this);
    a->setText(tr("Search In Files"));
    a->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_F));
    addAction(a);
    connect(a, SIGNAL(triggered()), this, SLOT(doSearchInFiles()));
    c2bUtils::addSeparator(this);

    a = new QAction(this);
    a->setText(tr("Set LyX pipe"));
    a->setShortcut(QKeySequence(Qt::ALT + Qt::Key_L));
    addAction(a);
    connect(a, SIGNAL(triggered()), _citerP, SLOT(setLyXPipe()));
    c2bUtils::addSeparator(this);

    a = new QAction(this);
    a->setText(tr("Toggle cb2Bib"));
    a->setShortcut(QKeySequence(Qt::Key_F2));
    addAction(a);
    connect(a, SIGNAL(triggered()), this, SLOT(toggleCb2Bib()));
    a = new QAction(this);
    a->setText(tr("Toggle Full Screen"));
    a->setShortcut(QKeySequence(Qt::ALT + Qt::Key_F));
    addAction(a);
    connect(a, SIGNAL(triggered()), this, SLOT(toggleFullScreen()));
    c2bUtils::addSeparator(this);

    a = new QAction(this);
    a->setText(tr("Exit"));
    a->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Q));
    addAction(a);
    connect(a, SIGNAL(triggered()), c2b::instance(), SLOT(exitRequested()));
}

void c2bCiter::openDirectory()
{
    QString dir;
    if (_bibtex_files.count() > 0)
        dir = _bibtex_files.at(0);
    dir = c2bFileDialog::getExistingDirectory(this, tr("Select BibTeX directory"), dir);
    if (dir.isEmpty())
        return;
    _bibtex_files.clear();
    addFiles(dir);
    reload();
}

void c2bCiter::openFiles()
{
    QString dir;
    if (_bibtex_files.count() > 0)
        dir = _bibtex_files.at(0);
    const QStringList fns(c2bFileDialog::getOpenFilenames(this, "Select BibTeX filenames", dir, "BibTeX (*.bib);;All (*)"));
    if (fns.count() > 0)
    {
        _bibtex_files = fns;
        reload();
    }
}

void c2bCiter::doSearchInFiles()
{
    const QString fn(_citations->dataBibTeXFile(ui.citationsView->currentIndex()));
    if (fn.isEmpty())
        showMessage(tr("No active reference."));
    else
        c2b::doSearchInFiles(QString(), fn);
}

void c2bCiter::toggleCb2Bib()
{
    if (c2b::mainWidget()->isVisible())
        c2b::mainWidget()->hide();
    else
        c2bUtils::setWidgetOnTop(c2b::mainWidget());
}

void c2bCiter::toggleFullScreen()
{
    if (isFullScreen())
        showNormal();
    else
        showFullScreen();
}

void c2bCiter::keyPressEvent(QKeyEvent* qevent)
{
    if (_filter_mode)
    {
        switch (qevent->key())
        {
        case Qt::Key_Enter:
        case Qt::Key_Return:
        case Qt::Key_Down:
        case Qt::Key_Escape:
        case Qt::Key_Up:
            _filter_mode = false;
            setFilter(true);
            qevent->accept();
            return;
        }
        switch (qevent->key())
        {
        case Qt::Key_Backspace:
            _filter_string.chop(1);
            break;
        default:
            _filter_string += c2bUtils::toAscii(qevent->text(), c2bUtils::Collation);
        }
        setFilter(true);
        qevent->ignore();
        return;
    }
    else
    {
        switch (qevent->key())
        {
        case Qt::Key_Escape:
            hide();
            qevent->accept();
            return;
        case Qt::Key_F:
            _filter_mode = true;
            setFilter(false);
            qevent->accept();
            return;
        case Qt::Key_S:
            toggleSelectedFilter();
            qevent->accept();
            return;
        case Qt::Key_C:
            citeReferences();
            qevent->accept();
            return;
        case Qt::Key_E:
            editReference();
            qevent->accept();
            return;
        case Qt::Key_O:
            openFile();
            qevent->accept();
            return;
        case Qt::Key_U:
            openUrl();
            qevent->accept();
            return;
        }
        qevent->ignore();
    }
}

void c2bCiter::setFilter(const bool do_filter)
{
    citationFilterIcon->setVisible(_filter_mode);
    if (_filter_mode)
        ui.statusBar->showMessage(tr("Filter: %1_").arg(_filter_string), 0);
    else
    {
        if (!_filter_string.isEmpty())
            ui.statusBar->showMessage(tr("Filter: %1").arg(_filter_string), 0);
        else
            ui.statusBar->clearMessage();
    }
    if (do_filter)
    {
        QModelIndex i = ui.citationsView->currentIndex();
        _citations->setFilter(_filter_string, &i);
        ui.citationsView->updateCurrentIndex(i);
    }
    _view_selected = false;
}

void c2bCiter::toggleSelectedFilter()
{
    _view_selected = !_view_selected;
    if (_view_selected)
    {
        ui.statusBar->showMessage(tr("Selection"), 0);
        QModelIndex i = ui.citationsView->currentIndex();
        _citations->setSelectedFilter(&i);
        ui.citationsView->updateCurrentIndex(i);
    }
    else
        setFilter(true);
}

void c2bCiter::citeReferences()
{
    const QStringList k(_citations->dataSelectedCiteIds());
    if (k.count() > 0)
    {
        hide();
        _citerP->cite(k);
    }
    else
        showMessage(tr("No selected references."));
}

void c2bCiter::editReference()
{
    const QString r(_citations->dataBibTeXPosition(ui.citationsView->currentIndex()));
    if (r.isEmpty())
        showMessage(tr("No active reference."));
    else
        emit openFile(r);
}

void c2bCiter::openFile()
{
    if (_citations->count() == 0)
        return;
    const QString f(_citations->dataFile(ui.citationsView->currentIndex()));
    if (f.isEmpty())
        showMessage(tr("No File entry in this reference."));
    else
    {
        showMessage(tr("Opening %1").arg(f));
        c2bUtils::openFile(f, parentWidget());
    }
}

void c2bCiter::openUrl()
{
    if (_citations->count() == 0)
        return;
    const QString u(_citations->dataUrl(ui.citationsView->currentIndex()));
    if (u.isEmpty())
        showMessage(tr("No Url entry in this reference."));
    else
    {
        showMessage(tr("Opening %1").arg(QUrl::fromPercentEncoding(u.toUtf8())));
        c2bUtils::openFile(u, parentWidget());
    }
}

void c2bCiter::showMessage(const QString& ms)
{
    ui.statusBar->showMessage(ms, C2B_MESSAGE_TIME);
}

void c2bCiter::help()
{
    c2bUtils::displayHelp("http://www.molspaces.com/d_cb2bib-c2bciter.php");
}
