// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef OSP_IMPL_PRESENTATION_URL_AVAILABILITY_REQUESTER_H_
#define OSP_IMPL_PRESENTATION_URL_AVAILABILITY_REQUESTER_H_

#include <map>
#include <memory>
#include <set>
#include <string>
#include <vector>

#include "osp/msgs/osp_messages.h"
#include "osp/public/message_demuxer.h"
#include "osp/public/presentation/presentation_controller.h"
#include "osp/public/protocol_connection_client.h"
#include "osp/public/service_info.h"
#include "platform/api/time.h"
#include "platform/base/error.h"

namespace openscreen {
namespace osp {

// Handles Presentation API URL availability requests and persistent watches.
// It keeps track of the set of currently known receivers as well as all
// registered URLs and observers in order to query each receiver with all URLs.
// It uses the availability protocol message watch mechanism to stay informed of
// any availability changes as long as at least one observer is registered for a
// given URL.
class UrlAvailabilityRequester {
 public:
  explicit UrlAvailabilityRequester(ClockNowFunctionPtr now_function);
  ~UrlAvailabilityRequester();

  // Adds a persistent availability request for |urls| to all known receivers.
  // These URLs will also be queried for any receivers discovered in the future.
  // |observer| will be called back once for the first known availability (which
  // may be cached from previous requests) and when the availability of any of
  // these URLs changes on any receiver.
  void AddObserver(const std::vector<std::string>& urls,
                   ReceiverObserver* observer);

  // Disassociates |observer| from all the URLs in |urls| so it will no longer
  // receive availability updates for these URLs.  Additionally, if |urls| is
  // only a subset of the URL list it was originally added with, it will still
  // be observing the URLs not included here.
  void RemoveObserverUrls(const std::vector<std::string>& urls,
                          ReceiverObserver* observer);

  // Disassociates |observer| from all the URLs it is observing.  This
  // guarantees that it is safe to delete |observer| after this call.
  void RemoveObserver(ReceiverObserver* observer);

  // Informs the UrlAvailabilityRequester of changes to the set of known
  // receivers.  New receivers are immediately queried for all currently
  // observed URLs and removed receivers cause any URLs that were available on
  // that receiver to become unavailable.
  void AddReceiver(const ServiceInfo& info);
  void ChangeReceiver(const ServiceInfo& info);
  void RemoveReceiver(const ServiceInfo& info);
  void RemoveAllReceivers();

  // Ensures that all open availability watches (to all receivers) that are
  // about to expire are refreshed by sending a new request with the same URLs.
  // Returns the time point at which this should next be scheduled to run.
  Clock::time_point RefreshWatches();

 private:
  // Handles Presentation API URL availability requests and watches for one
  // particular receiver.  When first constructed, it attempts to open a
  // ProtocolConnection to the receiver, then it makes an availability request
  // for all the observed URLs, then it continues to listen for update events
  // during the following watch period.  Before a watch will expire, it needs to
  // send a new request to restart the watch, as long as there are active
  // observers for a given URL.
  struct ReceiverRequester final
      : ProtocolConnectionClient::ConnectionRequestCallback,
        MessageDemuxer::MessageCallback {
    struct Request {
      uint64_t watch_id;
      std::vector<std::string> urls;
    };

    struct Watch {
      Clock::time_point deadline;
      std::vector<std::string> urls;
    };

    ReceiverRequester(UrlAvailabilityRequester* listener,
                      const std::string& service_id,
                      const IPEndpoint& endpoint);
    ~ReceiverRequester() override;

    void GetOrRequestAvailabilities(
        const std::vector<std::string>& requested_urls,
        ReceiverObserver* observer);
    void RequestUrlAvailabilities(std::vector<std::string> urls);
    ErrorOr<uint64_t> SendRequest(uint64_t request_id,
                                  const std::vector<std::string>& urls);
    Clock::time_point RefreshWatches(Clock::time_point now);
    Error::Code UpdateAvailabilities(
        const std::vector<std::string>& urls,
        const std::vector<msgs::UrlAvailability>& availabilities);
    void RemoveUnobservedRequests(const std::set<std::string>& unobserved_urls);
    void RemoveUnobservedWatches(const std::set<std::string>& unobserved_urls);
    void RemoveReceiver();

    // ProtocolConnectionClient::ConnectionRequestCallback overrides.
    void OnConnectionOpened(
        uint64_t request_id,
        std::unique_ptr<ProtocolConnection> connection) override;
    void OnConnectionFailed(uint64_t request_id) override;

    // MessageDemuxer::MessageCallback overrides.
    ErrorOr<size_t> OnStreamMessage(uint64_t endpoint_id,
                                    uint64_t connection_id,
                                    msgs::Type message_type,
                                    const uint8_t* buffer,
                                    size_t buffer_size,
                                    Clock::time_point now) override;

    UrlAvailabilityRequester* const listener;

    uint64_t next_watch_id = 1;

    const std::string service_id;
    uint64_t endpoint_id{0};

    ProtocolConnectionClient::ConnectRequest connect_request;
    // TODO(btolsch): Observe connection and restart all the things on close.
    std::unique_ptr<ProtocolConnection> connection;

    MessageDemuxer::MessageWatch response_watch;
    std::map<uint64_t, Request> request_by_id;
    MessageDemuxer::MessageWatch event_watch;
    std::map<uint64_t, Watch> watch_by_id;

    std::map<std::string, msgs::UrlAvailability> known_availability_by_url;
  };

  const ClockNowFunctionPtr now_function_;

  std::map<std::string, std::vector<ReceiverObserver*>> observers_by_url_;

  std::map<std::string, std::unique_ptr<ReceiverRequester>>
      receiver_by_service_id_;
};

}  // namespace osp
}  // namespace openscreen

#endif  // OSP_IMPL_PRESENTATION_URL_AVAILABILITY_REQUESTER_H_