#ifndef STATEMENTTOKENBUILDER_H
#define STATEMENTTOKENBUILDER_H

#include "token.h"
#include "ast/sqliteconflictalgo.h"
#include "ast/sqlitesortorder.h"

class SqliteStatement;

/**
 * @brief Builder producing token list basing on certain inputs.
 *
 * This builder provides several methods to build list of tokens from various input values. It can produce
 * token list for entire AST objects, or it can produce token list for list of names, etc.
 *
 * Token builder is used in SqliteStatement derived classes to rebuild SqliteStatement::tokens basing on the
 * values in their class members.
 *
 * Typical use case:
 * @code
 * TokenList SqliteCreateView::rebuildTokensFromContents()
 * {
 *     StatementTokenBuilder builder;
 *
 *     builder.withKeyword("CREATE").withSpace();
 *     if (tempKw)
 *         builder.withKeyword("TEMP").withSpace();
 *     else if (temporaryKw)
 *         builder.withKeyword("TEMPORARY").withSpace();
 *
 *     builder.withKeyword("VIEW").withSpace();
 *     if (ifNotExists)
 *         builder.withKeyword("IF").withSpace().withKeyword("NOT").withSpace().withKeyword("EXISTS").withSpace();
 *
 *     if (!database.isNull())
 *         builder.withOther(database).withOperator(".");
 *
 *     builder.withOther(view).withSpace().withKeyword("AS").withStatement(select);
 *
 *     return builder.build();
 * }
 * @endcode
 */
class StatementTokenBuilder
{
    public:
        /**
         * @brief Constructor.
         * @param replaceStatementTokens Defines whether tokens in statements passed to withStatement() and withStatementList()
         *                               methods should be replaced with tokens built by those statements themselves.
         *
         * This makes all the difference between SqliteStatement::rebuildTokens() and SqliteStatement::produceTokens() methods.
         * The first one replaces tokens in statements with the tokens produced by those statements,
         * while the second one just takes tokens from statements without any modification. So if you want to produce tokens
         * that are exactly the same as tokens from statements, pass false to this constructor. If you want to produce tokens
         * that are built by statements themselves, pass true to this constructor. The default value is true, since in most cases
         * you want to produce tokens built by statements, and not just take tokens from statements. The only case when you want to take
         * tokens from statements is when you want to produce tokens for the statement that was not modified since it was parsed, so its tokens are still valid.
         */
        StatementTokenBuilder(bool replaceStatementTokens);

        /**
         * @brief Adds keyword token.
         * @param value Value of the keyword token.
         * @return Reference to the builder for the further building.
         *
         * Keyword \p value gets converted to upper case.
         */
        StatementTokenBuilder& withKeyword(const QString& value);

        /**
         * @brief Adds "other" token (some object name, or other word).
         * @param value Value for the token.
         * @return Reference to the builder for the further building.
         *
         * This is used for table names, etc. The \p value is quoted just as passed.
         */
        StatementTokenBuilder& withOther(const QString& value);

        /**
         * @brief Adds "other" token (some object name, or other word).
         * @param value Value for the token.
         * @param wrapIfNeeded Defined whether the value should be wrapped if identified to require wrapping (i.e. is a keyword, or multiword).
         * @return Reference to the builder for the further building.
         *
         * This is used for table names, etc. The \p value is quoted just as passed.
         * You may want to pass wrapIfNeeded=false if you know that this may be equal to keyword,
         * that is allowed as ID too (Lemon's fallback to ID).
         */
        StatementTokenBuilder& withOther(const QString& value, bool wrapIfNeeded);

        /**
         * @brief Adds string using double-quote wrapping.
         * @param value Value for the token.
         * @return Reference to the builder for the further building.
         *
         * The \p value is wrapped with double quote, but if it's not possible then the proper wrapper is used by wrapObjIfNeeded().
         *
         * @overload
         */
        StatementTokenBuilder& withStringPossiblyOther(const QString& value, bool preferSingleQuote);

        /**
         * @brief Adds list of "other" tokens.
         * @param value List of values for tokens.
         * @param separator Optional value for separator tokens.
         * @return Reference to the builder for the further building.
         *
         * Given the input \p value, this method produces list of tokens. Additionally it can put extra separator
         * token between all produced tokens using the \p separator value. To skip separator tokens pass
         * an empty string as the separator value.
         */
        StatementTokenBuilder& withOtherList(const QList<QString>& value, const QString& separator = ",");

        /**
         * @brief Adds operator token.
         * @param value Value of the operator (";", "+", etc).
         * @return Reference to the builder for the further building.
         */
        StatementTokenBuilder& withOperator(const QString& value);

        /**
         * @brief Adds comment token.
         * @param value Comment value, including start/end characters of the comment.
         * @return Reference to the builder for the further building.
         */
        StatementTokenBuilder& withComment(const QString& value);

        /**
         * @brief Adds decimal number token.
         * @param value Value for the token.
         * @return Reference to the builder for the further building.
         */
        StatementTokenBuilder& withFloat(const QVariant &value);

        /**
         * @brief Add integer numer token.
         * @param value Value for the token.
         * @return Reference to the builder for the further building.
         */
        StatementTokenBuilder& withInteger(qint64 value);

        /**
         * @brief Adds bind parameter token.
         * @param value Name of the bind parameter, including ":" or "@" at the begining.
         * @return Reference to the builder for the further building.
         */
        StatementTokenBuilder& withBindParam(const QString& value);

        /**
         * @brief Adds left parenthesis token (<tt>"("</tt>).
         * @return Reference to the builder for the further building.
         */
        StatementTokenBuilder& withParLeft();

        /**
         * @brief Adds right parenthesis token (<tt>")"</tt>).
         * @return Reference to the builder for the further building.
         */
        StatementTokenBuilder& withParRight();

        /**
         * @brief Adds a single whitespace token.
         * @return Reference to the builder for the further building.
         */
        StatementTokenBuilder& withSpace();

        /**
         * @brief Adds BLOB value token.
         * @param value BLOB value for the token.
         * @return Reference to the builder for the further building.
         */
        StatementTokenBuilder& withBlob(const QString& value);

        /**
         * @brief Adds string value token.
         * @param value Value for the token.
         * @return Reference to the builder for the further building.
         *
         * The string is wrapped with single quote characters if it's not wrapped yet.
         */
        StatementTokenBuilder& withString(const QString& value);

        /**
         * @brief Adds set of tokens represeting "ON CONFLICT" statement.
         * @param onConflict Conflict resolution algorithm to build for.
         * @return Reference to the builder for the further building.
         *
         * If algorithm is SqliteConflictAlgo::null, no tokens are added.
         */
        StatementTokenBuilder& withConflict(SqliteConflictAlgo onConflict);

        /**
         * @brief Adds space and <tt>"ASC"/"DESC"</tt> token.
         * @param sortOrder Sort order to use.
         * @return Reference to the builder for the further building.
         *
         * If the sort order is SqliteSortOrder::null, no tokens are added.
         */
        StatementTokenBuilder& withSortOrder(SqliteSortOrder sortOrder);

        /**
         * @brief Adds set of tokens representing entire statement.
         * @param stmt Statement to add tokens for.
         * @return Reference to the builder for the further building.
         */
        StatementTokenBuilder& withStatement(SqliteStatement* stmt);

        /**
         * @brief Adds already defined list of tokens to this builder.
         * @param tokens Tokens to add.
         * @return Reference to the builder for the further building.
         */
        StatementTokenBuilder& withTokens(TokenList tokens);

        /**
         * @brief Adds literal value token (integer, decimal, string, BLOB).
         * @param value Value for the token.
         * @return Reference to the builder for the further building.
         *
         * This method tries to convert given \p value to integer,
         * then to double, then it checks if the value has format <tt>X'...'</tt>
         * and if if succeeded at any of those steps, then it adds appropriate
         * token. If none of above succeeded, then the string token is added.
         */
        StatementTokenBuilder& withLiteralValue(const QVariant& value);

        /**
         * @brief Adds tokens representing list of entire statements.
         * @param stmtList List of statements to add tokens for.
         * @param separator Optional separator to be used for separator tokens.
         * @return Reference to the builder for the further building.
         *
         * This method is very similar to withOtherList(), except it works
         * on the entire statements.
         */
        template <class T>
        StatementTokenBuilder& withStatementList(QList<T*> stmtList, const QString& separator = ",")
        {
            bool first = true;
            for (T* stmt : stmtList)
            {
                if (!first)
                {
                    if (!separator.isEmpty())
                        withOperator(separator);

                    withSpace();
                }
                withStatement(stmt);
                first = false;
            }
            return *this;
        }

        /**
         * @brief Provides all tokens added so far as a compat token list.
         * @return List of tokens built so far.
         */
        TokenList build() const;

        /**
         * @brief Cleans up all tokens added so far.
         */
        void clear();

    private:
        /**
         * @brief Adds token of given type and value.
         * @param type Type of the token to add.
         * @param value Value for the token to add.
         * @return Reference to the builder for the further building.
         */
        StatementTokenBuilder& with(Token::Type type, const QString& value);

        void updateCurrentIdxFromTokens();

        /**
         * @brief List of tokens added so far.
         */
        TokenList tokens;

        /**
         * @brief Current character position index.
         *
         * This index is used to generate proper values for Token::start and Token::end.
         * Each added token increments this index by the value length.
         */
        int currentIdx = 0;

        /**
         * @brief Defines whether the tokens added for statements in withStatement() and withStatementList() methods should replace tokens of those statements.
         *
         * If false, then tokens of statements added with withStatement() and withStatementList() are kept as they are, and built tokens are to be only returned
         * to the caller, without altering the statement.
         */
        bool replaceStatementTokens = false;
};

#endif // STATEMENTTOKENBUILDER_H
