#pragma once

#include "Messages/BinaryMsg.h"
#include "Messages/InvestigateMsg.h"
#include "Messages/ModelUrlMsg.h"
#include "Messages/NameMsg.h"
#include "Messages/SiteMsg.h"
#include "Messages/ParticipantMsg.h"
#include "Messages/PoseMsg.h"
#include "Messages/ThingMsg.h"
#include "RemoteParticipant.h"

#include <list>

#if defined(_WIN32) || defined(_WIN64)
#include <winsock2.h>
#elif defined(__unix__) || defined(__APPLE__)
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>
#elif defined(ARDUINO)
#include <WiFiUdp.h>
#endif

namespace RoboidControl {

/// @brief A participant is device which can communicate with other participants
class Participant : public RemoteParticipant {
 public:
  char buffer[1024];
  long publishInterval = 3000;  // 3 seconds

  const char* name = "Participant";

  int localPort = 0;

#if defined(ARDUINO)
  const char* remoteIpAddress = nullptr;
  unsigned short remotePort = 0;
  char* broadcastIpAddress = nullptr;

  WiFiUDP udp;
#else

#if defined(_WIN32) || defined(_WIN64)
  SOCKET sock;
#elif defined(__unix__) || defined(__APPLE__)
  int sock;
#endif
  sockaddr_in remote_addr;
  sockaddr_in server_addr;
  sockaddr_in broadcast_addr;

#endif

  Participant(int port = 7681);
  Participant(const char* ipAddress, int port);

  void begin();
  bool connected = false;

  virtual void Update(unsigned long currentTimeMs = 0);

  void SendThingInfo(RemoteParticipant* remoteParticipant, Thing* thing);
  void PublishThingInfo(Thing* thing);

  bool Send(RemoteParticipant* remoteParticipant, IMessage* msg);
  bool Publish(IMessage* msg);

  void ReceiveData(unsigned char bufferSize, RemoteParticipant* remoteParticipant);

 protected:
  std::list<RemoteParticipant*> senders;

  unsigned long nextPublishMe = 0;

  void SetupUDP(int localPort, const char* remoteIpAddress, int remotePort);

  RemoteParticipant* GetParticipant(const char* ipAddress, int port);
  RemoteParticipant* AddParticipant(const char* ipAddress, int port);

  void ReceiveUDP();

  virtual void Process(RemoteParticipant* sender, ParticipantMsg* msg);
  virtual void Process(RemoteParticipant* sender, SiteMsg* msg);
  virtual void Process(RemoteParticipant* sender, InvestigateMsg* msg);
  virtual void Process(RemoteParticipant* sender, ThingMsg* msg);
  virtual void Process(RemoteParticipant* sender, NameMsg* msg);
  virtual void Process(RemoteParticipant* sender, PoseMsg* msg);
  virtual void Process(RemoteParticipant* sender, BinaryMsg* msg);
};

}  // namespace RoboidControl