/***************************************************************************
 *   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 "c2bCiterModel.h"

#include <bibParser.h>

#include "c2b.h"

#include <QIcon>


template <typename T> class ascending
{
public:
    ascending(const T& data) : _data(data) {}
    inline bool operator()(const int i, const int j)
    {
        return _data.at(i) < _data.at(j);
    }
private:
    const T& _data;
};


c2bCiterModel::c2bCiterModel(QObject* parento) : QAbstractTableModel(parento)
{
    _first_column_color = QApplication::palette().color(QPalette::Active, QPalette::Base).darker(110);
    _clear();
}

c2bCiterModel::~c2bCiterModel()
{}


void c2bCiterModel::loadCitations(const QStringList& fns)
{
    emit layoutAboutToBeChanged();
    _clear();
    for (int i = 0; i < fns.count(); ++i)
        _add_citations(fns.at(i));
    _set_table_data();
    _set_sort_indices();
    _update_format();
    emit layoutChanged();
    emit statusMessage(tr("Loaded %1 references from %2 files.").arg(_citation_count).arg(fns.count()));
}

void c2bCiterModel::loadCitations(const QString& fn)
{
    emit layoutAboutToBeChanged();
    _clear();
    _add_citations(fn);
    _set_table_data();
    _set_sort_indices();
    _update_format();
    emit layoutChanged();
    emit statusMessage(tr("Loaded %1 references.").arg(_citation_count));
}

QVariant c2bCiterModel::data(const QModelIndex& i, int role) const
{
    if (role == Qt::DisplayRole)
        return (this->*_display_ptr)(i.row(), i.column());
    else if (role == Qt::DecorationRole)
    {
        if (i.column() == 0)
        {
            if (_is_selected.at(_offset(i.row())))
                return QVariant(QIcon(":/icons/icons/citer_citation_checked.png"));
            else
                return QVariant(QIcon(":/icons/icons/citer_citation.png"));
        }
        return QVariant();
    }
    else if (role == Qt::BackgroundRole)
    {
        if (i.column() == 0)
            return _first_column_color;
        else
            return QVariant();
    }
    else
        return QVariant();
}

QStringList c2bCiterModel::dataSelectedCiteIds() const
{
    QStringList ids;
    for (int i = 0; i < _citation_count; ++i)
        if (_is_selected.at(_map_yajt.at(i)))
            ids.append(_citeId.at(_map_yajt.at(i)));
    return ids;
}

void c2bCiterModel::clearSelection()
{
    emit layoutAboutToBeChanged();
    for (int i = 0; i < _citation_count; ++i)
        _is_selected[i] = false;
    emit layoutChanged();
}

void c2bCiterModel::setFilter(const QString& pattern, QModelIndex* current_index)
{
    emit layoutAboutToBeChanged();
    int current_citation(-1);
    if (_is_index_valid(*current_index))
        current_citation = _offset(current_index->row());
    const QStringList word(pattern.split(c2bUtils::nonLetter, QString::SkipEmptyParts));
    _is_filter_set = !pattern.isEmpty();
    if (_is_filter_set)
        for (int i = 0; i < _citation_count; ++i)
        {
            _matches_filter[i] = true;
            const QString& str = _search_string.at(i);
            for (int w = 0; w < word.count(); ++w)
                if (!str.contains(word.at(w), Qt::CaseSensitive))
                {
                    _matches_filter[i] = false;
                    break;
                }
        }
    _set_mapping();
    _update_current_index(current_citation, current_index);
    emit layoutChanged();
}

void c2bCiterModel::setSelectedFilter(QModelIndex* current_index)
{
    emit layoutAboutToBeChanged();
    int current_citation(-1);
    if (_is_index_valid(*current_index))
        current_citation = _offset(current_index->row());
    for (int i = 0; i < _citation_count; ++i)
        _matches_filter[i] = _is_selected.at(i);
    _is_filter_set = true;
    _set_mapping();
    _update_current_index(current_citation, current_index);
    emit layoutChanged();
}

void c2bCiterModel::selectCitation(const QModelIndex& i)
{
    _is_selected[_offset(i.row())] = !_is_selected.at(_offset(i.row()));
    emit layoutChanged();
}

QList<int> c2bCiterModel::sizeHintForColumns() const
{
    QList<int> sizes;
    switch (_format)
    {
    case AJYT:
        sizes.append(qMin(20, _author_max_length + 2));
        sizes.append(20);
        sizes.append(6);
        sizes.append(100);
        break;
    case JYA:
        sizes.append(qMin(25, _journal_max_length));
        sizes.append(6);
        sizes.append(100);
        break;
    case T:
        sizes.append(100);
        break;
    case YAJT:
    default:
        sizes.append(8);
        sizes.append(75);
        sizes.append(25);
        sizes.append(100);
        break;
    }
    return sizes;
}

void c2bCiterModel::updateFormat(const Format format, QModelIndex* current_index)
{
    emit layoutAboutToBeChanged();
    _update_format(format, current_index);
    emit layoutChanged();
}

void c2bCiterModel::_update_format(const Format format, QModelIndex* current_index)
{
    int current_citation(-1);
    if (_mapping && current_index)
        current_citation = _offset(current_index->row());
    _format = format;
    switch (_format)
    {
    case AJYT:
        _column_count = 4;
        _display_ptr = &c2bCiterModel::_display_ajyt;
        break;
    case JYA:
        _column_count = 3;
        _display_ptr = &c2bCiterModel::_display_jya;
        break;
    case T:
        _column_count = 1;
        _display_ptr = &c2bCiterModel::_display_t;
        break;
    case YAJT:
    default:
        _column_count = 4;
        _display_ptr = &c2bCiterModel::_display_yajt;
        break;
    }
    _set_mapping();
    if (current_index)
        _update_current_index(current_citation, current_index);
}

void c2bCiterModel::_update_current_index(const int current_citation, QModelIndex* current_index)
{
    if (current_citation >= 0)
        for (int i = 0; i < _row_count; ++i)
            if (_offset(i) == current_citation)
            {
                *current_index = index(i, 0);
                return;
            }
    *current_index = index(0, 0);
}

void c2bCiterModel::_add_citations(const QString& fn)
{
    bibParser* bpP = c2b::bibParser();
    QStringList fields;
    fields.append("author");
    fields.append("booktitle");
    fields.append("doi");
    fields.append("editor");
    fields.append("file");
    fields.append("journal");
    fields.append("title");
    fields.append("url");
    fields.append("year");
    bibReference ref;
    bpP->initReferenceParsing(fn, fields, &ref);
    const QRegExp initials1("\\b\\w\\b");
    const QRegExp initials2("[^\\w\\s]");
    const QString bibtex(c2bUtils::fileToString(fn));

    while (bpP->referencesIn(bibtex, &ref))
    {
        _citation_count++;

        QString author(ref.anyAuthor());
        if (!author.isEmpty())
        {
            author = bpP->authorFromBibTeX(author);
            author.remove(initials1);
            author.remove(initials2);
            author.replace(" and ", ", ");
            author = author.simplified();
        }

        QString title(ref.anyTitle());
        c2bUtils::cleanTitle(title, true);

        QString url(ref.value("url"));
        if (url.isEmpty())
        {
            const QString doi(ref.value("doi"));
            if (!doi.isEmpty())
            {
                if (doi.startsWith("http://"))
                    url = QUrl::toPercentEncoding(doi);
                else
                    url = "http://dx.doi.org/" + QUrl::toPercentEncoding(doi);
            }
        }

        QString file(ref.value("file"));
        if (!file.isEmpty())
            file = QDir::cleanPath(file);

        _author_string.append(author);
        _bibtex_position.append(QString("%1:%2").arg(fn).arg(ref.positionValue));
        _citeId.append(ref.citeidName);
        _file.append(file);
        _journal.append(ref.value("journal"));
        _title.append(title);
        _url.append(url);
        _year.append(ref.value("year"));
    }
}

void c2bCiterModel::_clear()
{
    _format = AJYT;
    _author_string.clear();
    _bibtex_position.clear();
    _citeId.clear();
    _file.clear();
    _journal.clear();
    _title.clear();
    _url.clear();
    _year.clear();
    _author.clear();
    _search_string.clear();
    _is_selected.clear();
    _matches_filter.clear();
    _map_ajyt.clear();
    _map_author.clear();
    _map_filter.clear();
    _map_jya.clear();
    _map_t.clear();
    _map_yajt.clear();
    _mapping = 0;
    _is_filter_set = false;
    _author_count = 0;
    _author_max_length = 0;
    _citation_count = 0;
    _column_count = 0;
    _journal_max_length = 0;
    _row_count = 0;
}


/****************************************************************************

  SETTING DATA POINTERS

*****************************************************************************/

void c2bCiterModel::_set_table_data()
{
    for (int i = 0; i < _citation_count; ++i)
        _author_count += _author_string.at(i).count(", ") + 1;
    _author.resize(_author_count);
    _map_author.resize(_author_count);
    int ij(0);
    for (int i = 0; i < _citation_count; ++i)
    {
        const QStringList authors(_author_string.at(i).split(", ", QString::KeepEmptyParts));
        for (int j = 0; j < authors.count(); ++j)
        {
            _author[ij] = authors.at(j).trimmed();
            _map_author[ij] = i;
            _author_max_length = qMax(_author_max_length, _author.at(ij).length());
            ++ij;
        }
    }
    if (_author_count != ij)
        qFatal("c2bCiterModel::_set_table_data: Mismatch author mapping");

    bibParser* bpP = c2b::bibParser();
    for (int i = 0; i < _citation_count; ++i)
    {
        _journal[i] = bpP->abbreviatedJournal(_journal.at(i));
        _journal[i].remove('.');
        _journal_max_length = qMax(_journal_max_length, _journal.at(i).length());
    }

    _is_selected.resize(_citation_count);
    for (int i = 0; i < _citation_count; ++i)
        _is_selected[i] = false;

    _matches_filter.resize(_author_count);
    _map_filter.resize(_author_count);
}

void c2bCiterModel::_set_sort_indices()
{
    // AJYT
    // Use _search_string as temporary for sorting
    _search_string.resize(_author_count);
    for (int i = 0; i < _author_count; ++i)
    {
        const int j(_map_author.at(i));
        _search_string[i] = c2bUtils::toAscii(_author.at(i) + ' ' + _journal.at(j) + ' ' + _year.at(j) + ' ' + _title.at(j),
                                              c2bUtils::Collation);
    }
    _map_ajyt.resize(_author_count);
    for (int i = 0; i < _author_count; ++i)
        _map_ajyt[i] = i;
    ascending< QVector<QString> > ajyt(_search_string);
    qSort(_map_ajyt.begin(), _map_ajyt.end(), ajyt);

    // JYA
    _search_string.resize(_citation_count);
    for (int i = 0; i < _citation_count; ++i)
        _search_string[i] = c2bUtils::toAscii(_journal.at(i) + ' ' + _year.at(i) + ' ' + _author_string.at(i),
                                              c2bUtils::Collation);
    _map_jya.resize(_citation_count);
    for (int i = 0; i < _citation_count; ++i)
        _map_jya[i] = i;
    ascending< QVector<QString> > jya(_search_string);
    qSort(_map_jya.begin(), _map_jya.end(), jya);

    // T
    for (int i = 0; i < _citation_count; ++i)
        _search_string[i] = c2bUtils::toAscii(_title.at(i), c2bUtils::Collation);
    _map_t.resize(_citation_count);
    for (int i = 0; i < _citation_count; ++i)
        _map_t[i] = i;
    ascending< QVector<QString> > t(_search_string);
    qSort(_map_t.begin(), _map_t.end(), t);

    // YAJT
    for (int i = 0; i < _citation_count; ++i)
        _search_string[i] = c2bUtils::toAscii(_year.at(i) + ' ' + _author_string.at(i) + ' ' + _journal.at(i) + ' ' + _title.at(i),
                                              c2bUtils::Collation);
    _map_yajt.resize(_citation_count);
    for (int i = 0; i < _citation_count; ++i)
        _map_yajt[i] = i;
    ascending< QVector<QString> > yajt(_search_string);
    qSort(_map_yajt.begin(), _map_yajt.end(), yajt);
    // Keep _search_string for filtering
}

void c2bCiterModel::_set_mapping()
{
    switch (_format)
    {
    case AJYT:
        _row_count = _author_count;
        _mapping = &_map_ajyt;
        break;
    case JYA:
        _row_count = _citation_count;
        _mapping = &_map_jya;
        break;
    case T:
        _row_count = _citation_count;
        _mapping = &_map_t;
        break;
    case YAJT:
    default:
        _row_count = _citation_count;
        _mapping = &_map_yajt;
        break;
    }
    if (_is_filter_set)
    {
        int r(0);
        if (_format == AJYT)
        {
            for (int i = 0; i < _row_count; ++i)
                if (_matches_filter.at(_map_author.at(_mapping->at(i))))
                    _map_filter[r++] = _mapping->at(i);
        }
        else
        {
            for (int i = 0; i < _row_count; ++i)
                if (_matches_filter.at(_mapping->at(i)))
                    _map_filter[r++] = _mapping->at(i);
        }
        _row_count = r;
        _mapping = &_map_filter;
    }
}

QString c2bCiterModel::_display_ajyt(const int row, const int column) const
{
    switch (column)
    {
    case 0:
        return _author.at(_mapping->at(row));
    case 1:
        return _journal.at(_map_author.at(_mapping->at(row)));
    case 2:
        return _year.at(_map_author.at(_mapping->at(row)));
    case 3:
        return _title.at(_map_author.at(_mapping->at(row)));
    default:
        return QString();
    }
}

QString c2bCiterModel::_display_jya(const int row, const int column) const
{
    switch (column)
    {
    case 0:
        return _journal.at(_offset(row));
    case 1:
        return _year.at(_offset(row));
    case 2:
        return _author_string.at(_offset(row));
    default:
        return QString();
    }
}

QString c2bCiterModel::_display_t(const int row, const int column) const
{
    switch (column)
    {
    case 0:
        return _title.at(_offset(row));
    default:
        return QString();
    }
}

QString c2bCiterModel::_display_yajt(const int row, const int column) const
{
    switch (column)
    {
    case 0:
        return _year.at(_offset(row));
    case 1:
        return _author_string.at(_offset(row));
    case 2:
        return _journal.at(_offset(row));
    case 3:
        return _title.at(_offset(row));
    default:
        return QString();
    }
}

int c2bCiterModel::_offset(const int i) const
{
    if (_format == AJYT)
        return _map_author.at(_mapping->at(i));
    else
        return _mapping->at(i);
}
