/*
   This file is part of the Nepomuk KDE project.
   Copyright (C) 2008-2010 Sebastian Trueg <trueg@kde.org>

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License version 2 as published by the Free Software Foundation.

   This library 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
   Library General Public License for more details.

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

#ifndef _NEPOMUK_QUERY_QUERY_H_
#define _NEPOMUK_QUERY_QUERY_H_

#include <QtCore/QSharedDataPointer>
#include <QtCore/QList>
#include <QtCore/QPair>
#include <QtCore/QDebug>

#include <kurl.h>

#include "property.h"

#include "nepomukquery_export.h"

class QTextStream;

namespace Nepomuk {
    namespace Query {

        class Term;
        class QueryPrivate;
        class FileQuery;

        /**
         * \brief Convinience definition for request property mappings as used in
         * QueryServiceClient::sparqlQuery() and QueryServiceClient::blockingSparqlQuery().
         */
        typedef QHash<QString, Nepomuk::Types::Property> RequestPropertyMap;

        /**
         * \class Query query.h Nepomuk/Query/Query
         *
         * \brief A Nepomuk desktop query.
         *
         * A Query consists or a combination of Terms that can be optionally
         * restricted via a limit (setLimit()) or folder filters (setIncludeFolders() and
         * setExcludeFolders()).
         *
         * Additionally setRequestProperties() allows to retrieve additional information
         * about the results.
         *
         * Queries can be easily converted to SPARQL via the toSparqlQuery() method or to
         * search URLs ready for KIO::DirLister using toSearchUrl().
         *
         * The typical usage of a Query instance would be QueryServiceClient::query().
         * However one can also use toSparqlQuery() to convert it into a SPARQL query
         * string and use that string in QueryServiceClient::sparqlQuery() or even
         * directly in Soprano::Model::executeQuery() via \p ResourceManager::instance()->mainModel()
         * or in a custom Model.
         *
         * \sa QueryParser, FileQuery
         *
         * \author Sebastian Trueg <trueg@kde.org>
         *
         * \since 4.4
         */
        class NEPOMUKQUERY_EXPORT Query
        {
        public:
            /**
             * Create an empty invalid query object.
             */
            Query();

            /**
             * Create a query with root term \a term.
             */
            explicit Query( const Term& term );

            /**
             * Copy constructor.
             */
            Query( const Query& );

            /**
             * Destructor
             */
            ~Query();

            /**
             * Assignment operator
             */
            Query& operator=( const Query& );

            /**
             * Assignment operator. Assigns \p term to
             * this query without changing any other
             * properties.
             *
             * \since 4.5
             */
            Query& operator=( const Term& term );

            /**
             * \return \p true if the query is valid, i.e.
             * it has a valid term().
             */
            bool isValid() const;

            /**
             * \return \p true if this is a file query that will
             * only return files and folders.
             *
             * \sa FileQuery
             *
             * \since 4.5
             */
            bool isFileQuery() const;

            /**
             * \return A copy of this query which is restricted to files.
             *
             * \since 4.6
             */
            FileQuery toFileQuery() const;

            /**
             * The root term of the query.
             * This can be any type of term.
             *
             * \sa setTerm()
             */
            Term term() const;

            /**
             * The maximum number of results that this query should yield.
             *
             * \sa setLimit()
             */
            int limit() const;

            /**
             * The first result that should be retrieved.
             *
             * \sa setOffset()
             *
             * \since 4.5
             */
            int offset() const;

            /**
             * Set the root term of the query.
             *
             * \sa term
             */
            void setTerm( const Term& );

            /**
             * Set the maximum number of results this query
             * should yield.
             *
             * \sa limit
             */
            void setLimit( int );

            /**
             * The first result that should be retrieved. This can be combined
             * with setLimit() to do paged results.
             *
             * \since 4.5
             */
            void setOffset( int offset );

            /**
             * %Nepomuk supports scoring the results based on any full text matching
             * used in the query (full text matching is done via ComparisonTerm with
             * the ComparisonTerm::Contains comperator) and sorting the results based
             * on that score.
             *
             * By default full text scoring is disabled since it can mean a serious
             * impact on query performance.
             *
             * \sa setFullTextScoringSortOrder(), Result::score()
             *
             * \since 4.6
             */
            void setFullTextScoringEnabled( bool enabled );

            /**
             * Set the full text scoring sort order. Ignored if full text scoring is
             * disabled.
             *
             * By default the sort order is Qt::DescendingOrder.
             *
             * \sa setFullTextScoringEnabled()
             *
             * \since 4.6
             */
            void setFullTextScoringSortOrder( Qt::SortOrder order );

            /**
             * \return \p true if full text scoring has been enabled.
             *
             * \sa setFullTextScoringEnabled()
             *
             * \since 4.6
             */
            bool fullTextScoringEnabled() const;

            /**
             * \return The full text scoring sort order as set via setFullTextScoringSortOrder()
             *
             * \since 4.6
             */
            Qt::SortOrder fullTextScoringSortOrder() const;

            /**
             * A set of flags that influence the result of the query.
             *
             * \sa setQueryFlags(), SparqlFlags
             *
             * \since 4.6
             */
            enum QueryFlag {
                /**
                 * No flags set. This is the default.
                 */
                NoQueryFlags = 0x0,

                /**
                 * By default queries that will only return results which are intended for
                 * the user's eyes. In situations where an application needs to work on
                 * internal or statistical data this restriction is not desireable.
                 * This flag disables the restriction and returns the full set of results.
                 */
                NoResultRestrictions = 0x1,

                /**
                 * Disables the return of full text search excerpts for ComparisonTerm::Contains
                 * terms which are normally reported through Result::excerpt(). It might make sense
                 * to set this flag in case one has no need for excerpts and does not want to suffer
                 * the small performance penalty that comes from querying them
                 */
                WithoutFullTextExcerpt = 0x2
            };
            Q_DECLARE_FLAGS( QueryFlags, QueryFlag )

            /**
             * Set the query flags to configure this query.
             *
             * \sa queryFlags()
             *
             * \since 4.6
             */
            void setQueryFlags( QueryFlags flags );

            /**
             * Get the query flags to configure this query.
             *
             * \sa setQueryFlags()
             *
             * \since 4.6
             */
            QueryFlags queryFlags() const;

            /**
             * \class RequestProperty query.h Nepomuk/Query/Query
             *
             * \brief A request property can be added to a Query to retrieve
             * additional information about the results.
             *
             * Normally a query would simply yield a list of resources,
             * ie. URIs. Using RequestProperty one can request additional
             * fields such as the modification time or the label or whatever
             * is of interest in the current context.
             *
             * \sa addRequestProperty, setRequestProperties
             */
            class NEPOMUKQUERY_EXPORT RequestProperty {
            public:
                /**
                 * Create a new request property.
                 * \param property The requested %property.
                 * \param optional if \p true the requested property will
                 * be %optional, ie. marked as OPTIONAL in SPARQL queries.
                 */
                RequestProperty( const Nepomuk::Types::Property& property,
                                 bool optional = true );

                /**
                 * Copy constructor.
                 */
                RequestProperty( const RequestProperty& );

                /**
                 * Destructor.
                 */
                ~RequestProperty();

                /**
                 * Copy operator.
                 */
                RequestProperty& operator=( const RequestProperty& );

                /**
                 * Comparison operator. Two RequestProperty instances
                 * are equal if their property() and value of optional()
                 * are equal.
                 */
                bool operator==( const RequestProperty& other ) const;

                /**
                 * \return The property set in the constructor.
                 */
                Nepomuk::Types::Property property() const;

                /**
                 * \return The optional value set in the constructor.
                 */
                bool optional() const;

            private:
                class Private;
                QSharedDataPointer<Private> d;
            };

            /**
             * Add a property that should be reported with each search result.
             *
             * \param property The requested property.
             *
             * ComparisonTerm::setVariableName() provides a more flexible (but also
             * slightly more complicated) way to select additional results.
             *
             * \sa setRequestProperties, requestProperties, ComparisonTerm::setVariableName()
             */
            void addRequestProperty( const RequestProperty& property );

            /**
             * Set the properties that should be reported with each search result.
             *
             * \param properties The requested properties.
             *
             * ComparisonTerm::setVariableName() provides a more flexible (but also
             * slightly more complicated) way to select additional results.
             *
             * \sa addRequestProperty, requestProperties, ComparisonTerm::setVariableName()
             */
            void setRequestProperties( const QList<RequestProperty>& properties );

            /**
             * \return The list of RequestProperty instances set via addRequestProperty
             * and setRequestProperties.
             */
            QList<RequestProperty> requestProperties() const;

            /**
             * \brief Aditional flags modifying the behaviour of toSparqlQuery() and toSearchUrl().
             */
            enum SparqlFlag {
                /**
                 * No flags, i.e. create a default query.
                 */
                NoFlags = 0x0,

                /**
                 * Create a SPARQL count query which will return the number
                 * of results rather than the results themselves. The resuling query will have
                 * a single binding called \p 'cnt'.
                 */
                CreateCountQuery = 0x1,

                /**
                 * Automatically handle inverse properties, consider for example nie:isPartOf
                 * and nie:hasPart at the same time and even if only one of both is
                 * defined.
                 */
                HandleInverseProperties = 0x2,

                /**
                 * Create a SPARQL ask query which will simply check if a matching result exists.
                 * Use Soprano::QueryResultIterator::boolValue() to check the result.
                 *
                 * \since 4.6
                 */
                CreateAskQuery = 0x4
            };
            Q_DECLARE_FLAGS( SparqlFlags, SparqlFlag )

            /**
             * Convert the query into a SPARQL query which can be used with the
             * Nepomuk query service or directly in Soprano::Model::executeQuery.
             *
             * It is recommended to use QueryServiceClient instead of creating
             * the SPARQL query this way.
             *
             * The resulting query will bind the results to variable \p 'r'. Request
             * properties will be bound to variables \p 'reqProp1' through \p 'reqPropN'
             * (the only exception is a count query created via the CreateCountQuery flag).
             *
             * If you are looking for a serialization of a Query which can be parsed again
             * use toString() instead.
             *
             * \warning The SPARQL queries created by this method contain SPARQL extensions
             * from Virtuoso and will not work with other RDF storage solutions!
             *
             * \param flags Optional flags to change the query.
             *
             * \return The SPARQL representation of this query or an empty string
             * if the query could not be converted (invalid query.)
             *
             * \sa toString(), toSearchUrl(), SparqlFlag
             */
            QString toSparqlQuery( SparqlFlags flags = NoFlags ) const;

            /**
             * Convert the query into a URL which can be listed using KIO::DirLister.
             * The URL will use the \p nepomuksearch:/ KIO protocol to handle the listing
             * of search results.
             *
             * \param flags Optional flags to change the query.  Query::CreateCountQuery is not
             * supported and will silently be dropped from \p flags.
             *
             * \return A URL which will list a virtual folder containing all search results
             * from this query or an invalid URL in case this query is invalid.
             *
             * \sa toSparqlQuery(), SparqlFlag
             */
            KUrl toSearchUrl( SparqlFlags flags = NoFlags ) const;

            /**
             * Convert the query into a URL which can be listed using KIO::DirLister.
             * The URL will use the \p nepomuksearch:/ KIO protocol to handle the listing
             * of search results.
             *
             * This is the perfect method for listing results in file managers or file
             * dialogs.
             *
             * \param customTitle An optional custom title that will be used for the listing
             * of the results. This is achieved by setting the KIO::UDSEntry::UDS_DISPLAY_NAME to
             * the customTitle value.
             * \param flags Optional flags to change the query.  Query::CreateCountQuery is not
             * supported and will silently be dropped from \p flags.
             *
             * \return A URL which will list a virtual folder containing all search results
             * from this query or an invalid URL in case this query is invalid.
             *
             * \sa toSparqlQuery(), SparqlFlag
             *
             * \since 4.5
             */
            KUrl toSearchUrl( const QString& customTitle, SparqlFlags flags = NoFlags ) const;

            /**
             * Build a request property map as used in QueryServiceClient::sparqlQuery()
             * from the request properties set via addRequestProperty() and setRequestProperties().
             *
             * Be aware that in most situations it is much simpler to use QueryServiceClient::query().
             */
            RequestPropertyMap requestPropertyMap() const;

            /**
             * Comparison operator.
             *
             * \return \p true if this query is equal to \p query.
             */
            bool operator==( const Query& query ) const;

            /**
             * Comparison operator.
             *
             * \return \p true if this query differs from \p query.
             *
             * \since 4.6
             */
            bool operator!=( const Query& query ) const;

            /**
             * Optimizes the query without chaning its meaning. This removes
             * redundant terms such as NegationTerm and OptionalTerm nestings and flattens
             * AndTerm and OrTerm hierarchies.
             *
             * \return An optimized version of this query.
             *
             * \since 4.6
             *
             * \sa Term::optimized()
             */
            Query optimized() const;

            /**
             * Encode the Query in a string. Be aware that this does NOT create a SPARQL
             * query. The returned string can be used to serialize queries that can later
             * be read via fromString().
             *
             * \sa fromString(), toSparqlQuery()
             *
             * \since 4.5
             */
            QString toString() const;

            /**
             * Parse a Query that has been encoded as a string via toString().
             *
             * \warning This method can NOT parse SPARQL syntax.
             *
             * \sa toString()
             *
             * \since 4.5
             */
            static Query fromString( const QString& queryString );

            /**
             * Extract a query from a nepomuksearch:/ query URL.
             *
             * \return The query that was encoded in \p url or an invalid query if
             * either \p url is not a nepomuksearch:/ URL or if it contains a pure SPARQL
             * query. In the latter case sparqlFromQueryUrl() can be used to extract that
             * query.
             *
             * \sa sparqlFromQueryUrl()
             *
             * \since 4.5
             */
            static Query fromQueryUrl( const KUrl& url );

            /**
             * Extract the SPARQL query from a nepomuksearch:/ query URL. All kinds of
             * nepomuksearch:/ URLs are supported.
             *
             * \return The SPARQL query string representing the query encoded
             * in \p url.
             *
             * \sa fromQueryUrl()
             *
             * \since 4.5
             */
            static QString sparqlFromQueryUrl( const KUrl& url );

            /**
             * Extact the title from a nepomuksearch:/ query URL. The title
             * is either a custom title which has been specified in
             * toSearchUrl(const QString&, SparqlFlags) or the user query string
             * in case of nepomuksearch:/myquery URLs.
             *
             * \return A title for the query \p url or an empty string
             * in case \p url is not a nepomuksearch:/ URL or a useful title
             * cannot be extracted.
             *
             * \since 4.5
             */
            static QString titleFromQueryUrl( const KUrl& url );

        protected:
            /** \cond protected_query_members */
            QSharedDataPointer<QueryPrivate> d;
            /** \endcond */
        };

        /**
         * Logical and operator which combines \p term into the term
         * of \p query to match both.
         *
         * \sa AndTerm
         *
         * \since 4.6
         */
        NEPOMUKQUERY_EXPORT Query operator&&( const Query& query, const Term& term );

        /**
         * Logical or operator which combines \p term into the term
         * of \p query to match either one.
         *
         * \sa OrTerm
         *
         * \since 4.6
         */
        NEPOMUKQUERY_EXPORT Query operator||( const Query& query, const Term& term );

        /**
         * Logical negation operator which negates the meaning of
         * a query.
         *
         * \sa NegationTerm::negateTerm()
         *
         * \since 4.6
         */
        NEPOMUKQUERY_EXPORT Query operator!( const Query& query );

        NEPOMUKQUERY_EXPORT uint qHash( const Nepomuk::Query::Query& );
    }
}

Q_DECLARE_OPERATORS_FOR_FLAGS( Nepomuk::Query::Query::SparqlFlags )
Q_DECLARE_OPERATORS_FOR_FLAGS( Nepomuk::Query::Query::QueryFlags )

NEPOMUKQUERY_EXPORT QDebug operator<<( QDebug, const Nepomuk::Query::Query& );

#endif
