/**
 * Copyright Soramitsu Co., Ltd. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

#ifndef IROHA_YAC_HPP
#define IROHA_YAC_HPP

#include <boost/optional.hpp>
#include <memory>
#include <mutex>
#include <rxcpp/rx.hpp>

#include "consensus/yac/cluster_order.hpp"     //  for ClusterOrdering
#include "consensus/yac/outcome_messages.hpp"  // because messages passed by value
#include "consensus/yac/storage/yac_vote_storage.hpp"  // for VoteStorage
#include "consensus/yac/transport/yac_network_interface.hpp"  // for YacNetworkNotifications
#include "consensus/yac/yac_gate.hpp"                         // for HashGate
#include "logger/logger_fwd.hpp"

namespace iroha {
  namespace consensus {
    namespace yac {

      class YacCryptoProvider;
      class Timer;

      class Yac : public HashGate, public YacNetworkNotifications {
       public:
        /**
         * Method for creating Yac consensus object
         * @param delay for timer in milliseconds
         */
        static std::shared_ptr<Yac> create(
            YacVoteStorage vote_storage,
            std::shared_ptr<YacNetwork> network,
            std::shared_ptr<YacCryptoProvider> crypto,
            std::shared_ptr<Timer> timer,
            ClusterOrdering order,
            logger::LoggerPtr log);

        Yac(YacVoteStorage vote_storage,
            std::shared_ptr<YacNetwork> network,
            std::shared_ptr<YacCryptoProvider> crypto,
            std::shared_ptr<Timer> timer,
            ClusterOrdering order,
            logger::LoggerPtr log);

        // ------|Hash gate|------

        void vote(YacHash hash, ClusterOrdering order) override;

        rxcpp::observable<Answer> onOutcome() override;

        // ------|Network notifications|------

        void onState(std::vector<VoteMessage> state) override;

       private:
        // ------|Private interface|------

        /**
         * Voting step is strategy of propagating vote
         * until commit/reject message received
         */
        void votingStep(VoteMessage vote);

        /**
         * Erase temporary data of current round
         */
        void closeRound();

        /**
         * Find corresponding peer in the ledger from vote message
         * @param vote message containing peer information
         * @return peer if it is present in the ledger, boost::none otherwise
         */
        boost::optional<std::shared_ptr<shared_model::interface::Peer>>
        findPeer(const VoteMessage &vote);

        /// Remove votes from unknown peers from given vector.
        void removeUnknownPeersVotes(std::vector<VoteMessage> &votes);

        // ------|Apply data|------
        void applyState(const std::vector<VoteMessage> &state);

        // ------|Propagation|------
        void propagateState(const std::vector<VoteMessage> &msg);
        void propagateStateDirectly(const shared_model::interface::Peer &to,
                                    const std::vector<VoteMessage> &msg);
        void tryPropagateBack(const std::vector<VoteMessage> &state);

        // ------|Fields|------
        YacVoteStorage vote_storage_;
        std::shared_ptr<YacNetwork> network_;
        std::shared_ptr<YacCryptoProvider> crypto_;
        std::shared_ptr<Timer> timer_;
        rxcpp::subjects::subject<Answer> notifier_;
        std::mutex mutex_;

        // ------|One round|------
        ClusterOrdering cluster_order_;

        // ------|Logger|------
        logger::LoggerPtr log_;
      };
    }  // namespace yac
  }    // namespace consensus
}  // namespace iroha

#endif  // IROHA_YAC_HPP
