//  boost/chrono/stopwatches/stopwatch.hpp  -----------------------------//

//  Copyright 2011 Vicente J. Botet Escriba
//  Distributed under the Boost Software License, Version 1.0. (See accompanying
//  file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//  See http://www.boost.org/libs/libs/chrono/stopwatches for documentation.

#ifndef BOOST_CHRONO_STOPWATCHES_STOPWATCH_HPP
#define BOOST_CHRONO_STOPWATCHES_STOPWATCH_HPP


#include <boost/chrono/config.hpp>

#include <boost/chrono/stopwatches/stopwatch_scoped.hpp>
#include <boost/chrono/stopwatches/collectors/no_memory.hpp> // default laps_collector
#include <boost/chrono/stopwatches/dont_start.hpp>
#include <boost/chrono/system_clocks.hpp> // default_clock
#include <boost/chrono/detail/system.hpp>
#include <utility>

namespace boost
{
  namespace chrono
  {


    /**
     * A stopwatch is a model of @c Stopwatch taking as parameters the @c Clock and the @c LapsCollector.
     *
     * The main difference respect to a @c simple_stopwatch is that the user can stop it.
     * Each sequence of start-stop results in a new elapsed duration sample that is provided to the LapsCollector.
     *
     * It is up to the LapsCollector to do whatever it wants with each sample.
     * A LapCollector must define a store(duration const&) and a clear() functions.
     *
     * The library provides LapsCollectors that forget the sample, store the
     * last one, cummulates the samples in an accumulator set or store them in a container.
     * For simplicity the default LapCollector is the one that forget the samples.
     *
     * Even if it is preferable to use process or thread wide clocks,
     * the default of the Clock parameter is high_resolution_clock,
     * as it is the single one ensured on all platforms.
     */
    template<typename Clock=high_resolution_clock, typename LapsCollector=no_memory<typename Clock::duration> >
    class stopwatch
    {
    public:
      typedef LapsCollector laps_collector;
      typedef Clock clock;
      typedef typename Clock::duration duration;
      typedef typename Clock::time_point time_point;
      typedef typename Clock::rep rep;
      typedef typename Clock::period period;
      BOOST_STATIC_CONSTEXPR bool is_steady = Clock::is_steady;

      /**
       * Default constructor.
       *
       * Effects: Starts the stopwatch.
       * Post-conditions: is_running().
       */
      stopwatch()
      :
        start_(duration::zero()),
        running_(false),
        laps_collector_()
      {
        start();
      }

#if !defined BOOST_CHRONO_DONT_PROVIDE_HYBRID_ERROR_HANDLING
      /**
       * Default constructor.
       *
       * Effects: Assign the error code if any internal error occur while retrieving the current time.
       * Effects: Starts the stopwatch.
       * Post-conditions: is_running() if no error occur.
       */
      explicit stopwatch(
          system::error_code & ec
          ) :
        start_(duration::zero()),
        running_(false),
        laps_collector_()
      {
        start(ec);
      }
#endif
      /**
       * Not starting constructor.
       *
       * Effects: Don't starts the stopwatch.
       * Post-conditions: ! is_running() if no error occur.
       */
      explicit stopwatch(
          const dont_start_t&
          ) :
        start_(duration::zero()),
        running_(false),
        laps_collector_()
      {
      }

      /**
       * Starting constructor from a LapsCollector instance.
       *
       * Effects: Copies the LapsCollector. Starts the stopwatch.
       * Post-conditions: is_running() if no error occur.
       *
       * Remark: The LapsCollector is copied and owned by the stopwatch.
       */
      explicit stopwatch(
          laps_collector const& acc
          ) :
        start_(duration::zero()),
        running_(false),
        laps_collector_(acc)
      {
        start();
      }

#if !defined BOOST_CHRONO_DONT_PROVIDE_HYBRID_ERROR_HANDLING
      /**
       * Starting constructor from a LapsCollector instance.
       *
       * Effects: Assign the error code if any internal error occur while retrieving the current time.
       * Effects: Copies the LapsCollector. Starts the stopwatch.
       * Post-conditions: is_running() if no error occur.
       *
       * Remark: The LapsCollector is copied and owned by the stopwatch.
       */
      explicit stopwatch(
          laps_collector const& acc,
          system::error_code & ec
          ) :
        start_(duration::zero()),
        running_(false),
        laps_collector_(acc)
      {
        start(ec);
      }
#endif

      /**
       * Not starting constructor from a LapsCollector instance.
       *
       * Effects: Copies the LapsCollector. Don't starts the stopwatch.
       * Post-conditions: ! is_running() if no error occur.
       *
       * Remark: The LapsCollector is copied and owned by the stopwatch.
       */
      stopwatch(
          laps_collector const& acc,
          const dont_start_t&
          ) :
        start_(duration::zero()),
        running_(false),
        laps_collector_(acc)
      {
      }

      /**
       * Destructor.
       *
       * Effects: Do nothing.
       */
      ~stopwatch()
      {
      }

      /**
       * Restart the stopwatch.
       *
       * Effects: Assign the error code if any internal error occur while retrieving the current time.
       * Effects: As if stop(); start() were called, but ensuring that the start time is the same as the stop time.
       *
       * Post-conditions: is_running() if no error occur.
       */
      void restart()
      {
        time_point tmp = clock::now();

        if (is_running())
        {
          laps_collector_.store(tmp - start_);
        }
        else
        {
          running_ = true;
        }
        start_ = tmp;
      }

#if !defined BOOST_CHRONO_DONT_PROVIDE_HYBRID_ERROR_HANDLING
      /**
       * Restart the stopwatch.
       *
       * Effects: Assign the error code if any internal error occur while retrieving the current time.
       * Effects: As if stop(); start() were called, but ensuring that the start time is the same as the stop time.
       *
       * Post-conditions: is_running() if no error occur.
       */
      void restart(
          system::error_code & ec
          )
      {
        time_point tmp = clock::now(ec);
        if (!BOOST_CHRONO_IS_THROWS(ec) && ec) return;

        if (is_running())
        {
          laps_collector_.store(tmp - start_);
        }
        else
        {
          running_ = true;
        }
        start_ = tmp;
      }
#endif

      /**
       * Start the stopwatch.
       *
       * Effects: Memorize the current time.
       *
       * Post-conditions: is_running().
       */
      void start()
      {
          start_ = clock::now();
          running_ = true;
      }

#if !defined BOOST_CHRONO_DONT_PROVIDE_HYBRID_ERROR_HANDLING
      /**
       * Start the stopwatch.
       *
       * Effects: Assign the error code if any internal error occur while retrieving the current time.
       * Effects: Memorize the current time.
       *
       * Post-conditions: is_running() if no error occur.
       */
      void start(
          system::error_code & ec
          )
      {
          time_point tmp = clock::now(ec);
          if (!BOOST_CHRONO_IS_THROWS(ec) && ec) return;

          start_ = tmp;
          running_ = true;
      }
#endif

      /**
       * Start the stopwatch.
       *
       * Effects: Gives the elapsed time since start time to the LapCollector.
       *
       * Throws: Any exception that the LapCollector can throw when .
       *
       * Post-conditions: !is_running() if no error occur.
       */
      void stop()
      {
        if (is_running())
        {
          laps_collector_.store(clock::now() - start_);
          start_ = time_point(duration::zero());
          running_ = false;
        }
      }

#if !defined BOOST_CHRONO_DONT_PROVIDE_HYBRID_ERROR_HANDLING
      /**
       * Start the stopwatch.
       *
       * Effects: Assign the error code if any internal error occur while retrieving the current time.
       * Effects: Gives the elapsed time since start time to the LapCollector if no internal error occurs.
       *
       * Throws: Any exception that the LapCollector can Throw.
       *
       * Post-conditions: !is_running() if no error occur.
       */
      void stop(
          system::error_code & ec
          )
      {
        if (is_running())
        {
          time_point tmp = clock::now(ec);
          if (!BOOST_CHRONO_IS_THROWS(ec) && ec) return;

          laps_collector_.store(tmp - start_);
          start_ = time_point(duration::zero());
          running_ = false;
        }
      }
#endif

      /**
       * States if the Stopwatch is running.
       */
      bool is_running() const {
        return running_;
      }

      /**
       * Elapsed time getter for the current lap.
       *
       * Returns: the elapsed time since the last start if no internal error occur.
       *
       */
      duration elapsed_current_lap() const
      {
        if (is_running())
        {
          return clock::now() - start_;
        }
        else
        {
          return duration::zero();
        }
      }

#if !defined BOOST_CHRONO_DONT_PROVIDE_HYBRID_ERROR_HANDLING
      /**
       * Elapsed time getter for the current lap.
       *
       * Effects: Assign the error code if any internal error occur while retrieving the current time.
       *
       * Returns: the elapsed time since the start if no internal error occur.
       *
       */
      duration elapsed_current_lap(
          system::error_code & ec
          ) const
      {
        if (is_running())
        {
            time_point tmp = clock::now(ec);
            if (!BOOST_CHRONO_IS_THROWS(ec) && ec) return duration::zero();

            return tmp - start_;
        } else
        {
          return duration::zero();
        }
      }
#endif

      /**
       * Elapsed time getter.
       *
       * Effects: Assign the error code if any internal error occur while retrieving the current time.
       *
       * Returns: the elapsed time since the start if no internal error occur.
       *
       */
      duration elapsed() const
      {
        return laps_collector_.elapsed()+elapsed_current_lap();
      }

#if !defined BOOST_CHRONO_DONT_PROVIDE_HYBRID_ERROR_HANDLING
      /**
       * Elapsed time getter.
       *
       * Effects: Assign the error code if any internal error occur while retrieving the current time.
       *
       * Returns: the elapsed time since the start if no internal error occur.
       *
       */
      duration elapsed(
          system::error_code & ec
          ) const
      {
        duration tmp = elapsed_current_lap(ec);
        if (!BOOST_CHRONO_IS_THROWS(ec) && ec) return duration::zero();
        return laps_collector_.elapsed() + tmp;
      }
#endif
      /**
       * Elapsed time for the last lap.
       *
       * Returns: the elapsed time of the last lap.
       *
       */

      duration last() const
      {
        return laps_collector_.last();
      }
      /**
       * Resets the stopwatch.
       *
       * Effects: Resets the LapCollector.
       *
       * Post-conditions: !is_running() if no error occur.
       *
       */
      void reset()
      {

        laps_collector_.reset();
        running_ = false;
        start_ = time_point(duration::zero());
      }

      /**
       * LapsCollector getter.
       *
       * Returns: the LapCollector instance.
       *
       */
      laps_collector const& get_laps_collector() BOOST_NOEXCEPT
      {
        return laps_collector_;
      }

      /**
       * Useful typedef for scoped run
       */
      typedef stopwatch_runner<stopwatch<Clock, LapsCollector> >
          scoped_run;
      /**
       * Useful typedef for scoped stop
       */
      typedef stopwatch_stopper<stopwatch<Clock, LapsCollector> >
          scoped_stop;

    private:
      time_point start_;
      bool running_;
      laps_collector laps_collector_;
    };

  } // namespace chrono
} // namespace boost

#endif // header
