#ifndef ROSBRIDGEIMPL_H
#define ROSBRIDGEIMPL_H

#include "rosbridge.h"

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

#include <deque>
#include <memory>
#include <set>

struct ServiceCall;

class RosbridgeImpl : public QObject {
  Q_OBJECT
public:
  enum class STATE { STOPPED, STOPPING, STARTING, CONNECTED, TIMEOUT };

  RosbridgeImpl(const QUrl &url, QObject *parent = nullptr);
  ~RosbridgeImpl();

  STATE state(); // thread safe
public slots:
  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,
                      const Rosbridge::CallBack &callback);

  void unsubscribeTopic(const QString &topic);

  void unsubscribeAllTopics();

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

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

  void unadvertiseService(const QString &service);

  void unadvertiseAllServices();

signals:
  void stateChanged();

private slots:
  void _onConnected();
  void _onDisconnected();
  void _onError(QAbstractSocket::SocketError e);
  void _doAction();
  void _onTextMessageReceived(const QString &message);

private:
  void _processTopic(const QJsonObject &o);
  void _processServiceResponse(const QJsonObject &o);
  void _processServiceCall(const QJsonObject &o);
  void _setState(STATE newState);
  int _getMessageId();
  void _serviceResponse(const QString &service, const QString &id, bool result,
                        const QJsonObject &values);
  void _clearAllPendingServiceCalls();
  void _checkIfConnected();

  QWebSocket _webSocket;
  QUrl _url;
  std::set<QString> _advertisedTopics;
  std::map<QString, Rosbridge::CallBack> _subscribedTopics;
  std::map<QString, Rosbridge::CallBackWReturn> _advertisedServices;
  std::map<QString, std::deque<std::unique_ptr<ServiceCall>>>
      _pendingServiceCalls;
  std::atomic<STATE> _state;
};

#endif // ROSBRIDGEIMPL_H
