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

  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. The image is generated from a 'payload' received from the RS.
 * Beside the paylod, there is additional information packed into `vtkPacket`
 * related to 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 chunks combined over time, make a webm file (mime=video/webm; codecs=vp09.00.10.08)
 * or they may be individual bitmap images (mime=image/bmp) in case of
 * lossless transmission.
 *
 * By default, the render view does not use video encoding.
 * Software encoding with VP9 is the best supported codec right now
 * and the related view output stream is in webm chunks.
 * Software encoding with AV1, h.264 and h.265 are also supported
 * but the view output is the raw bitstream (mime=application/octet-stream)
 *
 * There are two paths that can be taken to display the RS output.
 * 1. Lossless
 *   If the RS transmits a lossless image,
 *   a client can display that image directly without decoding it.
 * 2. Video codecs
 *   On the other hand, if a client receives a compressed video packet,
 *   the video packet is decompressed with vtkVideoDecoder and that decoded
 *   result is finally drawn to the screen.
 *
 * In first path, the raw image is in rgba pixel format, whereas
 * the output of a video decoder is almost never in such format.
 *
 * Just before rendering, the lossless image or the decoded output is
 * sent to the GPU with `vtkOpenGLVideoFrame`, which can render planar
 * YCbCr pixel formats like - IYUV and NV12 as well as packed pixel formats
 * like rgba.
 *
 * @warning: Some H.264 and H.265 encoders require the view dimensions to be divisible by 2.
 * @warning: Intel VAAPI encoders lag by 1-frame.
 */

#ifndef vtkSMViewProxy_h
#define vtkSMViewProxy_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 "vtkRemotingServerManagerViewsModule.h" // for exports
#include "vtkSmartPointer.h"                     // for function argument
#include "vtkVideoProcessingWorkUnitTypes.h"     // for function argument

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

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 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 vtkProcessInitializer when a rendering result is available.
   */
  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);

  /**
   * Detects when the render service changes to another video codec.
   * Necessary to flush, shutdown and set the codec type.
   */
  void RefreshVideoDecoder(const vtkPacket& packet);

  /**
   * The render service puts extra information concerning
   * render passes, fps and various other performance metrics
   * in the packet. Need to parse those into vtkTextRepresentation
   * that annotates the view in upper left corner.
   */
  void ParseMetadata(const vtkPacket& packet);

  /**
   * The payload is made up of flat unsigned char data.
   * It may correspond to a compressed video packet or the
   * r,g,b,a pixels of a raw image.
   */
  void ParsePayload(const vtkPacket& packet);

  /**
   * Streams the payload frame by frame into a multimedia
   * container (*.webm,bmp) with a set mimetype.
   * An application that can display/playback the mimetype should
   * be able to decode and stream images.
   */
  void ContainerizePackage();

  /**
   * Populate our OpenGL raw frame with output of vtkVideoDecoder.
   */
  void ParseDecoderOutput(const VTKVideoDecoderResultType& output);

  /**
   * Prepares our internal raw frame that will be drawn on
   * top of our render window (if we have one).
   */
  void RefreshOpenGLRawFrame(int width, int height, VTKPixelFormatType pixelFormat);

  /**
   * Annotates the view with interesting render+encode performance metrics.
   */
  void AddMetadataAsAnnotations(const vtkNJson& metadata);

  /**
   * Draws a raw frame on top of the render window.
   */
  void DrawOverlay();

  ///{@
  /**
   * 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*);

  ///{@
  /**
   * 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 vtkNJson& metadata);
  void UpdateSize();
  bool GetContinueRendering() const;
  ///@}

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

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

#endif
