// SPDX-FileCopyrightText: Copyright (c) Kitware Inc.
// SPDX-License-Identifier: BSD-3-Clause
/**
 * @class   vtkProcessModule
 * @brief   process initialization and management core for
 * ParaView processes.
 * vtkProcessModule is the process initialization and session management core
 * for ParaView processes.
 */

#ifndef vtkProcessModule_h
#define vtkProcessModule_h

#include "vtkNew.h" // needed for vtkNew
#include "vtkObject.h"
#include "vtkRemotingCoreModule.h" //needed for exports
#include "vtkSmartPointer.h"       // needed for vtkSmartPointer.

#include <string> // for std::string

class vtkInformation;
class vtkMultiProcessController;
class vtkNetworkAccessManager;
class vtkProcessModuleInternals;
class vtkSession;
class vtkSessionIterator;
class vtkThreadedCallbackQueue;

class VTKREMOTINGCORE_EXPORT vtkProcessModule : public vtkObject
{
public:
  static vtkProcessModule* New();
  vtkTypeMacro(vtkProcessModule, vtkObject);
  void PrintSelf(ostream& os, vtkIndent indent) override;

  enum ProcessTypes
  {
    PROCESS_CLIENT,        /* Capable of connecting to remote server or self.
                      Cannot run in Parallel */
    PROCESS_SERVER,        /* data-server+render-server */
    PROCESS_DATA_SERVER,   /* data-server */
    PROCESS_RENDER_SERVER, /* render-server */
    PROCESS_BATCH,         /* Capable of running in parallel with root node acting as client.
                      Cannot connect to remote server */
    PROCESS_INVALID = -1,
  };

  /**
   * These flags are used to specify destination servers for the
   * SendStream function.
   * FIXME: These flags should simply move to PVSession. They don't make sense
   * in non-PVSessions since the processes may have different roles in that
   * case.
   */
  enum ServerFlags
  {
    DATA_SERVER = 0x01,
    DATA_SERVER_ROOT = 0x02,
    RENDER_SERVER = 0x04,
    RENDER_SERVER_ROOT = 0x08,
    SERVERS = DATA_SERVER | RENDER_SERVER,
    CLIENT = 0x10,
    CLIENT_AND_SERVERS = DATA_SERVER | CLIENT | RENDER_SERVER
  };

  static ProcessTypes GetProcessType();
  static unsigned int GetProcessTypeAsInt()
  {
    return static_cast<unsigned int>(vtkProcessModule::GetProcessType());
  }

  /**
   * This method has been added to support migration from one type to another
   * but this method call if NOT RECOMMENDED.
   * -> We use it to handle the Animation saving at disconnection time on the
   * server side. We create a new session and migrate the sever process to
   * a batch process.
   */
  void UpdateProcessType(ProcessTypes newType, bool dontKnowWhatImDoing = true);

  //********** PROCESS INITIALIZATION/CLEANUP API *****************************

  /**
   * Initializes the process and the ProcessModule.
   * The function is to initialize MPI if applicable
   * for the process and setup some environment e.g. DISPLAY.
   * Initializes the ProcessModule.
   * for the process and setup some environment e.g. DISPLAY.
   */
  static bool Initialize(ProcessTypes type, int& argc, char**& argv);

  /**
   * Finalizes and cleans up the process.
   */
  static bool Finalize();

  //********** SESSION MANAGEMENT API *****************************

  /**
   * Registers a new session. A new ID is assigned for the session and
   * that ID is returned. The ID can be used in future to access this
   * session.
   * Fires vtkCommand::ConnectionCreatedEvent every time a session is
   * registered. The event-data for this event is a vtkIdType whose value is the
   * session id.
   */
  vtkIdType RegisterSession(vtkSession*);

  ///@{
  /**
   * Unregister a session given its ID. This is the same ID that is returned
   * when the session was registered. Returns true is the session was
   * unregistered. Unregistering a session implies that the ProcessModule
   * will no longer monitor communication on the sockets, if any, in the
   * session.
   * Fires vtkCommand::ConnectionClosedEvent every time a session is
   * unregistered. The event-data for this event is a vtkIdType whose value is the
   * session id.
   */
  bool UnRegisterSession(vtkIdType sessionID);
  bool UnRegisterSession(vtkSession* session);
  ///@}

  ///@{
  /**
   * RegisterSession and UnRegisterSession fire events with SessionID in
   * calldata. To provide access to that in Python, we have this method. The
   * value is valid only in vtkCommand::ConnectionCreatedEvent and
   * vtkCommand::ConnectionClosedEvent callbacks and is set to 0 at other times.
   */
  vtkGetMacro(EventCallDataSessionId, vtkIdType);
  ///@}

  /**
   * Returns the session associated with a given ID.
   */
  vtkSession* GetSession(vtkIdType);

  /**
   * Returns the session id for the session, if any. Return 0 is the session has
   * not been registered with the process module.
   */
  vtkIdType GetSessionID(vtkSession*);

  /**
   * Returns a new session iterator that can be used to iterate over the
   * registered sessions.
   */
  vtkSessionIterator* NewSessionIterator();

  /**
   * Whenever any session is processing some message, it typically marks itself
   * active with the process module. The active session can be accessed using
   * this method.
   */
  vtkSession* GetActiveSession();

  /**
   * This is a convenience method that either returns the active session, if
   * present, otherwise the first session. Don't use this for new API. This is
   * provided for some old api.
   */
  vtkSession* GetSession();

  ///@{
  /**
   * Return true, if multiple sessions can be used simultanuously.
   * We set the default to be FALSE.
   */
  vtkGetMacro(MultipleSessionsSupport, bool);
  vtkSetMacro(MultipleSessionsSupport, bool);
  vtkBooleanMacro(MultipleSessionsSupport, bool);
  ///@}

  //********** ACCESSORS FOR VARIOUS HELPERS *****************************

  /**
   * Provides access to the global ProcessModule. This method can only be called
   * after Initialize().
   */
  static vtkProcessModule* GetProcessModule();

  //********** ACCESSORS FOR VARIOUS HELPERS *****************************

  ///@{
  /**
   * Get/Set the network access manager. vtkNetworkAccessManager encapsulates
   * the setup of interprocess communication channels. By default a
   * vtkTCPNetworkAccessManager is setup. If you want to change the network
   * access manager, it should be done only when no sessions are present.
   * Ideally, you want to do that during the initialization of the process
   * itself.
   */
  vtkGetObjectMacro(NetworkAccessManager, vtkNetworkAccessManager);
  void SetNetworkAccessManager(vtkNetworkAccessManager*);
  ///@}

  /**
   * Provides access to the global MPI controller, if any. Same can be obtained
   * using vtkMultiProcessController::GetGlobalController();
   */
  vtkMultiProcessController* GetGlobalController();

  /**
   * Returns the number of processes in this process group.
   */
  int GetNumberOfLocalPartitions();

  /**
   * Returns the local process id.
   */
  int GetPartitionId();

  /**
   * Return whether MPI is initialized in this process group.
   */
  bool IsMPIInitialized();

  ///@{
  /**
   * Set/Get whether to report errors from the Interpreter.
   */
  vtkGetMacro(ReportInterpreterErrors, bool);
  vtkSetMacro(ReportInterpreterErrors, bool);
  vtkBooleanMacro(ReportInterpreterErrors, bool);
  ///@}

  ///@{
  static bool GetSymmetricMPIMode();
  ///@}

  /**
   * The full path to the current executable that is running (or empty if unknown).
   */
  const std::string& GetProgramPath() const { return this->ProgramPath; }
  /**
   * The directory containing the current executable (or empty if unknown).
   */
  const std::string& GetSelfDir() const { return this->SelfDir; }

  ///@{
  /**
   * This is temporary approach to control the number of ghost-levels to request
   * by default for data pipelines. Currently, change in ghost level
   * request causes the pipeline to re-execute which can be expensive. In an
   * ideal world, additional ghost levels can be automatically provided by
   * data-exchange between ranks. Until we do that, this is only mechanism
   * available to override the number of ghost levels requested by default.
   *
   * The default is 0 for structured pipelines, and 1 for unstructured
   * pipelines. When not running in parallel, however, these ghost-level
   * requests don't make sense and hence are generally ignored.
   *
   * Note, this is expected to change in the future, so use this with
   * caution.
   */
  static void SetDefaultMinimumGhostLevelsToRequestForStructuredPipelines(int);
  static int GetDefaultMinimumGhostLevelsToRequestForStructuredPipelines();
  static void SetDefaultMinimumGhostLevelsToRequestForUnstructuredPipelines(int);
  static int GetDefaultMinimumGhostLevelsToRequestForUnstructuredPipelines();
  ///@}

  /**
   * This returns number of ghost level to request based on characteristics of
   * the pipelines.
   */
  static int GetNumberOfGhostLevelsToRequest(vtkInformation* outInfo);

  /**
   * Returns a callback queue in which one can defer tasks that can run asynchronously.
   */
  vtkThreadedCallbackQueue* GetCallbackQueue();

protected:
  vtkProcessModule();
  ~vtkProcessModule() override;

  ///@{
  /**
   * Push/Pop the active session.
   */
  void PushActiveSession(vtkSession*);
  void PopActiveSession(vtkSession*);
  ///@}

  /**
   * Marking vtkSession as friend since it needs access to
   * PushActiveSession/PopActiveSession.
   */
  friend class vtkSession;

  vtkNetworkAccessManager* NetworkAccessManager;

  /**
   * Used to keep track of maximum session used. Only used to ensure that no
   * session id is ever repeated.
   */
  vtkIdType MaxSessionId;

  /**
   * Sets the executable path of the process so that ParaView can, e.g., set up
   * paths for Python properly.
   */
  void SetExecutablePath(const std::string& path);

  vtkProcessModuleInternals* Internals;

  // vtkSessionIterator needs access to vtkInternals to be able to iterate over
  // the sessions efficiently.
  friend class vtkSessionIterator;

  bool ReportInterpreterErrors;

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

  void DetermineExecutablePath(int argc, char** argv);

  // Helper to initialize Python environment. This doesn't initialize Python
  // but simply sets up the environment so when Python is initialized, it can
  // find ParaView modules. This does nothing if not build with Python support.
  bool InitializePythonEnvironment();

  static ProcessTypes ProcessType;

  // Set to true in Initialize if Finalize() should cleanup MPI.
  static bool FinalizeMPI;

  // Set to true in InitializePythonEnvironment() if Finalize() should cleanup
  // Python.
  static bool FinalizePython;

  static vtkSmartPointer<vtkProcessModule> Singleton;
  static vtkSmartPointer<vtkMultiProcessController> GlobalController;

  bool MultipleSessionsSupport;

  vtkIdType EventCallDataSessionId;

  std::string ProgramPath;
  std::string SelfDir;

  static int DefaultMinimumGhostLevelsToRequestForStructuredPipelines;
  static int DefaultMinimumGhostLevelsToRequestForUnstructuredPipelines;
};

#endif // vtkProcessModule_h
