// Copyright (c) 2006-2013, Andrey N. Sabelnikov, www.sabelnikov.net
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of the Andrey N. Sabelnikov nor the
// names of its contributors may be used to endorse or promote products
// derived from this software without specific prior written permission.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER  BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// 




#pragma once


#include <list>
#include <numeric>
#include <chrono>
#include <atomic>

#include "misc_os_dependent.h"
#include "syncobj.h"

namespace epee
{
namespace math_helper
{

	template<typename val, int default_base>
	class average
	{
	public:

		average()
		{
			m_base = default_base;
			m_last_avg_val = 0;
		}

		bool set_base()
		{
			CRITICAL_REGION_LOCAL(m_lock);

			m_base = default_base;
			if(m_list.size() > m_base)
				m_list.resize(m_base);

      return true;
		}

		typedef val value_type;

		void push(const value_type& vl)
		{
			CRITICAL_REGION_LOCAL(m_lock);

//#ifndef DEBUG_STUB
			m_list.push_back(vl);
			if(m_list.size() > m_base )
				m_list.pop_front();
//#endif
		}

		double update(const value_type& vl)
		{
			CRITICAL_REGION_LOCAL(m_lock);
//#ifndef DEBUG_STUB
			push(vl);
//#endif

			return get_avg();
		}

		double get_avg()
		{
			CRITICAL_REGION_LOCAL(m_lock);

			value_type vl = std::accumulate(m_list.begin(), m_list.end(), value_type(0));
			if(m_list.size())
				return m_last_avg_val = (double)(vl/m_list.size());
	
			return m_last_avg_val = (double)vl;
		}

		value_type get_last_val()
		{
			CRITICAL_REGION_LOCAL(m_lock);
			if(m_list.size())
				return m_list.back();

			return 0;
		}

	private:
		unsigned int m_base;
		double m_last_avg_val;
		std::list<value_type> m_list;
		critical_section m_lock;
	};

	
#ifdef WINDOWS_PLATFORM
	
	/************************************************************************/
	/*                                                                      */
	/************************************************************************/
	class timing_guard_base
	{
	public:
		virtual ~timing_guard_base(){};
	};

	template<class T>
	class timing_guard: public timing_guard_base
	{
	public:
		timing_guard(T& avrg):m_avrg(avrg)
		{
			m_start_ticks = ::GetTickCount();
		}

		~timing_guard()
		{
			m_avrg.push(::GetTickCount()-m_start_ticks);
		}

	private:
		T& m_avrg;
		DWORD m_start_ticks;
	};

	template<class t_timing>
	timing_guard_base* create_timing_guard(t_timing&  timing){return new timing_guard<t_timing>(timing);}

#define BEGIN_TIMING_ZONE(timing_var) {		boost::shared_ptr<math_helper::timing_guard_base> local_timing_guard_ptr(math_helper::create_timing_guard(timing_var));
#define END_TIMING_ZONE()			  }
#endif

//#ifdef WINDOWS_PLATFORM_EX
	template<uint64_t default_time_window>
	class speed
	{
	public:

		speed()
		{
			m_time_window = default_time_window;
			m_last_speed_value = 0;
		}
		bool chick()
		{
#ifndef DEBUG_STUB
			uint64_t ticks = misc_utils::get_tick_count();
			CRITICAL_REGION_BEGIN(m_lock);
			m_chicks.push_back(ticks);
			CRITICAL_REGION_END();
			//flush(ticks);
#endif
			return true;			
		}

		bool chick(size_t count)
		{
			for(size_t s = 0; s != count; s++)
				chick();

			return true;			
		}


		size_t get_speed()
		{
			flush(misc_utils::get_tick_count());
			return m_last_speed_value = m_chicks.size();
		}
	private:

		bool flush(uint64_t ticks)
		{
			CRITICAL_REGION_BEGIN(m_lock);
			std::list<uint64_t>::iterator it = m_chicks.begin();
			while(it != m_chicks.end())
			{
				if(*it + m_time_window < ticks)
					m_chicks.erase(it++);
				else
					break;
			}
			CRITICAL_REGION_END();
			return true;
		}

		std::list<uint64_t> m_chicks;
		uint64_t m_time_window;
		size_t m_last_speed_value;
		critical_section m_lock;
	};
//#endif

	template<class tlist>
	void randomize_list(tlist& t_list)
	{
		for(typename tlist::iterator  it = t_list.begin();it!=t_list.end();it++)
		{
			size_t offset = rand()%t_list.size();
			typename tlist::iterator  it_2 = t_list.begin();
			for(size_t local_offset = 0;local_offset!=offset;local_offset++)
				it_2++;
			if(it_2 == it)
				continue;
			std::swap(*it_2, *it);
		}

	}

  // Periodic timer that gatekeeps calling of a job to a minimum interval after the previous job
  // finished.  Only the reset() call is thread-safe; everything else should be confined to the
  // owning thread.
  class periodic_task {
  public:
    explicit periodic_task(std::chrono::microseconds interval, bool start_immediate = true)
      : m_interval{interval}, m_last_worked_time{std::chrono::steady_clock::now()}, m_trigger_now{start_immediate}
    {}

    template <class functor_t>
    void do_call(functor_t functr)
    {
      if (m_trigger_now || std::chrono::steady_clock::now() - m_last_worked_time > m_interval)
      {
        functr();
        m_last_worked_time = std::chrono::steady_clock::now();
        m_trigger_now = false;
      }
    }

    // Makes the next task attempt run the job, regardless of the time since the last job. Atomic.
    void reset() { m_trigger_now = true; }
    // Returns the current interval
    std::chrono::microseconds interval() const { return m_interval; }
    // Changes the current interval
    void interval(std::chrono::microseconds us) { m_interval = us; }

  private:
    std::chrono::microseconds m_interval;
    std::chrono::steady_clock::time_point m_last_worked_time;
    std::atomic<bool> m_trigger_now;
  };
}
}
