/* -*- mode: C++; c-file-style: "gnu" -*-
  kmsearchpattern.h
  Author: Marc Mutz <Marc@Mutz.com>

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License along
  with this program; if not, write to the Free Software Foundation, Inc.,
  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

#ifndef MAILCOMMON_SEARCHPATTERN_H
#define MAILCOMMON_SEARCHPATTERN_H

#include "mailcommon_export.h"

#include <akonadi/kmime/messagestatus.h>
#include <klocale.h>

#ifndef KDEPIM_NO_NEPOMUK
#include <Nepomuk/Query/GroupTerm>
#include <Nepomuk/Query/ComparisonTerm>
#endif

#include <KLocale>
#include <KUrl>

#include <QList>
#include <QString>

#include <boost/shared_ptr.hpp>

using Akonadi::MessageStatus;

class QXmlStreamWriter;

namespace Akonadi {
class Item;
}

namespace KMime {
class Message;
}

class KConfigGroup;

namespace MailCommon {

// maximum number of filter rules per filter
const int FILTER_MAX_RULES = 8;

/**
 * @short This class represents one search pattern rule.
 * Incoming mail is sent through the list of mail filter
 * rules before it is placed in the associated mail folder (usually "inbox").
 * This class represents one mail filter rule. It is also used to represent
 * a search rule as used by the search dialog and folders.
 */
class MAILCOMMON_EXPORT SearchRule
{
  public:
    /**
     * Defines a pointer to a search rule.
     */
    typedef boost::shared_ptr<SearchRule> Ptr;

    /**
     * Describes operators for comparison of field and contents.
     *
     * If you change the order or contents of the enum: do not forget
     * to change funcConfigNames[], sFilterFuncList and matches()
     * in SearchRule, too.
     * Also, it is assumed that these functions come in pairs of logical
     * opposites (ie. "=" <-> "!=", ">" <-> "<=", etc.).
     */
    enum Function {
      FuncNone = -1,
      FuncContains = 0,
      FuncContainsNot,
      FuncEquals,
      FuncNotEqual,
      FuncRegExp,
      FuncNotRegExp,
      FuncIsGreater,
      FuncIsLessOrEqual,
      FuncIsLess,
      FuncIsGreaterOrEqual,
      FuncIsInAddressbook,
      FuncIsNotInAddressbook,
      FuncIsInCategory,
      FuncIsNotInCategory,
      FuncHasAttachment,
      FuncHasNoAttachment,
      FuncStartWith,
      FuncNotStartWith,
      FuncEndWith,
      FuncNotEndWith
    };

    /**
     * Creates new new search rule.
     *
     * @param field The field to search in.
     * @param function The function to use for searching.
     * @param contents The contents to search for.
     */
    explicit SearchRule ( const QByteArray &field = 0, Function function = FuncContains,
                          const QString &contents = QString() );

    /**
     * Creates a new search rule from an @p other rule.
     */
    SearchRule( const SearchRule &other );

    /**
     * Initializes this rule with an @p other rule.
     */
    const SearchRule& operator=( const SearchRule &other );

    /**
     * Creates a new search rule of a certain type by instantiating the
     * appropriate subclass depending on the @p field.
     *
     * @param field The field to search in.
     * @param function The function to use for searching.
     * @param contents The contents to search for.
     */
    static SearchRule::Ptr createInstance( const QByteArray &field = 0,
                                           Function function = FuncContains,
                                           const QString &contents = QString() );

    /**
     * Creates a new search rule of a certain type by instantiating the
     * appropriate subclass depending on the @p field.
     *
     * @param field The field to search in.
     * @param function The name of the function to use for searching.
     * @param contents The contents to search for.
     */
    static SearchRule::Ptr createInstance( const QByteArray &field,
                                           const char *function,
                                           const QString &contents );

    /**
     * Creates a new search rule by cloning an @p other rule.
     */
    static SearchRule::Ptr createInstance( const SearchRule &other );

    /**
     * Creates a new search rule by deseralizing its structure from a data @p stream.
     */
    static SearchRule::Ptr createInstance( QDataStream &stream );

    /**
     * Creates a new search rule from a given config @p group.
     *
     * @param group The config group to read the structure from.
     * @param index The identifier that is used to distinguish
     *              rules within a single config group.
     *
     * @note This function does no validation of the data obtained
     *       from the config file. You should call isEmpty yourself
     *       if you need valid rules.
     */
    static SearchRule::Ptr createInstanceFromConfig( const KConfigGroup &group, int index );

    /**
     * Destroys the search rule.
     */
    virtual ~SearchRule();

    /**
     * Tries to match the rule against the KMime::Message in the
     * given @p item.
     *
     * @return true if the rule matched, false otherwise.
     *
     * @note Must be implemented by subclasses.
     */
    virtual bool matches( const Akonadi::Item &item ) const = 0;

    /**
     * Determines whether the rule is worth considering.
     * It isn't if either the field is not set or the contents is empty.
     * The calling code should make sure that it's rule list contains
     * only non-empty rules, as matches doesn't check this.
     */
    virtual bool isEmpty() const = 0;

    /**
     * Returns true if the rule depends on a complete message,
     * otherwise returns false.
     */
    virtual bool requiresBody() const;


    /**
     * Saves the object into a given config @p group.
     *
     * @param index The identifier that is used to distinguish
     *              rules within a single config group.
     *
     * @note This function will happily write itself even when it's
     *       not valid, assuming higher layers to Do The Right Thing(TM).
     */
    void writeConfig( KConfigGroup &group, int index ) const;

    /**
     * Sets the filter @p function of the rule.
     */
    void setFunction( Function function );

    /**
     * Returns the filter function of the rule.
     */
    Function function() const;

    /**
     * Sets the message header field @p name.
     *
     * @note Make sure the name contains no trailing ':'.
     */
    void setField( const QByteArray &name );

    /**
     * Returns the message header field name (without the trailing ':').
     *
     * There are also six pseudo-headers:
     * @li \<message\>: Try to match against the whole message.
     * @li \<body\>: Try to match against the body of the message.
     * @li \<any header\>: Try to match against any header field.
     * @li \<recipients\>: Try to match against both To: and Cc: header fields.
     * @li \<size\>: Try to match against size of message (numerical).
     * @li \<age in days\>: Try to match against age of message (numerical).
     * @li \<status\>: Try to match against status of message (status).
     * @li \<tag\>: Try to match against message tags.
     */
    QByteArray field() const;

    /**
     * Set the @p contents of the rule.
     *
     * This can be either a substring to search for in
     * or a regexp pattern to match against the header.
     */
    void setContents( const QString &contents );

    /**
     * Returns the contents of the rule.
     */
    QString contents() const;

    /**
     * Returns the rule as string for debugging purpose
     */
    const QString asString() const;

#ifndef KDEPIM_NO_NEPOMUK 
    /**
     * Adds query terms to the given term group.
     */
    virtual void addQueryTerms( Nepomuk::Query::GroupTerm &groupTerm ) const = 0;
#endif  

    /**
     * Adds a serialization of the rule in XESAM format into the stream.
     */
    virtual void addXesamClause( QXmlStreamWriter& stream ) const = 0;

    QDataStream& operator>>( QDataStream& ) const;


protected:
    /**
     * Helper that returns whether the rule has a negated function.
     */
    bool isNegated() const;

#ifndef KDEPIM_NO_NEPOMUK
    /**
     * Converts the rule function into the corresponding Nepomuk query operator.
     */
    Nepomuk::Query::ComparisonTerm::Comparator nepomukComparator() const;

    /**
     * Adds @p term to @p termGroup and adds a negation term inbetween if needed.
     */
    void addAndNegateTerm( const Nepomuk::Query::Term &term, Nepomuk::Query::GroupTerm &termGroup ) const;
#endif
    /**
     * Converts the rule function into the corresponding Xesam query operator.
     */
    QString xesamComparator() const;

protected:
    QString quote( const QString &content ) const;
private:
    static Function configValueToFunc( const char * );
    static QString functionToString( Function );

    QByteArray mField;
    Function mFunction;
    QString  mContents;
};

// Needed for MSVC 2010, as it seems to not implicit cast for a pointer anymore
#ifdef _MSC_VER
uint qHash(SearchRule::Ptr sr);
#endif

/**
 * @short This class represents a search pattern rule operating on a string.
 *
 * This class represents a search to be performed against a string.
 * The string can be either a message header, or a pseudo header, such
 * as \<body\>
 */
class SearchRuleString : public SearchRule
{
  public:
    /**
     * Creates new new string search rule.
     *
     * @param field The field to search in.
     * @param function The function to use for searching.
     * @param contents The contents to search for.
     */
    explicit SearchRuleString( const QByteArray &field = 0,
                               Function function = FuncContains,
                               const QString &contents = QString() );

    /**
     * Creates a new string search rule from an @p other rule.
     */
    SearchRuleString( const SearchRuleString &other );

    /**
     * Initializes this rule with an @p other rule.
     */
    const SearchRuleString& operator=( const SearchRuleString &other );

    /**
     * Destroys the string search rule.
     */
    virtual ~SearchRuleString();

    /**
     * @copydoc SearchRule::isEmpty()
     */
    virtual bool isEmpty() const ;

    /**
     * @copydoc SearchRule::requiresBody()
     */
    virtual bool requiresBody() const;

    /**
     * @copydoc SearchRule::matches()
     */
    virtual bool matches( const Akonadi::Item &item ) const;

    /**
     * A helper method for the main matches() method.
     * Does the actual comparing.
     */
    bool matchesInternal( const QString &contents ) const;

#ifndef KDEPIM_NO_NEPOMUK
    /**
     * @copydoc SearchRule::addQueryTerms()
     */
    virtual void addQueryTerms( Nepomuk::Query::GroupTerm &groupTerm ) const;
#endif
    
    /**
     * @copydoc SearchRule::addXesamClause(QXmlStreamWriter& stream )
     */
    virtual void addXesamClause(QXmlStreamWriter &stream) const;
private:
#ifndef KDEPIM_NO_NEPOMUK
    /**
     * @copydoc SearchRule::addPersonTerms()
     */
    void addPersonTerm( Nepomuk::Query::GroupTerm &groupTerm, const QUrl &field ) const;
    void addHeaderTerm (Nepomuk::Query::GroupTerm& groupTerm, const Nepomuk::Query::Term& field) const;

#endif
};


/**
 * @short This class represents a search pattern rule operating on numerical values.
 *
 * This class represents a search to be performed against a numerical value,
 * such as the age of the message in days or its size.
 */
class SearchRuleNumerical : public SearchRule
{
  public:
    /**
     * Creates new new numerical search rule.
     *
     * @param field The field to search in.
     * @param function The function to use for searching.
     * @param contents The contents to search for.
     */
    explicit SearchRuleNumerical( const QByteArray &field = 0,
                                  Function function = FuncContains,
                                  const QString &contents = QString() );

    /**
     * @copydoc SearchRule::isEmpty()
     */
    virtual bool isEmpty() const ;

    /**
     * @copydoc SearchRule::matches()
     */
    virtual bool matches( const Akonadi::Item &item ) const;

    // Optimized matching not implemented, will use the unoptimized matching
    // from SearchRule
    using SearchRule::matches;

    /**
     * A helper method for the main matches() method.
     * Does the actual comparing.
     */
    bool matchesInternal( long numericalValue, long numericalContents,
                          const QString &contents ) const;
                          
#ifndef KDEPIM_NO_NEPOMUK
    /**
     * @copydoc SearchRule::addQueryTerms()
     */
    virtual void addQueryTerms( Nepomuk::Query::GroupTerm &groupTerm ) const;
#endif

    /**
     * @copydoc SearchRule::addXesamClause(QXmlStreamWriter& stream )
     */
    virtual void addXesamClause(QXmlStreamWriter &stream) const;
};

//TODO: Check if the below one is needed or not!
// The below are used in several places and here so they are accessible.
  struct MessageStatusInfo {
    const char *text;
    const char *icon;
  };

  // If you change the ordering here; also do it in the enum below
  static const MessageStatusInfo StatusValues[] = {
    { I18N_NOOP2( "message status", "Important" ),              "emblem-important"    },
    { I18N_NOOP2( "message status", "Action Item" ),            "mail-task"           },
    { I18N_NOOP2( "message status", "Unread" ),                 "mail-unread"         },
    { I18N_NOOP2( "message status", "Read" ),                   "mail-read"           },
    { I18N_NOOP2( "message status", "Deleted" ),                "mail-deleted"        },
    { I18N_NOOP2( "message status", "Replied" ),                "mail-replied"        },
    { I18N_NOOP2( "message status", "Forwarded" ),              "mail-forwarded"      },
    { I18N_NOOP2( "message status", "Queued" ),                 "mail-queued"         },
    { I18N_NOOP2( "message status", "Sent" ),                   "mail-sent"           },
    { I18N_NOOP2( "message status", "Watched" ),                "mail-thread-watch"   },
    { I18N_NOOP2( "message status", "Ignored" ),                "mail-thread-ignored" },
    { I18N_NOOP2( "message status", "Spam" ),                   "mail-mark-junk"      },
    { I18N_NOOP2( "message status", "Ham" ),                    "mail-mark-notjunk"   },
    { I18N_NOOP2( "message status", "Has Attachment"),          "mail-attachment"     } //must be last
  };
  // If you change the ordering here; also do it in the array above
  enum StatusValueTypes {
    StatusImportant = 0,
    StatusToAct = 1,
    StatusUnread = 2,
    StatusRead = 3,
    StatusDeleted = 4,
    StatusReplied = 5,
    StatusForwarded = 6,
    StatusQueued = 7,
    StatusSent = 8,
    StatusWatched = 9,
    StatusIgnored = 10,
    StatusSpam = 11,
    StatusHam = 12,
    StatusHasAttachment = 13 //must be last
  };

  static const int StatusValueCount =
    sizeof( StatusValues ) / sizeof( MessageStatusInfo );
  // we want to show all status entries in the quick search bar, but only the
  // ones up to attachment in the search/filter dialog, because there the
  // attachment case is handled separately.
  static const int StatusValueCountWithoutHidden = StatusValueCount - 1;


/** This class represents a search to be performed against the status of a
 *  messsage. The status is represented by a bitfield.
    @short This class represents a search pattern rule operating on message
    status.
*/
class MAILCOMMON_EXPORT SearchRuleStatus : public SearchRule
{
public:
   explicit SearchRuleStatus( const QByteArray & field=0, Function function=FuncContains,
                                const QString & contents=QString() );
   explicit SearchRuleStatus( Akonadi::MessageStatus status, Function function=FuncContains );

   virtual bool isEmpty() const ;
   virtual bool matches( const Akonadi::Item &item ) const;
   
#ifndef KDEPIM_NO_NEPOMUK
   virtual void addQueryTerms( Nepomuk::Query::GroupTerm &groupTerm ) const;
#endif   

   //Not possible to implement optimized form for status searching
   using SearchRule::matches;


  static Akonadi::MessageStatus statusFromEnglishName(const QString&);
  /**
   * @copydoc SearchRule::addXesamClause(QXmlStreamWriter& stream )
   */
  virtual void addXesamClause(QXmlStreamWriter &stream) const;

private:
#ifndef KDEPIM_NO_NEPOMUK
  void addTagTerm( Nepomuk::Query::GroupTerm &groupTerm, const QString &tagId ) const;
#endif
  
private:
   Akonadi::MessageStatus mStatus;
};

// ------------------------------------------------------------------------

/** This class is an abstraction of a search over messages.  It is
    intended to be used inside a KFilter (which adds KFilterAction's),
    as well as in KMSearch. It can read and write itself into a
    KConfig group and there is a constructor, mainly used by KMFilter
    to initialize from a preset KConfig-Group.

    From a class hierarchy point of view, it is a QPtrList of
    SearchRule's that adds the boolean operators (see Operator)
    'and' and 'or' that connect the rules logically, and has a name
    under which it could be stored in the config file.

    As a QPtrList with autoDelete enabled, it assumes that it is the
    central repository for the rules it contains. So if you want to
    reuse a rule in another pattern, make a deep copy of that rule.

    @short An abstraction of a search over messages.
    @author Marc Mutz <Marc@Mutz.com>
*/
class MAILCOMMON_EXPORT SearchPattern : public QList<SearchRule::Ptr>
{

  public:
    /**
     * Boolean operators that connect the return values of the
     * individual rules. A pattern with @p OpAnd will match iff all
     *  it's rules match, whereas a pattern with @p OpOr will match if
     *  any of it's rules matches.
     */
    enum Operator {
      OpAnd,
      OpOr,
      OpAll
    };

    /**
     * Constructor which provides a pattern with minimal, but
     * sufficient initialization. Unmodified, such a pattern will fail
     * to match any KMime::Message. You can query for such an empty
     * rule by using isEmpty, which is inherited from QPtrList.
     */
    SearchPattern();

    /**
     * Constructor that initializes from a given KConfig group, if
     * given. This feature is mainly (solely?) used in KMFilter,
     * as we don't allow to store search patterns in the config (yet).
     */
    SearchPattern( const KConfigGroup &config );

    /** Destructor. Deletes all stored rules! */
    ~SearchPattern();

    /**
     * The central function of this class. Tries to match the set of
     * rules against a KMime::Message. It's virtual to allow derived
     * classes with added rules to reimplement it, yet reimplemented
     * methods should and (&&) the result of this function with their
     * own result or else most functionality is lacking, or has to be
     * reimplemented, since the rules are private to this class.
     *
     * @return true if the match was successful, false otherwise.
     */
    bool matches( const Akonadi::Item &item, bool ignoreBody = false ) const;

    /**
     * Returns true if the pattern only depends the DwString that backs a message.
     */
    bool requiresBody() const;

    /**
     * Removes all empty rules from the list. You should call this
     * method whenever the user had had control of the rules outside of
     * this class. (e.g. after editing it with SearchPatternEdit).
     */
    void purify();

    /**
     * Reads a search pattern from a KConfigGroup. If it does not find
     * a valid saerch pattern in the preset group, initializes the pattern
     * as if it were constructed using the default constructor.
     *
     * For backwards compatibility with previous versions of KMail, it
     * checks for old-style filter rules (e.g. using @p OpIgnore)
     * in @p config und converts them to the new format on writeConfig.
     *
     * Derived classes reimplementing readConfig() should also call this
     * method, or else the rules will not be loaded.
     */
    void readConfig( const KConfigGroup &config );

    /**
     * Writes itself into @p config. Tries to delete old-style keys by
     * overwriting them with QString().
     *
     * Derived classes reimplementing writeConfig() should also call this
     * method, or else the rules will not be stored.
     */
    void writeConfig( KConfigGroup &config ) const;

    /**
     * Returns the name of the search pattern.
     */
    QString name() const
    {
      return mName;
    }

    /**
     * Sets the name of the search pattern. KMFilter uses this to
     * store it's own name, too.
     */
    void setName( const QString &newName )
    {
      mName = newName;
    }

    /**
     * Returns the filter operator.
     */
    SearchPattern::Operator op() const
    {
      return mOperator;
    }

    /**
     * Sets the filter operator.
     */
    void setOp( SearchPattern::Operator aOp )
    {
      mOperator = aOp;
    }

    /**
     * Returns the pattern as string. For debugging.
     */
    QString asString() const;

    /**
     * Returns the pattern as a SPARQL query.
     */
    QString asSparqlQuery(const KUrl::List& url = KUrl::List()) const;

    /**
     * Returns the pattern as a XESAM query.
     */
    QString asXesamQuery() const;

    /**
     * Overloaded assignment operator. Makes a deep copy.
     */
    const SearchPattern &operator=( const SearchPattern &aPattern );

    /**
     * Writes the pattern into a byte array for persistance purposes.
     */
    QByteArray serialize() const;

    /**
     * Constructs the pattern from a byte array serialization.
     */
    void deserialize( const QByteArray & );

    QDataStream &operator>>( QDataStream &s ) const;
    QDataStream &operator<<( QDataStream &s );

  private:
    /**
     * Tries to import a legacy search pattern, ie. one that still has
     * e.g. the @p unless or @p ignore operator which were useful as long as
     * the number of rules was restricted to two. This method is called from
     * readConfig, which detects legacy configurations and also makes sure
     * that this method is called from an initialized object.
     */
    void importLegacyConfig( const KConfigGroup &config );

    /**
     * Initializes the object. Clears the list of rules, sets the name
     * to "<i18n("unnamed")>", and the boolean operator to @p OpAnd.
     */
    void init();
    Nepomuk::Query::ComparisonTerm createChildTerm( const KUrl& url, bool& empty ) const;
    QString  mName;
    Operator mOperator;
};

}
#endif /* MAILCOMMON_SEARCHPATTERN_H_ */
