// Copyright (c) 2020 OPEN CASCADE SAS
//
// This file is part of Open CASCADE Technology software library.
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License version 2.1 as published
// by the Free Software Foundation, with special exception defined in the file
// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
// distribution for complete text of the license and disclaimer of any warranty.
//
// Alternatively, this file may be used under the terms of Open CASCADE
// commercial license or contractual agreement.

#include <TDF_DerivedAttribute.hxx>

#include <NCollection_DataMap.hxx>
#include <Standard_CStringHasher.hxx>
#include <TCollection_AsciiString.hxx>

#include <mutex>

namespace TDF_DerivedAttributeGlobals
{

//! Data for the derived attribute correct creation
struct CreatorData
{
  TDF_DerivedAttribute::NewDerived myCreator;
  const char*                      myNameSpace;
  const char*                      myTypeName;
};

//! List that contains the methods that create all registered derived attributes
static NCollection_List<CreatorData>& Creators()
{
  static NCollection_List<CreatorData> THE_CREATORS_LIST;
  return THE_CREATORS_LIST;
}

//! Global map of the string-type of derived attribute -> instance of such attribute
static NCollection_DataMap<const char*, occ::handle<TDF_Attribute>, Standard_CStringHasher>&
  Attributes()
{
  static NCollection_DataMap<const char*, occ::handle<TDF_Attribute>, Standard_CStringHasher>
    THE_DERIVED;
  return THE_DERIVED;
}

//! Global map of the string-type of derived attribute -> type name to identify this attribute
static NCollection_DataMap<const char*, TCollection_AsciiString*, Standard_CStringHasher>& Types()
{
  static NCollection_DataMap<const char*, TCollection_AsciiString*, Standard_CStringHasher>
    THE_DERIVED_TYPES;
  return THE_DERIVED_TYPES;
}

//! To minimize simultaneous access to global "DERIVED" maps from parallel threads
static std::mutex& Mutex()
{
  static std::mutex THE_DERIVED_MUTEX;
  return THE_DERIVED_MUTEX;
}
} // namespace TDF_DerivedAttributeGlobals

//=======================================================================
// function : Register
// purpose  : Registers a derived by the pointer to a method that creates a new derived attribute
// instance
//=======================================================================
TDF_DerivedAttribute::NewDerived TDF_DerivedAttribute::Register(NewDerived  theNewAttributeFunction,
                                                                const char* theNameSpace,
                                                                const char* theTypeName)
{
  TDF_DerivedAttributeGlobals::CreatorData aData = {theNewAttributeFunction,
                                                    theNameSpace,
                                                    theTypeName};
  std::lock_guard<std::mutex>              aLock(TDF_DerivedAttributeGlobals::Mutex());
  TDF_DerivedAttributeGlobals::Creators().Append(aData);
  return theNewAttributeFunction;
}

//=======================================================================
// function : Initialize
// purpose  : Checks synchronization and performs initialization of derived attributes maps if
// needed
//=======================================================================
static void Initialize()
{
  if (!TDF_DerivedAttributeGlobals::Creators().IsEmpty())
  { // initialization
    NCollection_List<TDF_DerivedAttributeGlobals::CreatorData>::Iterator aCreator;
    for (aCreator.Initialize(TDF_DerivedAttributeGlobals::Creators()); aCreator.More();
         aCreator.Next())
    {
      occ::handle<TDF_Attribute> aDerived            = aCreator.Value().myCreator();
      const char*                aDerivedDynamicType = aDerived->DynamicType()->Name();

      TCollection_AsciiString aTypeName;
      if (aCreator.Value().myNameSpace != nullptr)
      {
        if (aCreator.Value().myNameSpace[0] != '\0')
        {
          aTypeName = aCreator.Value().myNameSpace;
          aTypeName += ':';
        }
      }
      if (aCreator.Value().myTypeName == nullptr)
      {
        aTypeName += aDerivedDynamicType;
      }
      else
      {
        aTypeName += aCreator.Value().myTypeName;
      }

      // persistent storage of types strings: they are not changed like maps on resize
      static NCollection_List<TCollection_AsciiString> THE_TYPES_STORAGE;
      THE_TYPES_STORAGE.Append(aTypeName);
      TDF_DerivedAttributeGlobals::Types().Bind(aDerivedDynamicType, &(THE_TYPES_STORAGE.Last()));
      TDF_DerivedAttributeGlobals::Attributes().Bind(aDerivedDynamicType, aDerived);
    }
    TDF_DerivedAttributeGlobals::Creators().Clear();
  }
}

//=================================================================================================

occ::handle<TDF_Attribute> TDF_DerivedAttribute::Attribute(const char* theType)
{
  std::lock_guard<std::mutex> aLock(TDF_DerivedAttributeGlobals::Mutex());
  Initialize();
  if (const occ::handle<TDF_Attribute>* aResult =
        TDF_DerivedAttributeGlobals::Attributes().Seek(theType))
  {
    return *aResult;
  }

  static const occ::handle<TDF_Attribute> aNullAttrib;
  return aNullAttrib;
}

//=================================================================================================

const TCollection_AsciiString& TDF_DerivedAttribute::TypeName(const char* theType)
{
  std::lock_guard<std::mutex> aLock(TDF_DerivedAttributeGlobals::Mutex());
  Initialize();
  if (TCollection_AsciiString* const* aResult = TDF_DerivedAttributeGlobals::Types().Seek(theType))
  {
    return **aResult;
  }

  return TCollection_AsciiString::EmptyString();
}

//=================================================================================================

void TDF_DerivedAttribute::Attributes(NCollection_List<occ::handle<TDF_Attribute>>& theList)
{
  std::lock_guard<std::mutex> aLock(TDF_DerivedAttributeGlobals::Mutex());
  Initialize();
  NCollection_DataMap<const char*, occ::handle<TDF_Attribute>, Standard_CStringHasher>::Iterator
    anAttrIter;
  for (anAttrIter.Initialize(TDF_DerivedAttributeGlobals::Attributes()); anAttrIter.More();
       anAttrIter.Next())
  {
    theList.Append(anAttrIter.Value());
  }
}
