/***************************************************************************
 *   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.
 ***************************************************************************/
#include "searchPattern.h"

#include "triads.h"

#include <QMap>


/**
    Top level driver for calling cb2Bib search types
*/
searchPattern::searchPattern(const QString& pattern, const QString& patternType) :
    _matcher(pattern.simplified(), typemap().value(patternType), Qt::CaseInsensitive)
{
    _modifier.NOT = false;
    _modifier.string = pattern.simplified();
    _rank = 0;
}

searchPattern::searchPattern(const bool NOT, const bool caseSensitive, const QString& patternType,
                             const QString& scope, const QChar& yearScope, const QString& pattern) :
    _matcher(pattern.simplified(), typemap().value(patternType), qtcase(caseSensitive))
{
    _modifier.NOT = NOT;
    _modifier.string = pattern.simplified();
    _modifier.scope = scope;
    _modifier.yearScope = yearScope;

    if (_modifier.NOT)
        _formatted_string += "NOT.";
    _formatted_string += QString(" [%1][%2|case=%3]").arg(_modifier.string).arg(patternType).arg(caseSensitive);
    if (_modifier.scope == "year")
        _formatted_string += QString(" IN [%1(%2)]").arg(_modifier.scope).arg(_modifier.yearScope);
    else
        _formatted_string += QString(" IN [%1]").arg(_modifier.scope);

    // Set an approximate ranking to speed up composite searches
    _rank = 0;
    if (_modifier.scope == "file")
        _rank += 200;
    else if (_modifier.scope == "all")
        _rank += 100;
    else if (_modifier.scope == "year" || _modifier.scope == "volume" || _modifier.scope == "pages")
        _rank += 50;
    else
        _rank += 1;
    if (!caseSensitive)
        _rank *= 2;
    if (_matcher.type == FixedStringAllWords || _matcher.type == FixedStringAnyWord)
        _rank *= 5;
    else if (_matcher.type == ApproximateString)
        _rank *= 10;
    else if (_matcher.type == RegularExpression)
        _rank *= 20;
}

const QStringList searchPattern::types()
{
    return typemap().keys();
}

const QString searchPattern::type(const Type t)
{
    return typemap().key(t);
}

const QMap<QString, searchPattern::Type> searchPattern::typemap()
{
    QMap<QString, Type> tm;
    tm.insert(QObject::tr("Approximate string"), ApproximateString);
    tm.insert(QObject::tr("Fixed string: All Words"), FixedStringAllWords);
    tm.insert(QObject::tr("Fixed string: Any Word"), FixedStringAnyWord);
    tm.insert(QObject::tr("Fixed string: Context"), FixedStringContext);
    tm.insert(QObject::tr("Fixed string"), FixedString);
    tm.insert(QObject::tr("Regular expression"), RegularExpression);
    tm.insert(QObject::tr("Wildcard"), Wildcard);
    return tm;
}


searchPattern::matcher::matcher(const QString& pattern, const Type t, const Qt::CaseSensitivity cs) : type(t), length(-1)
{
    switch (t)
    {
    default:
        c2bUtils::warn(QObject::tr("Internal Error: Invalid search pattern type. Set to 'Approximate string'"));
    case ApproximateString:
    {
        appexp.setPattern(pattern, cs);
        if (appexp.isMultipattern())
        {
            signature = triads::textSignature(pattern);
            subsignatures = triads::textSignature(appexp.substrings());
        }
        else // Skip signatures for regular expression case
        {
            regexp = appexp.regexp();
            type = RegularExpression;
        }
    }
    break;
    case FixedStringAnyWord:
    {
        wordexp.setPattern(pattern, wordPattern::AnyWord, cs);
        signature = triads::textSignature(pattern);
        subsignatures = triads::textSignature(wordexp.substrings());
    }
    break;
    case FixedStringAllWords:
    {
        wordexp.setPattern(pattern, wordPattern::AllWords, cs);
        signature = triads::textSignature(pattern);
        subsignatures.fill(signature, wordexp.subpatternCount());
    }
    break;
    case FixedStringContext:
    {
        cwordexp.setPattern(pattern, cs);
        signature = triads::textSignature(pattern);
    }
    break;
    case FixedString:
    {
        strexp.setPattern(pattern);
        strexp.setCaseSensitivity(cs);
        signature = triads::textSignature(pattern);
    }
    break;
    case RegularExpression:
    {
#if QT_VERSION >= 0x050400
        const QRegularExpression::PatternOptions po(
            QRegularExpression::DontCaptureOption |
            QRegularExpression::UseUnicodePropertiesOption |
            QRegularExpression::OptimizeOnFirstUsageOption);
#else
        const QRegularExpression::PatternOptions po(
            QRegularExpression::DontCaptureOption |
            QRegularExpression::UseUnicodePropertiesOption);
#endif
        regexp.setPattern(pattern);
        regexp.setPatternOptions(cs == Qt::CaseSensitive ? po : po | QRegularExpression::CaseInsensitiveOption);
    }
    break;
    case Wildcard:
    {
#if QT_VERSION >= 0x050400
        const QRegularExpression::PatternOptions po(
            QRegularExpression::InvertedGreedinessOption |
            QRegularExpression::DontCaptureOption |
            QRegularExpression::UseUnicodePropertiesOption |
            QRegularExpression::OptimizeOnFirstUsageOption);
#else
        const QRegularExpression::PatternOptions po(
            QRegularExpression::InvertedGreedinessOption |
            QRegularExpression::DontCaptureOption |
            QRegularExpression::UseUnicodePropertiesOption);
#endif
        regexp.setPattern(_wildcard_escaped(pattern));
        regexp.setPatternOptions(cs == Qt::CaseSensitive ? po : po | QRegularExpression::CaseInsensitiveOption);
        signature = triads::textSignature(pattern);
    }
    break;
    }
}

bool searchPattern::matcher::match(const QString& contents) const
{
    length = -1;
    switch (type)
    {
    case ApproximateString:
        return appexp.matches(contents);
    case FixedStringAnyWord:
    case FixedStringAllWords:
        return wordexp.matches(contents);
    case FixedStringContext:
        return cwordexp.indexIn(contents) != -1;
    case FixedString:
        return strexp.indexIn(contents) != -1;
    default:
        return regexp.match(contents).hasMatch();
    }
}

bool searchPattern::matcher::match(const documentContents& contents) const
{
    length = -1;
    switch (type)
    {
    case ApproximateString:
        return _match_any(appexp.subpatternCount(), appexp.submatchers(), contents);
    case FixedStringAnyWord:
        return _match_any(wordexp.subpatternCount(), wordexp.submatchers(), contents);
    case FixedStringAllWords:
        return _match_all(wordexp.subpatternCount(), wordexp.submatchers(), contents);
    case FixedStringContext:
        return _match(cwordexp, signature, contents);
    case FixedString:
        return _match(strexp, signature, contents);
    default:
        return _match(regexp, signature, contents);
    }
}

int searchPattern::matcher::index(const QString& contents, const int from) const
{
    int i;
    switch (type)
    {
    case ApproximateString:
        i = appexp.indexIn(contents, from);
        length = appexp.matchedLength();
        return i;
    case FixedStringAnyWord:
    case FixedStringAllWords:
        i = wordexp.indexIn(contents, from);
        length = wordexp.matchedLength();
        return i;
    case FixedStringContext:
        i = cwordexp.indexIn(contents, from);
        length = cwordexp.matchedLength();
        return i;
    case FixedString:
        i = strexp.indexIn(contents, from);
        length = strexp.pattern().length();
        return i;
    default:
        regexp_match = regexp.match(contents, from);
        i = regexp_match.capturedStart();
        length = regexp_match.capturedLength();
        return i;
    }
}

QString searchPattern::matcher::_wildcard_escaped(const QString& pattern)
{
    QString scaped;
    for (int i = 0; i < pattern.length(); ++i)
        if (pattern.at(i) == '*')
            scaped += ".*";
        else if (pattern.at(i) == '?')
            scaped += '.';
        else
            scaped += QRegularExpression::escape(pattern.at(i));
    return scaped;
}
