/***************************************************************************
 *   Copyright (C) 2004-2017 by Pere Constans
 *   constans@molspaces.com
 *   cb2Bib version 1.9.7. Licensed under the GNU GPL version 3.
 *   See the LICENSE file that comes with this distribution.
 ***************************************************************************/
#ifndef COREBIBPARSER_H
#define COREBIBPARSER_H

#include "authorString.h"
#include "bibReference.h"
#include "cb2bib_utilities.h"
#include "settings.h"

#include <QDir>
#include <QObject>


/**
    Class for bibliographic reference parsing

    @author Pere Constans
*/
class coreBibParser : public QObject
{

    Q_OBJECT

public:
    explicit coreBibParser(QObject* parento = 0);
    inline virtual ~coreBibParser() {}

    QString referenceToBibTeX(const bibReference& ref) const;
    QString referenceToFomattedBibTeX(const bibReference& ref) const;
    bibReference wholeReference(const QString& str);
    bool referenceAtKey(const QString& key, const QString& str, bibReference* ref);
    bool referencesIn(const QString& str, bibReference* ref);
    void initReferenceParsing(const QString& dir, const QStringList& fields, bibReference* ref);

    inline const QStringList& bibliographicFields() const
    {
        return _bibliographic_fields;
    }
    inline const QStringList& sortedBibliographicFields() const
    {
        return _sorted_bibliographic_fields;
    }
    inline const QStringList& bibliographicTypes() const
    {
        return _bibliographic_types;
    }
    inline void setReferenceParsingDir(const QString& dir)
    {
        // Set base dir for cases of relative 'file'
        // Terminate dirname with separator to avoid adding it to each reference
        _bib_file_dir = QDir::toNativeSeparators(QFileInfo(dir).absolutePath() + QDir::separator());
    }
    inline bool hasBibTeX(const QString& str) const
    {
        if (_bib_begin0_re.indexIn(str) > -1)
            return true;
        else
            return _bib_begin1_re.indexIn(str) > -1;
    }
    inline int fieldCount(const bibReference& ref) const
    {
        // Counting Non Empty Fields
        int n(0);
        bibReferenceIterator i;
        for (i = ref.constBegin(); i != ref.constEnd(); ++i)
            if (!i.value().isEmpty())
                ++n;
        return n;
    }
    inline QString& authorFromBibTeX(QString& as)
    {
        // Avoid BibTeX reverse names
        if (as.contains(','))
            as = _authorString.toBibTeX(as, _settingsP->value("cb2Bib/UseFullNames").toBool());
        return as;
    }


protected:
    QRegExp _field_re;
    QString adjacentNumbers(const QString& numbers) const;
    QStringList _bibliographic_fields;
    QStringList _bibliographic_types;
    QStringList _sorted_bibliographic_fields;
    authorString _authorString;
    settings* _settingsP;


private:
    QList<QRegExp> _bib_fields_nb_re;
    QList<QRegExp> _bib_fields_re;
    QRegExp _bib_begin0_re;
    QRegExp _bib_begin1_re;
    QRegExp _bib_begin_re;
    QRegExp _bib_key_re;
    QRegExp _bib_type_re;
    QString _bib_file_dir;
    const QChar _close;
    const QChar _comma;
    const QChar _open;
    const QChar _space;
    void setFields();
    void setRegularExpressions();
    void setTypes();

    inline int referenceStarts(const QString& str, int pos = 0) const
    {
        int i;
        if (pos == 0)
        {
            i = _bib_begin0_re.indexIn(str);
            if (i < 0)
                i = _bib_begin1_re.indexIn(str, pos);
        }
        else
            i = _bib_begin1_re.indexIn(str, pos);
        if (i < 0)
            return i;
        return _bib_begin_re.indexIn(str, i);
    }
    inline int referenceStarts(const QString& key, const QString& str) const
    {
        if (!str.contains(key))
            return -1;
        int i(str.indexOf(QRegExp("^\\s*@\\w+\\s*\\{" + key + ',')));
        if (i < 0)
            i = str.indexOf(QRegExp("[\\r\\n]\\s*@\\w+\\s*\\{" + key + ','));
        if (i < 0)
            return i;
        return str.indexOf(QRegExp("@\\w+\\s*\\{" + key + ','), i);
    }
    inline int referenceEnds(const QString& str, int pos = 0) const
    {
        // If referenceStarts call is successful, we know for sure
        // that there is an opening { right after pos.
        // Do not check again here.
        // Checking for brace closure is the safest way for parsing.
        // It will fail, though, for references incorrectly written.
        // Avoid overextending in these cases by checking the
        // start of the next reference.
        int ref_length(referenceStarts(str, pos + 2) - 1);
        if (ref_length < 0)
            ref_length = str.length();
        const int brace_pos(str.indexOf(_open, pos));
        int open_braces(1);
        for (int i = brace_pos + 1; i < ref_length; ++i)
        {
            const QChar& si = str.at(i);
            if (si == _open)
                open_braces++;
            else if (si == _close)
                open_braces--;
            if (open_braces == 0)
                return i;
        }
        return ref_length - 1;
    }
    inline void setReferenceEnd(QString* str, const int& length) const
    {
        // Set safer termination: field="..." } -> field="...",}
        if (str->at(length - 2) == _space)
            (*str)[length - 2] = _comma;
        else
            (*str)[length - 1] = _comma;
    }
    inline QString referenceAt(const QString& str, int* pos) const
    {
        // String str contains one or multiple references (file contents)
        const int pos_(referenceEnds(str, *pos) + 1);
        const QString str_ref(str.mid(*pos, pos_ - (*pos)));
        *pos = pos_;
        return str_ref;
    }
    inline void referenceContents(const QString& str, bibReference* ref, int pos)
    {
        // File parsing for given fields in ref
        ref->positionValue = pos;
        QString str_ref(referenceAt(str, &pos));
        ref->pos = pos;
        ref->rawReference = str_ref;
        c2bUtils::bibToC2b(str_ref);
        c2bUtils::simplifyString(str_ref);
        ref->unicodeReference = str_ref;
        _bib_key_re.indexIn(str_ref);
        ref->citeidName = _bib_key_re.cap(1);
        _bib_type_re.indexIn(str_ref);
        ref->typeName = _bib_type_re.cap(1).toLower();
        setReferenceEnd(&str_ref, str_ref.length());
        QString fvalue;
        for (int i = 0; i < ref->_bib_fields_re.count(); ++i)
        {
            QRegExp* bf = &ref->_bib_fields_re[i];
            pos = bf->indexIn(str_ref);
            if (pos > 0)
            {
                if (c2bUtils::inBraces(pos + bf->matchedLength(), str_ref, &fvalue))
                    (*ref)[ref->bib_fieldList.at(i)] = fvalue.trimmed();
            }
            else
            {
                bf = &ref->_bib_fields_nb_re[i];
                if (bf->indexIn(str_ref) > -1)
                    (*ref)[ref->bib_fieldList.at(i)] = bf->cap(1).trimmed();
            }
        }
        if (ref->contains("file"))
            if (!QDir::isAbsolutePath(ref->value("file")))
                (*ref)["file"] = _bib_file_dir + ref->value("file");
    }

};

#endif
