#ifndef ROSBRIDGE_H
#define ROSBRIDGE_H

#include <QJsonObject>
#include <QObject>
#include <QtWebSockets/QWebSocket>

#include <deque>
#include <functional>
#include <future>
#include <map>
#include <memory>

class RosbridgeImpl;
class QThread;

class Rosbridge : public QObject {
  Q_OBJECT
public:
  enum class STATE {
    STOPPED,
    STARTED,
    CONNECTED,
    TIMEOUT,
  };

  typedef std::function<void(const QJsonObject &)> CallBack;
  typedef std::function<QJsonObject(const QJsonObject &)> CallBackWReturn;

  Rosbridge(QUrl _url, QObject *parent = nullptr);
  ~Rosbridge();

  void start();
  void stop();
  STATE state();
  //!
  //! \brief wait Waits for connection.
  //! Waits for connection. This function can be called after start() to wait
  //! for the state() to change to STATE::CONNECTED.
  //!
  void waitConnected();

  // Topics.
  void advertiseTopic(const QString &topic, const QString &type);

  void publish(const QString &topic, const QJsonObject &msg);
  void unadvertiseTopic(const QString &topic);
  void unadvertiseAllTopics();

  void subscribeTopic(const QString &topic, Rosbridge::CallBack callback);

  std::future<bool> topicAvailable(const QString &topic);

  std::future<QJsonObject> getAdvertisedTopics();

  void unsubscribeTopic(const QString &topic);

  void unsubscribeAllTopics();

  void waitForTopic(const QString &topic);
  bool waitForTopic(const QString &topic,
                    const std::function<bool()> &condition);

  // Services.
  void advertiseService(const QString &service, const QString &type,
                        Rosbridge::CallBackWReturn callback);

  void unadvertiseService(const QString &service);

  void unadvertiseAllServices();

  void callService(const QString &service, Rosbridge::CallBack callback,
                   const QJsonObject &args = QJsonObject());

  std::future<bool> serviceAvailable(const QString &service);

  std::future<QJsonObject> getAdvertisedServices();

  void waitForService(const QString &service);
  bool waitForService(const QString &service,
                      const std::function<bool()> &condition);

signals:
  void stateChanged();

private:
signals:
  void _start();
  void _stop();
  // Topics
  void _advertiseTopic(const QString &topic, const QString &type);
  void _publish(const QString &topic, const QJsonObject &msg);
  void _unadvertiseTopic(const QString &topic);
  void _unadvertiseAllTopics();
  void _subscribeTopic(const QString &topic, Rosbridge::CallBack callback);
  void _unsubscribeTopic(const QString &topic);
  void _unsubscribeAllTopics();

  // Services.
  void _advertiseService(const QString &service, const QString &type,
                         Rosbridge::CallBackWReturn callback);
  void _callService(const QString &service, Rosbridge::CallBack callback,
                    const QJsonObject &args = QJsonObject());
  void _unadvertiseService(const QString &service);
  void _unadvertiseAllServices();
private slots:
  void _onImplStateChanged();

private:
  std::atomic<STATE> _state;
  RosbridgeImpl *_impl;
  QThread *_t;
  QUrl _url;
  std::atomic_bool _running;
};

#endif // ROSBRIDGE_H
