/*=========================================================================
 *
 *  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.
 *
 *=========================================================================*/
#include "itkSingleton.h"

#include <mutex>

namespace
{
std::once_flag globalSingletonOnceFlag;

// This ensures that m_GlobalSingletonIndex has been initialized once the library
// has been loaded. In some cases, this call will perform the initialization.
// In other cases, static initializers like the IO factory initialization code
// will have done the initialization.
itk::SingletonIndex * initializedGlobalSingletonIndex = itk::SingletonIndex::GetInstance();

/** \class GlobalSingletonIndexInitializer
 *
 * \brief Initialize a GlobalSingletonIndex and delete it on program
 * completion.
 * */
class GlobalSingletonIndexInitializer
{
public:
  using Self = GlobalSingletonIndexInitializer;
  using SingletonIndex = itk::SingletonIndex;

  GlobalSingletonIndexInitializer() = default;

  /** Delete the time stamp if it was created. */
  ~GlobalSingletonIndexInitializer()
  {
    delete m_GlobalSingletonIndex;
    m_GlobalSingletonIndex = nullptr;
  }

  /** Create the GlobalSingletonIndex if needed and return it. */
  static SingletonIndex *
  GetGlobalSingletonIndex()
  {
    std::call_once(globalSingletonOnceFlag, []() {
      m_GlobalSingletonIndex = new SingletonIndex;

      // To avoid being optimized out. The compiler does not like this
      // statement at a higher scope.
      Unused(initializedGlobalSingletonIndex);
    });

    return m_GlobalSingletonIndex;
  }

private:
  static SingletonIndex * m_GlobalSingletonIndex;
};

// Takes care of cleaning up the GlobalSingletonIndex
static GlobalSingletonIndexInitializer GlobalSingletonIndexInitializerInstance;
// Initialized by the compiler to zero
GlobalSingletonIndexInitializer::SingletonIndex * GlobalSingletonIndexInitializer::m_GlobalSingletonIndex;

} // end anonymous namespace


// may return NULL if string is not registered already
//
// access something like a std::map<std::string, void *> or
// registered globals, it may be possible to restrict the held
// classes to be derived from itk::LightObject, so dynamic cast can
// work, and could use some type of Holder<T> class for intrinsic types
namespace itk
{
void *
SingletonIndex::GetGlobalInstancePrivate(const char * globalName)
{
  SingletonData::iterator it;
  it = m_GlobalObjects.find(globalName);
  if (it == m_GlobalObjects.end())
  {
    return nullptr;
  }
  return std::get<0>(it->second);
}

// If globalName is already registered remove it from map,
// otherwise global is added to the singleton index under globalName
bool
SingletonIndex::SetGlobalInstancePrivate(const char *                globalName,
                                         void *                      global,
                                         std::function<void(void *)> func,
                                         std::function<void()>       deleteFunc)
{
  m_GlobalObjects.erase(globalName);
  m_GlobalObjects.insert(std::make_pair(globalName, std::make_tuple(global, func, deleteFunc)));
  return true;
}

SingletonIndex *
SingletonIndex::GetInstance()
{
  if (m_Instance == nullptr)
  {
    m_Instance = GlobalSingletonIndexInitializer::GetGlobalSingletonIndex();
  }
  return m_Instance;
}

void
SingletonIndex::SetInstance(Self * instance)
{
  m_Instance = instance;
}


SingletonIndex::~SingletonIndex()
{
  for (auto & pair : m_GlobalObjects)
  {
    std::get<2>(pair.second)();
  }
}

SingletonIndex::Self * SingletonIndex::m_Instance;


} // end namespace itk
