/*=========================================================================
 *
 *  Copyright NumFOCUS
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *         https://www.apache.org/licenses/LICENSE-2.0.txt
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 *=========================================================================*/
/*=========================================================================
 *
 *  Portions of this file are subject to the VTK Toolkit Version 3 copyright.
 *
 *  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
 *
 *  For complete copyright, license and disclaimer of warranty information
 *  please refer to the NOTICE file at the top of the ITK source tree.
 *
 *=========================================================================*/
#ifndef itkObjectFactoryBase_h
#define itkObjectFactoryBase_h

#include "itkCreateObjectFunction.h"
#include "itkSingletonMacro.h"
#include "itkCommonEnums.h"
#include <list>
#include <memory> // For unique_ptr.
#include <vector>

namespace itk
{
/** \class ObjectFactoryBase
 * \brief Create instances of classes using an object factory.
 *
 * ObjectFactoryBase is used to create itk objects. The base class
 * ObjectFactoryBase contains a static method CreateInstance() that is
 * used to create itk objects from the list of registered ObjectFactoryBase
 * sub-classes.  The first time CreateInstance() is called, all dll's or
 * shared libraries in the environment variable ITK_AUTOLOAD_PATH are loaded
 * into the current process.  The C function itkLoad is called on each dll.
 * itkLoad should return an instance of the factory sub-class implemented in
 * the shared library. ITK_AUTOLOAD_PATH is an environment variable
 * containing a colon separated (semi-colon on win32) list of paths.
 *
 * This can be use to override the creation of any object in ITK.
 *
 * \ingroup ITKSystemObjects
 * \ingroup ITKCommon
 */

class ITKCommon_EXPORT ObjectFactoryBase : public Object
{
public:
  ITK_DISALLOW_COPY_AND_MOVE(ObjectFactoryBase);

  /** Standard class type aliases. */
  using Self = ObjectFactoryBase;
  using Superclass = Object;
  using Pointer = SmartPointer<Self>;
  using ConstPointer = SmartPointer<const Self>;

  /** \see LightObject::GetNameOfClass() */
  itkOverrideGetNameOfClassMacro(ObjectFactoryBase);

  /** Create and return an instance of the named itk object.
   * Each loaded ObjectFactoryBase will be asked in the order
   * the factory was in the ITK_AUTOLOAD_PATH.  After the
   * first factory returns the object no other factories are asked.
   *
   * \note The object returned by `CreateInstance` will have a reference count of 2, instead of 1. So in order to avoid
   * memory leaks, one may need to call `object->UnRegister()`.
   */
  static LightObject::Pointer
  CreateInstance(const char * itkclassname);

  /** Create and return all possible instances of the named itk object.
   * Each loaded ObjectFactoryBase will be asked in the order
   * the factory was in the ITK_AUTOLOAD_PATH.  All created objects
   * will be returned in the list. */
  static std::list<LightObject::Pointer>
  CreateAllInstance(const char * itkclassname);

  /** Re-check the ITK_AUTOLOAD_PATH for new factory libraries.
   * This calls UnRegisterAll before re-loading. */
  static void
  ReHash();

  /** Register a factory so it can be used to create itk objects.
   *  This method is intended to be called only for built-in default
   *  factories, not for loadable factories.
   *
   * Factories that are registered with this method will be
   * registered after ReHash.
   */
  static void
  RegisterFactoryInternal(ObjectFactoryBase *);

  using InsertionPositionEnum = ObjectFactoryEnums::InsertionPosition;
#if !defined(ITK_LEGACY_REMOVE)
  // We need to expose the enum values at the class level
  // for backwards compatibility
  static constexpr InsertionPositionEnum INSERT_AT_FRONT = InsertionPositionEnum::INSERT_AT_FRONT;
  static constexpr InsertionPositionEnum INSERT_AT_BACK = InsertionPositionEnum::INSERT_AT_BACK;
  static constexpr InsertionPositionEnum INSERT_AT_POSITION = InsertionPositionEnum::INSERT_AT_POSITION;
#endif

  /** Register a factory so it can be used to create itk objects.
   *
   * When InsertionPositionEnum::INSERT_AT_POSITION is selected, a third argument must be provided
   * with the actual integer number of the intended position. The position
   * number must be in the range [0, numberOfRegisteredFactories-1].
   *
   * Usage should be any of the following:
   *
   * itk::ObjectFactoryBase::RegisterFactory( newFactory1 ); // at back
   * itk::ObjectFactoryBase::RegisterFactory( newFactory2, InsertionPositionEnum::INSERT_AT_FRONT );
   * itk::ObjectFactoryBase::RegisterFactory( newFactory3, InsertionPositionEnum::INSERT_AT_BACK );
   * itk::ObjectFactoryBase::RegisterFactory( newFactory4, InsertionPositionEnum::INSERT_AT_POSITION, 5 );
   *
   * If the position value is out of range, an exception will be
   * thrown.
   * Returns true if the factory was successfully registered.
   * Returns false if factory is already loaded.
   */
  static bool
  RegisterFactory(ObjectFactoryBase *,
                  InsertionPositionEnum where = InsertionPositionEnum::INSERT_AT_BACK,
                  size_t                position = 0);

  /** Remove a factory from the list of registered factories. */
  static void
  UnRegisterFactory(ObjectFactoryBase *);

  /** Unregister all factories. */
  static void
  UnRegisterAllFactories();

  /** Return the list of all registered factories.  This is NOT a copy,
   * do not remove items from this list! */
  static std::list<ObjectFactoryBase *>
  GetRegisteredFactories();

  /** All sub-classes of ObjectFactoryBase should must return the version of
   * ITK they were built with.  This should be implemented with the macro
   * ITK_SOURCE_VERSION and NOT a call to Version::GetITKSourceVersion.
   * As the version needs to be compiled into the file as a string constant.
   * This is critical to determine possible incompatible dynamic factory loads. */
  virtual const char *
  GetITKSourceVersion() const = 0;

  /** Require the ITK version of this application to exactly match the ITK
   * version used to compile a dynamic library. When this is set to true, if the
   * versions do not match, an exception will be thrown. When this is false, and
   * the versions do not match, only a warning message is printed out in the
   * console, and the factory is still registered. */
  static void
  SetStrictVersionChecking(bool);
  static void
  StrictVersionCheckingOn();
  static void
  StrictVersionCheckingOff();
  static bool
  GetStrictVersionChecking();

  /** Return a descriptive string describing the factory. */
  virtual const char *
  GetDescription() const = 0;

  /** Return a list of classes that this factory overrides. */
  virtual std::list<std::string>
  GetClassOverrideNames();

  /** Return a list of the names of classes that override classes. */
  virtual std::list<std::string>
  GetClassOverrideWithNames();

  /** Return a list of descriptions for class overrides. */
  virtual std::list<std::string>
  GetClassOverrideDescriptions();

  /** Return a list of enable flags. */
  virtual std::list<bool>
  GetEnableFlags();

  /** Set the Enable flag for the specific override of className. */
  virtual void
  SetEnableFlag(bool flag, const char * className, const char * subclassName);

  /** Get the Enable flag for the specific override of className. */
  virtual bool
  GetEnableFlag(const char * className, const char * subclassName);

  /** Set all enable flags for the given class to 0.  This will
   * mean that the factory will stop producing class with the given
   * name. */
  virtual void
  Disable(const char * className);

  /** This returns the path to a dynamically loaded factory. */
  const char *
  GetLibraryPath();

  /** Registers the specified internal factory only once, even when `RegisterInternalFactoryOnce<TFactory>()` is called
   * multiple times (possibly even by multiple threads) for the very same factory. */
  template <typename TFactory>
  static void
  RegisterInternalFactoryOnce()
  {
    struct FactoryRegistration
    {};

    // Factory registration, made thread-safe by "magic statics" (as introduced with C++11).
    static const FactoryRegistration staticFactoryRegistration = [] {
      RegisterFactoryInternal(TFactory::New());
      return FactoryRegistration{};
    }();

    (void)staticFactoryRegistration;
  }

  /** Initialize the static members of ObjectFactoryBase. */
  static void
  Initialize();

protected:
  void
  PrintSelf(std::ostream & os, Indent indent) const override;

  /** Register object creation information with the factory. */
  void
  RegisterOverride(const char *               classOverride,
                   const char *               subclass,
                   const char *               description,
                   bool                       enableFlag,
                   CreateObjectFunctionBase * createFunction);

  /** This method is provided by sub-classes of ObjectFactoryBase.
   * It should create the named itk object or return 0 if that object
   * is not supported by the factory implementation. */
  virtual LightObject::Pointer
  CreateObject(const char * itkclassname);

  /** This method creates all the objects with the class override of
   * itkclass name, which are provide by this object
   */
  virtual std::list<LightObject::Pointer>
  CreateAllObject(const char * itkclassname);

  ObjectFactoryBase();
  ~ObjectFactoryBase() override;

private:
  // Forward reference because of private implementation
  class OverrideMap;
  class ObjectFactoryBasePrivate;

  /** Set/Get the pointer to ObjectFactoryBasePrivate.
   * No concurrent thread safe. */
  static void
  SynchronizeObjectFactoryBase(void * objectFactoryBasePrivate);
  itkGetGlobalDeclarationMacro(ObjectFactoryBasePrivate, PimplGlobals);

  const std::unique_ptr<OverrideMap> m_OverrideMap;

  /** Load dynamic factories from the ITK_AUTOLOAD_PATH */
  static void
  LoadDynamicFactories();

  /** Load all dynamic libraries in the given path */
  static void
  LoadLibrariesInPath(const char *);

  static void
  DeleteNonInternalFactory(ObjectFactoryBase *);

  /** Member variables for a factory set by the base class
   * at load or register time */
  void *        m_LibraryHandle{};
  unsigned long m_LibraryDate{};
  std::string   m_LibraryPath{};

  static ObjectFactoryBasePrivate * m_PimplGlobals;
};
} // end namespace itk

#endif
