/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * 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.
 */

#pragma once

#include <type_traits>
#include <utility>

#include <folly/Optional.h>
#include <folly/functional/Invoke.h>

namespace folly {

//////////////////////////////////////////////////////////////////////

/*
 * Lazy -- for delayed initialization of a value.  The value's
 * initialization will be computed on demand at its first use, but
 * will not be recomputed if its value is requested again.  The value
 * may still be mutated after its initialization if the lazy is not
 * declared const.
 *
 * The value is created using folly::lazy, usually with a lambda, and
 * its value is requested using operator().
 *
 * Note that the value is not safe for concurrent accesses by multiple
 * threads, even if you declare it const.  See note below.
 *
 *
 * Example Usage:
 *
 *   void foo() {
 *     auto const val = folly::lazy([&]{
 *       return something_expensive(blah());
 *     });
 *
 *     if (condition1) {
 *       use(val());
 *     }
 *     if (condition2) {
 *       useMaybeAgain(val());
 *     } else {
 *       // Unneeded in this branch.
 *     }
 *   }
 *
 *
 * Rationale:
 *
 *    - operator() is used to request the value instead of an implicit
 *      conversion because the slight syntactic overhead in common
 *      seems worth the increased clarity.
 *
 *    - Lazy values do not model CopyConstructible because it is
 *      unclear what semantics would be desirable.  Either copies
 *      should share the cached value (adding overhead to cases that
 *      don't need to support copies), or they could recompute the
 *      value unnecessarily.  Sharing with mutable lazies would also
 *      leave them with non-value semantics despite looking
 *      value-like.
 *
 *    - Not thread safe for const accesses.  Many use cases for lazy
 *      values are local variables on the stack, where multiple
 *      threads shouldn't even be able to reach the value.  It still
 *      is useful to indicate/check that the value doesn't change with
 *      const, particularly when it is captured by a large family of
 *      lambdas.  Adding internal synchronization seems like it would
 *      pessimize the most common use case in favor of less likely use
 *      cases.
 *
 */

//////////////////////////////////////////////////////////////////////

namespace detail {

template <class Func>
struct Lazy {
  typedef invoke_result_t<Func> result_type;

  static_assert(
      !std::is_const<Func>::value, "Func should not be a const-qualified type");
  static_assert(
      !std::is_reference<Func>::value, "Func should not be a reference type");

  explicit Lazy(Func&& f) : func_(std::move(f)) {}
  explicit Lazy(const Func& f) : func_(f) {}

  Lazy(Lazy&& o) : value_(std::move(o.value_)), func_(std::move(o.func_)) {}

  Lazy(const Lazy&) = delete;
  Lazy& operator=(const Lazy&) = delete;
  Lazy& operator=(Lazy&&) = delete;

  const result_type& operator()() const {
    ensure_initialized();

    return *value_;
  }

  result_type& operator()() {
    ensure_initialized();

    return *value_;
  }

 private:
  void ensure_initialized() const {
    if (!value_) {
      value_ = func_();
    }
  }

  mutable Optional<result_type> value_;
  mutable Func func_;
};

} // namespace detail

//////////////////////////////////////////////////////////////////////

template <class Func>
auto lazy(Func&& fun) {
  return detail::Lazy<remove_cvref_t<Func>>(std::forward<Func>(fun));
}

//////////////////////////////////////////////////////////////////////

} // namespace folly
