# Copyright 2018 The dm_control Authors.
#
# 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
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# 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.
# ============================================================================

"""Decorators for Entity methods returning elements and observables."""

import abc
import threading


class cached_property(property):  # pylint: disable=invalid-name
  """A property that is evaluated only once per object instance."""

  def __init__(self, func, doc=None):
    super().__init__(fget=func, doc=doc)
    self.lock = threading.RLock()

  def __get__(self, obj, cls):
    if obj is None:
      return self
    name = self.fget.__name__
    obj_dict = obj.__dict__
    try:
      # Try returning a precomputed value without locking first.
      # Profiling shows that the lock takes up a non-trivial amount of time.
      return obj_dict[name]
    except KeyError:
      # The value hasn't been computed, now we have to lock.
      with self.lock:
        try:
          # Check again whether another thread has already computed the value.
          return obj_dict[name]
        except KeyError:
          # Otherwise call the function, cache the result, and return it
          return obj_dict.setdefault(name, self.fget(obj))


# A decorator for base.Observables methods returning an observable. This
# decorator should be used by abstract base classes to indicate sub-classes need
# to implement a corresponding @observavble annotated method.
abstract_observable = abc.abstractproperty  # pylint: disable=invalid-name


class observable(cached_property):  # pylint: disable=invalid-name
  """A decorator for base.Observables methods returning an observable.

  The body of the decorated function is evaluated at Entity construction time
  and the observable is cached.
  """
  pass
