/* This file is part of Clementine.
   Copyright 2016, John Maguire <john.maguire@gmail.com>

   Clementine is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   Clementine is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with Clementine.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef LAZY_H
#define LAZY_H

#include <functional>
#include <memory>

// Helper for lazy initialisation of objects.
// Usage1:
//    Lazy<Foo> my_lazy_object([]() { return new Foo; });
// Usage2:
//    Lazy<Foo> my_lazy_object([]() { return new Foo; },
//                             [](Foo *foo) { delete foo; });
// Note: the Lazy::default_deleter just deletes the object. The default
//       unique_ptr would have a specialization for arrays. The second usage
//       should be used with arrays.
//
template <typename T>
class Lazy {
 public:
  explicit Lazy(std::function<T*()> init,
                std::function<void(T*)> deleter = Lazy::default_deleter)
      : init_(init), ptr_(nullptr, deleter) {}

  // Convenience constructor that will lazily default construct the object.
  Lazy()
      : init_([]() { return new T; }), ptr_(nullptr, Lazy::default_deleter) {}

  T* get() const {
    CheckInitialised();
    return ptr_.get();
  }

  typename std::add_lvalue_reference<T>::type operator*() const {
    CheckInitialised();
    return *ptr_;
  }

  T* operator->() const { return get(); }

  // Returns true if the object is not yet initialised.
  explicit operator bool() const { return ptr_; }

  // Deletes the underlying object and will re-run the initialisation function
  // if the object is requested again.
  void reset() { ptr_.reset(nullptr); }

  static void default_deleter(T* obj) { delete obj; }

 private:
  void CheckInitialised() const {
    if (!ptr_) {
      ptr_.reset(init_());
    }
  }

  const std::function<T*()> init_;
  mutable std::unique_ptr<T, std::function<void(T*)>> ptr_;
};

#endif  // LAZY_H