/* Copyright (C) 2019 Free Software Foundation, Inc.

   This file is part of GDB.

   This program 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.

   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.  */

#ifndef COMMON_SCOPE_EXIT_H
#define COMMON_SCOPE_EXIT_H

#include <functional>
#include <type_traits>
#include "gdbsupport/preprocessor.h"

/* scope_exit is a general-purpose scope guard that calls its exit
   function at the end of the current scope.  A scope_exit may be
   canceled by calling the "release" method.  The API is modeled on
   P0052R5 - Generic Scope Guard and RAII Wrapper for the Standard
   Library, which is itself based on Andrej Alexandrescu's
   ScopeGuard/SCOPE_EXIT.

   There are two forms available:

   - The "make_scope_exit" form allows canceling the scope guard.  Use
     it like this:

     auto cleanup = make_scope_exit ( <function, function object, lambda> );
     ...
     cleanup.release (); // cancel

   - If you don't need to cancel the guard, you can use the SCOPE_EXIT
     macro, like this:

     SCOPE_EXIT
       {
	 // any code you like here.
       }

   See also forward_scope_exit.
*/

/* CRTP base class for cancelable scope_exit-like classes.  Implements
   the common call-custom-function-from-dtor functionality.  Classes
   that inherit this implement the on_exit() method, which is called
   from scope_exit_base's dtor.  */

template <typename CRTP>
class scope_exit_base
{
public:
  scope_exit_base () = default;

  ~scope_exit_base ()
  {
    if (!m_released)
      {
	auto *self = static_cast<CRTP *> (this);
	self->on_exit ();
      }
  }

  /* This is needed for make_scope_exit because copy elision isn't
     guaranteed until C++17.  An optimizing compiler will usually skip
     calling this, but it must exist.  */
  scope_exit_base (const scope_exit_base &other)
    : m_released (other.m_released)
  {
    other.m_released = true;
  }

  void operator= (const scope_exit_base &) = delete;

  /* If this is called, then the wrapped function will not be called
     on destruction.  */
  void release () noexcept
  {
    m_released = true;
  }

private:

  /* True if released.  Mutable because of the copy ctor hack
     above.  */
  mutable bool m_released = false;
};

/* The scope_exit class.  */

template<typename EF>
class scope_exit : public scope_exit_base<scope_exit<EF>>
{
  /* For access to on_exit().  */
  friend scope_exit_base<scope_exit<EF>>;

public:

  template<typename EFP,
	   typename = gdb::Requires<std::is_constructible<EF, EFP>>>
  scope_exit (EFP &&f)
    try : m_exit_function ((!std::is_lvalue_reference<EFP>::value
			    && std::is_nothrow_constructible<EF, EFP>::value)
			   ? std::move (f)
			   : f)
  {
  }
  catch (...)
    {
      /* "If the initialization of exit_function throws an exception,
	 calls f()."  */
      f ();
    }

  template<typename EFP,
	   typename = gdb::Requires<std::is_constructible<EF, EFP>>>
  scope_exit (scope_exit &&rhs)
    noexcept (std::is_nothrow_move_constructible<EF>::value
	      || std::is_nothrow_copy_constructible<EF>::value)
    : m_exit_function (std::is_nothrow_constructible<EFP>::value
		       ? std::move (rhs)
		       : rhs)
  {
    rhs.release ();
  }

  /* This is needed for make_scope_exit because copy elision isn't
     guaranteed until C++17.  An optimizing compiler will usually skip
     calling this, but it must exist.  */
  scope_exit (const scope_exit &other)
    : scope_exit_base<scope_exit<EF>> (other),
      m_exit_function (other.m_exit_function)
  {
  }

  void operator= (const scope_exit &) = delete;
  void operator= (scope_exit &&) = delete;

private:
  void on_exit ()
  {
    m_exit_function ();
  }

  /* The function to call on scope exit.  */
  EF m_exit_function;
};

template <typename EF>
scope_exit<typename std::decay<EF>::type>
make_scope_exit (EF &&f)
{
  return scope_exit<typename std::decay<EF>::type> (std::forward<EF> (f));
}

namespace detail
{

enum class scope_exit_lhs {};

template<typename EF>
scope_exit<typename std::decay<EF>::type>
operator+ (scope_exit_lhs, EF &&rhs)
{
  return scope_exit<typename std::decay<EF>::type> (std::forward<EF> (rhs));
}

}

/* Register a block of code to run on scope exit.  Note that the local
   context is captured by reference, which means you should be careful
   to avoid inadvertently changing a captured local's value before the
   scope exit runs.  */

#define SCOPE_EXIT \
  auto CONCAT(scope_exit_, __LINE__) = ::detail::scope_exit_lhs () + [&] ()

#endif /* COMMON_SCOPE_EXIT_H */
