# Copyright 2017 The TensorFlow Authors. All Rights Reserved.
#
# 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.
# ==============================================================================
"""TensorBoard is a webapp for understanding TensorFlow runs and graphs.
"""

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import functools
import threading
import types


def lazy_load(name):
  """Decorator to define a function that lazily loads the module 'name'.

  This can be used to defer importing troublesome dependencies - e.g. ones that
  are large and infrequently used, or that cause a dependency cycle -
  until they are actually used.

  Args:
    name: the fully-qualified name of the module; typically the last segment
      of 'name' matches the name of the decorated function

  Returns:
    Decorator function that produces a lazy-loading module 'name' backed by the
    underlying decorated function.
  """
  def wrapper(load_fn):
    # Wrap load_fn to call it exactly once and update __dict__ afterwards to
    # make future lookups efficient (only failed lookups call __getattr__).
    @_memoize
    def load_once(self):
      if load_once.loading:
        raise ImportError("Circular import when resolving LazyModule %r" % name)
      load_once.loading = True
      try:
        module = load_fn()
      finally:
        load_once.loading = False
      self.__dict__.update(module.__dict__)
      load_once.loaded = True
      return module
    load_once.loading = False
    load_once.loaded = False

    # Define a module that proxies getattr() and dir() to the result of calling
    # load_once() the first time it's needed. The class is nested so we can close
    # over load_once() and avoid polluting the module's attrs with our own state.
    class LazyModule(types.ModuleType):
      def __getattr__(self, attr_name):
        return getattr(load_once(self), attr_name)

      def __dir__(self):
        return dir(load_once(self))

      def __repr__(self):
        if load_once.loaded:
          return '<%r via LazyModule (loaded)>' % load_once(self)
        return '<module %r via LazyModule (not yet loaded)>' % self.__name__

    return LazyModule(name)
  return wrapper


def _memoize(f):
  """Memoizing decorator for f, which must have exactly 1 hashable argument."""
  nothing = object()  # Unique "no value" sentinel object.
  cache = {}
  # Use a reentrant lock so that if f references the resulting wrapper we die
  # with recursion depth exceeded instead of deadlocking.
  lock = threading.RLock()
  @functools.wraps(f)
  def wrapper(arg):
    if cache.get(arg, nothing) == nothing:
      with lock:
        if cache.get(arg, nothing) == nothing:
          cache[arg] = f(arg)
    return cache[arg]
  return wrapper
