/*
 * Copyright 2009- ECMWF.
 *
 * This software is licensed under the terms of the Apache Licence version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
 * In applying this licence, ECMWF does not waive the privileges and immunities
 * granted to it by virtue of its status as an intergovernmental organisation
 * nor does it submit to any jurisdiction.
 */

#ifndef ecflow_node_AvisoAttr_HPP
#define ecflow_node_AvisoAttr_HPP

#include <cstdint>
#include <iostream>
#include <string>

#include "ecflow/core/Log.hpp"
#include "ecflow/core/NState.hpp"
#include "ecflow/core/Str.hpp"
#include "ecflow/service/aviso/AvisoService.hpp"

namespace cereal {
class access;
}

class Node;

namespace ecf {

///
/// \brief AvisoAttr represents an attribute, attached to a \ref Node.
///
/// The Aviso attribute effectively acts as a trigger for the Node, granting
/// the Node to be (re)queued as soon as a related notification is received.
///
/// \see https://github.com/ecmwf/aviso
///

class AvisoAttr {
public:
    using path_t     = std::string;
    using name_t     = std::string;
    using listener_t = std::string;
    using active_t   = std::string;
    using url_t      = std::string;
    using schema_t   = std::string;
    using polling_t  = std::string;
    using revision_t = std::uint64_t;
    using auth_t     = std::string;
    using reason_t   = std::string;

    using controller_t     = ecf::service::aviso::AvisoController;
    using controller_ptr_t = std::shared_ptr<controller_t>;

    static constexpr const char* default_url     = "%ECF_AVISO_URL%";
    static constexpr const char* default_schema  = "%ECF_AVISO_SCHEMA%";
    static constexpr const char* default_polling = "%ECF_AVISO_POLLING%";
    static constexpr const char* default_auth    = "%ECF_AVISO_AUTH%";

    static constexpr const char* reload_option_value = "reload";

    static bool is_valid_name(const std::string& name);

    /**
     * Creates a(n invalid) Aviso
     *
     * Note: this is required by Cereal serialization
     *       Cereal invokes the default ctor to create the object and only then proceeds to member-wise serialization.
     */
    AvisoAttr() = default;
    AvisoAttr(Node* parent,
              name_t name,
              const listener_t& handle,
              url_t url,
              schema_t schema,
              polling_t polling,
              revision_t revision,
              auth_t auth,
              const reason_t& reason);
    AvisoAttr(const AvisoAttr& rhs) = default;

    AvisoAttr& operator=(const AvisoAttr& rhs) = default;

    [[nodiscard]] AvisoAttr make_detached() const;

    [[nodiscard]] inline Node* parent() const { return parent_; }
    [[nodiscard]] inline const name_t& name() const { return name_; }
    [[nodiscard]] inline const listener_t& listener() const { return listener_; }
    [[nodiscard]] inline const url_t& url() const { return url_; }
    [[nodiscard]] inline const schema_t& schema() const { return schema_; }
    [[nodiscard]] inline polling_t polling() const { return polling_; }
    [[nodiscard]] inline revision_t revision() const { return revision_; }
    [[nodiscard]] inline const auth_t& auth() const { return auth_; }
    [[nodiscard]] inline const reason_t& reason() const { return reason_; }
    [[nodiscard]] inline const active_t& active() const { return active_; }
    [[nodiscard]] path_t path() const;

    void set_listener(std::string_view listener);
    void set_revision(revision_t revision);

    unsigned int state_change_no() const { return state_change_no_; }

    bool why(std::string& theReasonWhy) const;

    /**
     * Initialises the Aviso procedure, which effectively starts the background polling mechanism.
     * Typically, called when traversing the tree -- does nothing if Aviso service is already set up.
     */
    void reset();

    /**
     * Restarts the Aviso procedure, which effectively stops before restarting the background polling mechanism.
     * Typicallly, called explicitly via Alter command -- forces the reinitialisation of the Aviso service,
     * guaranteeing that parameters, given as ECF variables, are reevaluated.
     */
    void reload();

    [[nodiscard]] bool isFree() const;

    void start() const;
    void finish() const;

    template <class Archive>
    friend void serialize(Archive& ar, AvisoAttr& aviso, std::uint32_t version);

    /**
     * \brief Finishes all the Aviso attributes, effectively stopping the background polling mechanism.
     *
     * @param avisos the avisos to finish
     */
    static void finish(const std::vector<AvisoAttr>& avisos);

    /**
     * \brief When the given state is a Task "terminal" state (i.e. complete, aborted, unknown), finishes all Aviso
     * attributes, effectively stopping the background polling mechanism.
     *
     * @param avisos the avisos to finish
     * @param state the state to check against
     */
    static void finish(const std::vector<AvisoAttr>& avisos, NState::State state);

private:
    void start_controller(const std::string& aviso_path,
                          const std::string& aviso_listener,
                          const std::string& aviso_url,
                          const std::string& aviso_schema,
                          std::uint32_t polling,
                          const std::string& aviso_auth) const;
    void stop_controller(const std::string& aviso_path) const;

    /**
     * @brief The parent Node of this AvisoAttr.
     *
     *  -- This field is *not* serialized nor persisted; it is only used on the server side.
     */
    Node* parent_{nullptr}; // only ever used on the server side, to access parent Node variables

    /**
     * @brief The parent Node path of this AvisoAttr.
     */
    path_t parent_path_;

    /**
     * @brief The name of this AvisoAttr
     */
    name_t name_;

    /**
     * @brief The listener used to launch the Aviso listener
     *
     * This listener is the original configuration, and may contain variable placeholders.
     */
    listener_t listener_;

    /**
     * @brief The URL used to launch the Aviso listener
     *
     * This configuration parameter may contain variable placeholders.
     */
    url_t url_;

    /**
     * @brief The schema used to launch the Aviso listener
     *
     * This configuration parameter may contain variable placeholders.
     */
    schema_t schema_;

    /**
     * @brief The polling interval used to launch the Aviso listener
     *
     * This configuration parameter may contain variable placeholders.
     */
    polling_t polling_;

    /**
     * @brief The authentication token used to launch the Aviso listener
     *
     * This configuration parameter may contain variable placeholders.
     */
    auth_t auth_;

    // The following are mutable as they are modified by the const method isFree()

    /**
     * @brief A message buffer indicating, if any, the reason for the latest failure received from Aviso
     *
     * This field is empty if no error detected; otherwise contains a user facing message describing the error.
     **/
    mutable reason_t reason_{};

    /**
     * @brief The latest revision received from Aviso
     *
     * This is a 'marker' of the lastest revision processed, and is used to avoid receiving repeated notifications.
     **/
    mutable revision_t revision_;

    /**
     * @brief The state change number, used to detect changes in the Aviso attribute
     *
     *  -- This field is *not* serialized nor persisted; it is only used on the server side.
     */
    mutable unsigned int state_change_no_{0};

    /**
     * @brief A 'cache' buffer, storing the fully configured (i.e. all variables substituted) Aviso listener
     *
     * This is the listener actually used to configure the controller.
     */
    mutable active_t active_;

    /**
     * @brief The 'backgroung thread' controller, which is responsible for polling the Aviso service
     *
     * The controller is only instanciated between start() and finish() calls.
     * This allows AvisoAttr to have a copy-ctor and assignment operator.
     *
     *  -- This field is *not* serialized nor persisted; it is only used on the server side.
     **/
    mutable controller_ptr_t controller_;
};

bool operator==(const AvisoAttr& lhs, const AvisoAttr& rhs);

std::string to_python_string(const AvisoAttr& aviso);

template <class Archive>
void serialize(Archive& ar, AvisoAttr& aviso, [[maybe_unused]] std::uint32_t version) {
    ar & aviso.parent_path_;
    ar & aviso.name_;
    ar & aviso.listener_;
    ar & aviso.url_;
    ar & aviso.schema_;
    ar & aviso.polling_;
    ar & aviso.auth_;
    ar & aviso.reason_;
    ar & aviso.revision_;
    ar & aviso.active_;
}

} // namespace ecf

#endif /* ecflow_node_AvisoAttr_HPP */
