from threading import Lock


def singleton(cls):
  """
  A class decorator that ensures there can only ever be one instance of the decorated class. It prevents the decorated
  class from being created by overriding its '__new__' function, and it adds a new static zero-argument thread-safe
  function named 'instance' that should be used to initialize / retrieve the singleton instance. The wrapped class must
  have a zero-argument constructor, and it must not have an attribute named 'instance'.

  Example:
  @singleton
  class Test(object):
    pass

  Test() # Calling the constructor directly will raise an exception
  Test.instance() == Test.instance() # True
  type(Test.instance()) == Test # True
  Test.__name__ # Singleton<Test>
  """

  if hasattr(cls, 'instance'):
    raise Exception("The attribute 'instance' is reserved in classes decorated with '@singleton'")

  # We are going to replace these properties, but we still want references to the originals
  orig__new__ = cls.__new__
  orig__name__ = cls.__name__

  def override__new__(self):
    raise Exception("%s cannot be instantiated directly (use '%s.instance()' instead)" % (cls.__name__, orig__name__))
  cls.__new__ = override__new__
  cls.__name__ = "Singleton<%s>" % cls.__name__

  __instance = None
  __instance_lock = Lock()
  def instance():
    nonlocal __instance, __instance_lock
    if not __instance:
      __instance_lock.acquire()
      try:
        if not __instance:
          __instance = orig__new__(cls)
          __instance.__init__()
      finally:
        __instance_lock.release()
    return __instance
  cls.instance = instance

  return cls


def singleton_mock(singleton_cls):
  """
  Example:

  @singleton
  class Foo(object):
    def bar(self):
      return "a"
    def baz(self):
      return "b"

  Foo.instance().bar() -> "a"

  @singleton_mock(Foo)
  class MockFoo(object):
    def bar(self):
      return "c"

  Foo.instance().bar() -> "c"
  Foo.instance().baz() -> Exception

  # You can do partial mocks by extending from the original object
  @singleton_mock(Foo)
  class MockFoo(Foo):
    def baz(self):
      return "d"

  Foo.instance().bar() -> "a"
  Foo.instance().baz() -> "d"
  """

  if not hasattr(singleton_cls, 'instance'):
    raise Exception("The class '%s' does not appear to be decorated with '@singleton'" % singleton_cls)

  def replace_with_class(mock_cls):
    singleton_mock_cls = singleton(mock_cls)
    singleton_cls.instance = singleton_mock_cls.instance
    singleton_cls.__name__ = singleton_mock_cls.__name__
    return singleton_mock_cls
  return replace_with_class
