/***************************************************************************
 *   Copyright (C) 2004-2015 by Pere Constans
 *   constans@molspaces.com
 *   cb2Bib version 1.9.2. 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:
    {
        regexp.setPattern(pattern);
        regexp.setCaseSensitivity(cs);
        regexp.setPatternSyntax(QRegExp::RegExp2);
        regexp.setMinimal(true);
    }
    break;
    case Wildcard:
    {
        regexp.setPattern(pattern);
        regexp.setCaseSensitivity(cs);
        regexp.setPatternSyntax(QRegExp::Wildcard);
        regexp.setMinimal(true);
        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.indexIn(contents) != -1;
    }
}

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:
        i = regexp.indexIn(contents, from);
        length = regexp.matchedLength();
        return i;
    }
}
