/*=========================================================================

  Copyright (c) Kitware, Inc.
  All rights reserved.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notice for more information.

=========================================================================*/

// This class provides collaboration support for VR
// This class does not depend on other mineview classes
// mvLobbyClient can be used for cases where mineview
// specific code is required.

#include <zmq.hpp> // https://github.com/zeromq/cppzmq
#include <string>
#include <map>
#include <functional>
#include <vtkSmartPointer.h>
#include "vtksys/CommandLineArguments.hxx"

class vtkCallbackCommand;
class vtkEventDataDevice3D;
enum class vtkEventDataDevice;
class vtkObject;
class vtkOpenGLAvatar;
class vtkOpenGLRenderer;
class vtkOpenGLRenderWindow;

const double RAY_LENGTH = 200.0;

class mvCollaborationClient
{
public:
  mvCollaborationClient();
  ~mvCollaborationClient();

  // send messages to the collaboration server
  void SendMessage(std::string const &msgType);
  void SendMessage(std::string const &msgType, int index);
  template <typename T>
  void SendMessage(std::string const &msgType, std::vector<T> const &val);
  void SendMessage(std::string const &msgType, std::string const &val);
  void SendMessage(std::string const &msgType, std::vector<std::string> &vals);
  void SendMessage(std::string const &msgType, int index, double pos[3], double dir[3]);

  // call during the render loop to handle collaboration messages
  virtual void Render();

  // required call, true on success
  virtual bool Initialize(vtkOpenGLRenderer *);

  // close the connection
  void Disconnect();

  // set the values for the collaboration connection
  // Can be done through Set* methods or by passing in
  // the command line arguments via AddArgument
  virtual void AddArguments(vtksys::CommandLineArguments &arguments);
  void SetCollabHost(std::string const &val) {
    this->CollabHost = val;
  }
  void SetCollabSession(std::string const &val) {
    this->CollabSession = val;
  }
  void SetCollabName(std::string const &val) {
    this->CollabName = val;
  }
  void SetCollabPort(int val) {
    this->CollabPort = val;
  }

  // to receive log/warning/error output
  void SetLogCallback(std::function<void(std::string const &data, void *cd)> cb, void *clientData)
  {
    this->Callback = cb;
    this->CallbackClientData = clientData;
  }

  // to override the default method of getting avatar scales
  void SetScaleCallback(std::function<double(void *cd)> cb, void *clientData)
  {
    this->ScaleCallback = cb;
    this->ScaleCallbackClientData = clientData;
  }

  vtkOpenGLRenderer *GetRenderer() {
    return this->Renderer;
  }

  bool GetConnected() {
    return this->Connected;
  }

protected:
  // provided values
  std::string CollabID;
  std::string CollabHost;
  std::string CollabSession;
  std::string CollabName;
  int CollabPort;

  std::function<void(std::string const &data, void *cd)> Callback;
  void *CallbackClientData;

  std::function<double(void *cd)> ScaleCallback;
  void *ScaleCallbackClientData;

  bool DisplayOwnAvatar;
  bool PublishAvailable;
  void HandleCollabMessage();
  bool AvatarIdle(std::string id);
  void EraseIdleAvatars();
  zmq::context_t Context;
  zmq::socket_t Requester;
  zmq::socket_t Subscriber;
  zmq::pollitem_t CollabPollItems[2];
  double NeedHeartbeat;
  double NeedReply;
  int RetryCount;

  bool Connected;

  // get existing avatar, or create new one, if needed, and return it.
  vtkSmartPointer<vtkOpenGLAvatar> GetAvatar(std::string id);

  void SendDevicePoseMessage(vtkEventDataDevice device,
    double pos[4], double orient[3]);

  virtual void HandleBroadcastMessage(
    std::string const &otherID, std::string const &type);

  vtkCallbackCommand* EventCommand;
  static void EventCallback(
    vtkObject* object, unsigned long event, void* clientdata, void* calldata);
  long MoveObserver;

  vtkOpenGLRenderer *Renderer;
  vtkOpenGLRenderWindow *RenderWindow;

  // dynamic set of avatars, keyed on IDs sent with updates.
  std::map<std::string, vtkSmartPointer<vtkOpenGLAvatar>> Avatars;
  std::map<std::string, double[3]> AvatarUpdateTime;
};
