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

  Program:   ParaView
  Module:    vtkSMViewProxy.h

  Copyright (c) Kitware, Inc.
  All rights reserved.
  See Copyright.txt or http://www.paraview.org/HTML/Copyright.html for details.

     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.

=========================================================================*/
/**
 * @class vtkSMViewProxy
 * @brief a client side render window to drive rendering on the render service (RS).
 *
 * The client render window displays images that are rendered on the render
 * service. An image is generated from a 'payload' received from the RS.
 * Beside the paylod, there is additional information packed into `vtkPacket`
 * such as mimetype, video codec, packet-size and performance metrics.
 *
 * This proxy can stream the view's output. One may listen to this stream via an
 * observable that emits one chunk per frame. See GetViewOutputObservable().
 * The mimetype of those chunks can be `image/jpeg` or `image/rgb` for lossless transmission
 * or simply `application/octet-stream` which implies a video decoder is necessary to
 * display the stream. See `codec` field to infer the codec required to decode the stream.
 *
 * This view proxy is capable of decoding and efficient display of streams represented in
 * all above mime types except for `mimetype=application/octet-stream, codec=h264`.
 *
 */

#ifndef vtkSMViewProxy_h
#define vtkSMViewProxy_h

#include "vtkRemoteObjectProviderViews.h"
#include "vtkSMProxy.h"

#include "vtkCompressedVideoPacket.h"            // for output stream
#include "vtkNJsonFwd.h"                         // for function argument
#include "vtkPixelFormatTypes.h"                 // for argument
#include "vtkPythonObservableWrapper.h"          //for VTK_REMOTING_MAKE_PYTHON_OBSERVABLE
#include "vtkRawVideoFrame.h"                    // for video frame
#include "vtkRemoteViewResultItem.h"             // for result item
#include "vtkRemoteViewStatsItem.h"              // for stats item
#include "vtkRemotingServerManagerViewsModule.h" // for exports
#include "vtkSmartPointer.h"                     // for function argument
#include "vtkVideoCodecTypes.h"                  // for arg
#include "vtkVideoProcessingWorkUnitTypes.h"     // for function argument
#include <sstream>                               // for function argument

class vtkBoundingBox;
class vtkCamera;
class vtkImageData;
class vtkPacket;
class vtkRenderWindow;
class vtkRenderWindowInteractor;
class vtkSMSourceProxy;
class vtkSMViewProxyInternals;
class vtkVideoDecoder;

class VTKREMOTINGSERVERMANAGERVIEWS_EXPORT vtkSMViewProxy : public vtkSMProxy
{
public:
  static vtkSMViewProxy* New();
  vtkTypeMacro(vtkSMViewProxy, vtkSMProxy);
  void PrintSelf(ostream& os, vtkIndent indent) override;

  /**
   * Update the rendering pipeline.
   * Updates the visible presentations or DS and RS and calls StillRender to force a new rendering
   * Returns the status of the operation .
   * @todo the status is actually the status of the Update[on DS] -> UpdateForRendering[on RS]
   * chain. Not that useful.
   */
  rxcpp::observable<bool> Update();

  VTK_REMOTING_MAKE_PYTHON_OBSERVABLE(bool, Update());

  /** Sends a request for render to the RS using the current camera.  The
   * rendering pipeline should be up-to-date before calling this to ensure that
   * the latest data are displayed
   */
  void InteractiveRender();
  void StillRender();

  /**
   * These are the most recent visible prop bounds. These are gathered from the
   * rendering service in the most recent `Update` call after the updated
   * geometries have been delivered (and processed) by the rendering service.
   * Until that happens, these values may not be correct.
   */
  const vtkBoundingBox& GetVisiblePropBounds() const;

  /**
   * Convenience method to simply reset the camera using the current bounds
   * returned by `GetVisiblePropBounds`. If the bounds are not valid, then this
   * method has no effect.
   */
  void ResetCameraUsingVisiblePropBounds();

  /**
   * Provides access to the render window.
   */
  vtkRenderWindow* GetRenderWindow() const;

  /**
   * Provides access to the render window interactor.
   */
  vtkRenderWindowInteractor* GetRenderWindowInteractor() const;

  /**
   * Return the camera.
   */
  vtkCamera* GetCamera() const;

  /**
   * Called by vtkClientSessionViews when image subscription has new content.
   */
  void PostRenderingResult(const vtkPacket& packet);

  ///@{
  /**
   * Use this to indicate that the process should use
   * vtkGenericOpenGLRenderWindow rather than vtkRenderWindow when creating an
   * new render window.
   */
  static void SetUseGenericOpenGLRenderWindow(bool val);
  static bool GetUseGenericOpenGLRenderWindow();
  ///@}

  /**
   * Returns true if the view can display the data produced by the producer's
   * port. Internally calls GetRepresentationType() and returns true only if the
   * type is valid a representation proxy of that type can be created.
   */
  virtual bool CanDisplayData(vtkSMSourceProxy* producer, int outputPort);

  /**
   * Create a default representation for the given source proxy.
   * Returns a new proxy.
   * In version 4.1 and earlier, subclasses overrode this method. Since 4.2, the
   * preferred way is to simply override GetRepresentationType(). That
   * ensures that CreateDefaultRepresentation() and CanDisplayData() both
   * work as expected.
   */
  virtual vtkSMProxy* CreateDefaultRepresentation(vtkSMProxy*, int);

  /**
   * Returns the xml name of the representation proxy to create to show the data
   * produced in this view, if any. Default implementation checks if the
   * producer has any "Hints" that define the representation to create in this
   * view and if so, returns that.
   * Or if this->DefaultRepresentationName is set and its Input property
   * can accept the data produced, returns this->DefaultRepresentationName.
   * Subclasses should override this method.
   */
  virtual const char* GetRepresentationType(vtkSMSourceProxy* producer, int outputPort);

  /**
   * Finds the representation proxy showing the data produced by the provided
   * producer, if any. Note the representation may not necessarily be visible.
   */
  vtkSMProxy* FindRepresentation(vtkSMSourceProxy* producer, int outputPort);

  ///@{
  /**
   * Method used to hide other representations if the view has a
   * `<ShowOneRepresentationAtATime/>` hint.
   * This only affects other representations that have data inputs, not non-data
   * representations.
   *
   * @returns true if any representations were hidden by this call, otherwise
   *         returns false.
   */
  virtual bool HideOtherRepresentationsIfNeeded(vtkSMProxy* repr);
  static bool HideOtherRepresentationsIfNeeded(vtkSMViewProxy* self, vtkSMProxy* repr)
  {
    return self ? self->HideOtherRepresentationsIfNeeded(repr) : false;
  }
  ///@}

  ///@{
  /**
   * Certain views maintain properties (or other state) that should be updated
   * when visibility of representations is changed e.g. SpreadSheetView needs to
   * update the value of the "FieldAssociation" when a new data representation
   * is being shown in the view. Subclasses can override this method to perform
   * such updates to View properties. This is called explicitly by the
   * `vtkSMParaViewPipelineControllerWithRendering` after changing
   * representation visibility. Changes to representation visibility outside of
   * `vtkSMParaViewPipelineControllerWithRendering` will require calling this
   * method explicitly.
   *
   * Default implementation does not do anything.
   */
  virtual void RepresentationVisibilityChanged(vtkSMProxy* repr, bool new_visibility);
  static void RepresentationVisibilityChanged(
    vtkSMViewProxy* self, vtkSMProxy* repr, bool new_visibility)
  {
    if (self)
    {
      self->RepresentationVisibilityChanged(repr, new_visibility);
    }
  }
  ///@}

  /**
   * Helper method to locate a view to which the representation has been added.
   */
  static vtkSMViewProxy* FindView(vtkSMProxy* repr, const char* reggroup = "views");

  /**
   * Get an observable that streams this view's output.
   * While the output is designed to hold compressed video blobs, it could very well
   * be a raw image. See `vtkCompressedVideoPacket::GetMimeType()`.
   */
  rxcpp::observable<vtkSmartPointer<vtkCompressedVideoPacket>> GetViewOutputObservable() const;
  VTK_REMOTING_MAKE_PYTHON_OBSERVABLE(vtkSmartPointer<vtkObject>, GetViewOutputObservable() const)

  /**
   * Clear all subscriptions to the observable that generates view output stream.
   */
  void UnsubscribeOutputStreams();

  void RequestProgressiveRenderingPassIfNeeded();

protected:
  vtkSMViewProxy();
  ~vtkSMViewProxy() override;

  /**
   * Read attributes from an XML element.
   */
  int ReadXMLAttributes(vtkSMSessionProxyManager* pm, vtkPVXMLElement* element) override;

  void ProcessPiggybackedInformation(const vtkNJson& json) override;

  /**
   * Initialize the interactor.
   */
  virtual void InitializeInteractor(vtkRenderWindowInteractor* iren);

  /**
   * Re-build a `vtkCompressedVideoPacket` from the raw bytes in content.
   */
  vtkSmartPointer<vtkCompressedVideoPacket> ParseContent(
    const vtkRemoteViewResultItem& result, vtkSmartPointer<vtkUnsignedCharArray> content);

  /**
   * Initialzie a video decoder with `codecType` codec.
   */
  void InitializeVideoDecoder(VTKVideoCodecType codecType, const std::string& mime);

  /**
   * Pull out stats from the result and display them as text annotations on the render window.
   */
  void ExtractAnnotations(const vtkRemoteViewResultItem& result);

  /**
   * Get image from a payload with mime=image/rgb24 or image/rgba32.
   */
  vtkSmartPointer<vtkRawVideoFrame> LoadLosslessImage(
    vtkSmartPointer<vtkCompressedVideoPacket> payload);

  /**
   * Get image from a payload with mime=image/jpeg
   */
  vtkSmartPointer<vtkRawVideoFrame> LoadJpegImage(
    vtkSmartPointer<vtkCompressedVideoPacket> payload);

  /**
   * Get image from video decoder/demuxer based on mimetype.
   */
  vtkSmartPointer<vtkRawVideoFrame> LoadVideoFrame(
    vtkSmartPointer<vtkCompressedVideoPacket> payload);

  /**
   * Overlay an OpenGL frame on the render window.
   */
  void DrawOverlay(vtkSmartPointer<vtkRawVideoFrame> frame);

  ///{@
  /**
   * Upon receiving a render event from the interactor, the camera state is pushed
   * to the render service with rpc mechanism.
   */
  void SetupInteractor();
  void HandleInteractorEvents(vtkObject*, unsigned long eventId, void*);
  ///@}

  /**
   * Upon receiving an end event from the primary renderer (not the one for annotations),
   * either the video decoder decompresses the payload into a raw frame or the payload is directly
   * drawn on top of our render window.
   */
  void HandleRendererEndEvent(vtkObject*, unsigned long eventId, void*);

  /**
   * Flush the render service packets to our display frame buffer.
   */
  void FlushDisplayFramebuffer();

  ///{@
  /**
   * Convenient methods to communicate render requests with camera/size/renderpass
   * information to the render service.
   */
  void RequestRender();
  void RequestProgressiveRenderingPass();
  void UpdateCameraIfNeeded(bool force = false, bool progressiveRendering = false);
  void UpdateRayTracingState(const vtkRemoteViewStatsItem& stats);
  void UpdateSize();
  bool GetContinueRendering() const;
  ///@}

private:
  vtkSMViewProxy(const vtkSMViewProxy&) = delete;
  void operator=(const vtkSMViewProxy&) = delete;

  std::unique_ptr<vtkSMViewProxyInternals> Internals;
  static bool UseGenericOpenGLRenderWindow;
};

#endif
