/***************************************************************************
 *   SPDX-FileCopyrightText: 2006 Till Adam <adam@kde.org>                 *
 *                                                                         *
 *   SPDX-License-Identifier: LGPL-2.0-or-later                            *
 ***************************************************************************/
#pragma once

#include "connection.h"
#include "exception.h"
#include "global.h"

#include "private/protocol_p.h"

namespace Akonadi
{
namespace Server
{
class AkonadiServer;
class Response;

AKONADI_EXCEPTION_MAKE_INSTANCE(HandlerException);

/**
  \defgroup akonadi_server_handler Command handlers

  All commands supported by the Akonadi server are implemented as sub-classes of Akonadi::Handler.
*/

/**
The handler interfaces describes an entity capable of handling an AkonadiIMAP command.*/
class Handler
{
public:
    Handler() = delete;
    Handler(const Handler &) = delete;
    Handler(Handler &&) noexcept = delete;
    Handler &operator=(const Handler &) = delete;
    Handler &operator=(Handler &&) noexcept = delete;

    virtual ~Handler() = default;

    /**
     * Set the tag of the command to be processed, and thus of the response
     * generated by this handler.
     * @param tag The command tag, an alphanumerical string, normally.
     */
    void setTag(quint64 tag);

    /**
     * The tag of the command associated with this handler.
     */
    quint64 tag() const;

    void setCommand(const Protocol::CommandPtr &cmd);
    Protocol::CommandPtr command() const;

    /**
     * Find a handler for a command that is always allowed, like LOGOUT.
     * @param cmd the command string
     * @return an instance to the handler. The handler is deleted after @see handelLine is executed. The caller needs to delete the handler in case an exception
     * is thrown from handelLine.
     */
    static std::unique_ptr<Handler> findHandlerForCommandAlwaysAllowed(Protocol::Command::Type cmd, AkonadiServer &akonadi);

    /**
     * Find a handler for a command that is allowed when the client is not yet authenticated, like LOGIN.
     * @param cmd the command string
     * @return an instance to the handler. The handler is deleted after @see handelLine is executed. The caller needs to delete the handler in case an exception
     * is thrown from handelLine.
     */
    static std::unique_ptr<Handler> findHandlerForCommandNonAuthenticated(Protocol::Command::Type cmd, AkonadiServer &akonadi);

    /**
     * Find a handler for a command that is allowed when the client is authenticated, like LIST, FETCH, etc.
     * @param cmd the command string
     * @return an instance to the handler. The handler is deleted after @see handelLine is executed. The caller needs to delete the handler in case an exception
     * is thrown from handelLine.
     */
    static std::unique_ptr<Handler> findHandlerForCommandAuthenticated(Protocol::Command::Type cmd, AkonadiServer &akonadi);

    void setConnection(Connection *connection);
    Connection *connection() const;
    DataStore *storageBackend() const;

    AkonadiServer &akonadi() const;

    bool failureResponse(const char *response);
    bool failureResponse(const QByteArray &response);
    bool failureResponse(const QString &response);

    template<typename T>
    inline bool successResponse();
    template<typename T>
    inline bool successResponse(T &&response);

    template<typename T>
    inline void sendResponse(T &&response);
    template<typename T>
    inline void sendResponse();

    /**
     * Parse and handle the IMAP message using the streaming parser. The implementation MUST leave the trailing newline character(s) in the stream!
     * @return true if parsed successfully, false in case of parse failure
     */
    virtual bool parseStream() = 0;

    bool checkScopeConstraints(const Scope &scope, const QList<Scope::SelectionScope> &permittedScopes) const;

protected:
    Handler(AkonadiServer &akonadi);

private:
    AkonadiServer &m_akonadi;
    quint64 m_tag = 0;
    Connection *m_connection = nullptr;
    bool m_sentFailureResponse = false;

protected:
    Protocol::CommandPtr m_command;
};

template<typename T>
inline bool Handler::successResponse()
{
    sendResponse<T>(T{});
    return true;
}

template<typename T>
inline bool Handler::successResponse(T &&response)
{
    sendResponse<T>(std::move(response));
    return true;
}

template<typename T>
inline void Handler::sendResponse()
{
    m_connection->sendResponse<T>(T{});
}

template<typename T>
inline void Handler::sendResponse(T &&response)
{
    m_connection->sendResponse<T>(std::move(response));
}

} // namespace Server
} // namespace Akonadi
