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

  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.

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

#include <string>
#include <vector>
#include <map>
#include <atomic>
#include <vtkNew.h>
#include "vtkEventData.h" // for enums

class mvAudioHandler;
class mvLobbyClient;
class mvView;

class vtkActor;
class vtkCallbackCommand;
class vtkEventDataDevice3D;
enum class vtkEventDataDevice;
class vtkMultiThreader;
class vtkMutexLock;
class vtkObject;

class vtkOpenGLCamera;
class vtkOpenVRCameraPose;
class vtkOpenVROverlay;
class vtkInteractorStyle;
class vtkOpenGLRenderer;
class vtkOpenGLRenderWindow;
class vtkRenderWindowInteractor;

class mvLobby
{
public:
  mvLobby(int argc, char *argv[]);
  ~mvLobby();

  // start the interactor
  void Start();

  // Loads the heavyweight data for a view
  // and makes it active
  void LoadView(size_t viewNum, bool fromCollab = false);

  // views call to return
  void ReturnFromView(bool fromCollab = false);

  // lock/unlock the rendering thread mutex
  void RenderLock();
  void RenderUnlock();

  // used by the worker thread
  void AsynchronousLoadView(void *data);

  vtkOpenGLRenderer *GetRenderer();

  vtkRenderWindowInteractor *GetInteractor() {
    return this->Interactor;
  }

  vtkOpenGLRenderWindow *GetRenderWindow() {
    return this->RenderWindow;
  }

  vtkOpenVROverlay *GetDashboardOverlay() {
    return this->Overlay;
  }

  bool GetDisplayMap() { return this->DisplayMap;  }

  // Return the interactor style
  vtkInteractorStyle *GetStyle() { return this->Style; }

  // generate level of detail files for quicker loading.
  // Optionally for a specific view
  void GenerateLODs(std::string const &viewName);

  // get the audio handler instance
  mvAudioHandler *GetAudioHandler() {
    return this->AudioHandler;
  }

  // get the audio handler instance
  mvLobbyClient *GetCollaborationClient() {
    return this->Client;
  }

  // get the current physical scale
  double GetPhysicalScale();
  void SetPhysicalScale(double val);

  // get the current physical translation
  double *GetPhysicalTranslation();
  void SetPhysicalTranslation(double * val) {
    this->SetPhysicalTranslation(val[0], val[1], val[2]);
  }
  void SetPhysicalTranslation(double, double, double);

  // get the current physical translation
  double *GetPhysicalViewDirection();
  void SetPhysicalViewDirection(double * val) {
    this->SetPhysicalViewDirection(val[0], val[1], val[2]);
  }
  void SetPhysicalViewDirection(double, double, double);

  // physcial view up
  double *GetPhysicalViewUp();
  void SetPhysicalViewUp(double * val) {
    this->SetPhysicalViewUp(val[0], val[1], val[2]);
  }
  void SetPhysicalViewUp(double, double, double);

  vtkOpenVRCameraPose *GetPhysicalPose() {
    return this->PhysicalPose;
  }

  // Valid states for the application along with their transitions
  //
  // Unknown -> Lobby via Initialize()
  // Unknown -> GeneratingLOD via constructor
  // Lobby -> Unknown (exiting)
  // Lobby -> LoadingView via LoadView()
  // LoadingView -> Lobby via CancelLoadView()
  // LoadingView -> AsynchronousLoadComplete
  // CompletedAsynchronousLoad -> InView via FinishLoadView()
  // InView -> Lobby via ReturnFromView()
  enum States {
    Unknown,
    InLobby,
    LoadingView,
    CompletedAsynchronousLoad,
    InView,
    GeneratingLOD
  };

protected:
  mvLobby(const mvLobby&) = delete;
  void operator=(const mvLobby&) = delete;

  // called from mvLobbyClient
  friend mvLobbyClient;
  void ServerViewChanged(std::string const &viewName);
  void ServerTourStopChanged(int viewIndex,
    double translation[3], double direction[3]);

  // called once the View->Load has finished
  void FinishLoadView();

  // called to cancel a view that is in the process
  // of being loaded
  void CancelLoadView();

  int CurrentView;

  // see the enum of state up above
  std::atomic<int> State;

  // returns false on failure, true on success
  bool Initialize(int argc, char *argv[]);

  void LoadMap();

  // loads the lightweight data for the views
  void InitializeViews();

  int GetViewNumberFromEvent(vtkEventDataDevice3D *edd);

  mvAudioHandler *AudioHandler;

  mvLobbyClient *Client;

  bool FindViewsDirectory(char *argv0);
  std::string ViewsDirectory;
  std::vector<mvView *> Views;
  vtkNew<vtkActor> MapActor;

  double PosterCenter[2];
  double PosterRadius;
  double PosterHeight;
  double ArcStart; // in degrees
  double ArcContentSize; // in degrees
  double ArcGap; // in degreees

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

  vtkNew<vtkMutexLock> RenderMutex;
  vtkNew<vtkMultiThreader> Threader;
  int AsynchronousLoadViewThreadId;

  bool DisplayMap;
  std::vector<int> BackgroundColor;
  std::string LobbyAudio;
  bool Desktop;

  vtkOpenGLCamera *Camera;
  vtkOpenGLRenderer *Renderer;
  vtkOpenGLRenderWindow* RenderWindow;
  vtkRenderWindowInteractor *Interactor;
  vtkOpenVROverlay *Overlay;
  vtkInteractorStyle *Style;

  vtkOpenVRCameraPose *PhysicalPose;

  // volume level for audio output, defaults to -1.0 indicating
  // to not change the current volume
  double Volume;
};
