

//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
//_/_/
//_/_/  This file is part of CMSDK version 2.1.1.260726 (26 July 2026)
//_/_/  
//_/_/  developed by
//_/_/  
//_/_/  Communicative Machines Ltd. 
//_/_/    http://cmlabs.com/
//_/_/  Copyright (c) 2026
//_/_/  
//_/_/  in collaboration with 
//_/_/  
//_/_/  Icelandic Institute for Intelligent Machines ses
//_/_/    Menntavegur 1
//_/_/    101 Reykjavik
//_/_/    ICELAND
//_/_/    http://www.iiim.is/
//_/_/
//_/_/  and is released under the following
//_/_/  
//_/_/  EXTENDED BSD License
//_/_/
//_/_/  Redistribution and use in source and binary forms, with or without
//_/_/  modification, is permitted provided that the following conditions 
//_/_/  are met:
//_/_/
//_/_/  - Redistributions of source code must retain the above copyright 
//_/_/    and collaboration 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 its copyright holders nor the names of its 
//_/_/    contributors may be used to endorse or promote products derived from 
//_/_/    this software without specific prior written permission.
//_/_/
//_/_/  - CADIA Clause: The license granted in and to the software under this 
//_/_/    agreement is a limited-use license. The software may not be used in 
//_/_/    furtherance of: (i) intentionally causing bodily injury or severe 
//_/_/    emotional harm to any person; (ii) invading the personal privacy or 
//_/_/    violating the human rights of any person; or (iii) committing or 
//_/_/    preparing for any act of war. 
//_/_/
//_/_/  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 
//_/_/  OR CONTRIBUTORS 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.
//_/_/
//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
/*! @file MovingAverage.h
    @brief Time-binned moving average / throughput tracker.

    MovingAverage accumulates (value, count) samples into a circular array of
    fixed-duration time bins (default 600 bins of 100 ms = one minute of
    history) so recent averages, sums and rates can be queried over arbitrary
    windows without storing individual samples. Used for message-rate and
    performance monitoring throughout CMSDK. */
#if !defined(_MOVINGAVERAGE_H_)
#define _MOVINGAVERAGE_H_

#include "Stats.h"
#include "PsyTime.h"

namespace cmlabs {

/*! @brief Accumulated samples of one time bin: number of samples and their value sum. */
struct StatEntry {
	uint64 count; //!< Number of samples added to this bin.
	double value; //!< Sum of sample values in this bin.
};

/*! @brief Circular-buffer moving statistics: query average, sum and per-second throughput over recent time windows.

    The tracker divides time into @c binCount consecutive bins of @c binTimeMS
    milliseconds each and keeps only the per-bin sums (StatEntry), so memory
    use is constant regardless of sample rate. When a sample or query arrives,
    the circular buffer is rotated forward so that the bin containing the
    reference time becomes current; bins that fell out of the window are
    zeroed and recycled. Queries aggregate whole bins, so results are
    quantized to bin granularity: a window of @p ms milliseconds actually
    covers ceil(ms/binTimeMS) bins including the (partially filled) current
    bin. With the defaults (100 ms x 600 bins) you can query any window up to
    one minute with 100 ms resolution.

    Time handling: all timestamps are PsyTime GetTimeNow() values in
    <b>microseconds</b>. The constructor's @p binTimeMS is converted to µs
    internally (stored as binTimeMS*1000), while the query window arguments
    (@p ms) remain in milliseconds — do not mix the two up. Samples with
    timestamps slightly in the past are credited to the correct earlier bin
    if it is still within the window; samples older than the whole window
    only count towards the lifetime totals. If the clock jumps forward by
    more than the full window (e.g. after system sleep), all bins are reset
    and only getTotal() retains history.

    Thread-safe: every public method takes the internal mutex, and the
    *Multi() variants aggregate several windows under a single lock, which is
    cheaper and yields mutually consistent numbers.

    @code
    MovingAverage msgRate(100, 600);          // 100 ms bins, 60 s of history

    // On every message received:
    msgRate.add((double)messageBytes);        // count defaults to 1 event

    // Periodic monitoring:
    double bytesPerSec, msgsPerSec;
    msgRate.getThroughput(5000, bytesPerSec, msgsPerSec);  // last 5 seconds

    double avgSize; uint64 n;
    msgRate.getAverage(60000, avgSize, n);    // mean message size, last 60 s
    @endcode

    @warning Query windows longer than binTimeMS*binCount cannot be answered
    from the retained bins; keep @p ms within the configured history. */
class MovingAverage {
public:
	/*! @brief Self test. @return true when all checks pass. */
	static bool UnitTest();

	/*! @brief Create a tracker. @param binTimeMS Duration of each bin in ms. @param binCount Number of bins kept (history = binTimeMS*binCount). */
	MovingAverage(uint32 binTimeMS = 100, uint16 binCount = 600);
	~MovingAverage();
	
	/*! @brief Record a sample.
	    @param val Value to add (e.g. bytes, latency).
	    @param count How many events this value represents (added to the bin's count).
	    @param time Sample timestamp in µs (a GetTimeNow() value); defaults to now.
	           Slightly-past timestamps land in the correct earlier bin; timestamps older
	           than the whole window are added to the lifetime totals only.
	    @return true on success.
	    @note Passing an explicit @p time avoids a second clock read when the caller
	    already has a fresh GetTimeNow() value. */
	bool add(double val, uint32 count = 1, uint64 time = GetTimeNow());

	/*! @brief Compute rates over the last @p ms milliseconds.
	    @param ms Window length in milliseconds (quantized up to whole bins).
	    @param[out] valPerSec Value sum per second. @param[out] countPerSec Event count per second.
	    @param now Reference time in µs (GetTimeNow() value). @return true on success. */
	bool getThroughput(uint32 ms, double& valPerSec, double& countPerSec, uint64 now = GetTimeNow());
	/*! @brief Compute throughput for three windows in one locked pass (cheaper than three getThroughput() calls).
	    @param ms1,ms2,ms3 Window lengths.
	    @param[out] valPerSec1,countPerSec1 Rates for window 1.
	    @param[out] valPerSec2,countPerSec2 Rates for window 2.
	    @param[out] valPerSec3,countPerSec3 Rates for window 3.
	    @param now Reference time in µs (GetTimeNow() value). @return true on success. */
	bool getThroughputMulti(
		uint32 ms1, uint32 ms2, uint32 ms3,
		double& valPerSec1, double& countPerSec1,
		double& valPerSec2, double& countPerSec2,
		double& valPerSec3, double& countPerSec3, uint64 now = GetTimeNow() );

	/*! @brief Average value over the last @p ms milliseconds.
	    @param ms Window length in milliseconds. @param[out] val Average value (unchanged sum when @p count is 0).
	    @param[out] count Number of events in the window.
	    @param now Reference time in µs (GetTimeNow() value). @return true on success. */
	bool getAverage(uint32 ms, double& val, uint64& count, uint64 now = GetTimeNow());
	/*! @brief Averages for three windows in one locked pass; parameters parallel getThroughputMulti(). @return true on success. */
	bool getAverageMulti(
		uint32 ms1, uint32 ms2, uint32 ms3,
		double& avg1, uint64& count1,
		double& avg2, uint64& count2,
		double& avg3, uint64& count3, uint64 now = GetTimeNow());

	/*! @brief Value sum over the last @p ms milliseconds.
	    @param ms Window length in milliseconds. @param[out] val Sum. @param[out] count Number of events.
	    @param now Reference time in µs (GetTimeNow() value). @return true on success. */
	bool getSum(uint32 ms, double& val, uint64& count, uint64 now = GetTimeNow());
	/*! @brief Sums for three windows in one locked pass; parameters parallel getThroughputMulti(). @return true on success. */
	bool getSumMulti(
		uint32 ms1, uint32 ms2, uint32 ms3,
		double& sum1, uint64& count1,
		double& sum2, uint64& count2,
		double& sum3, uint64& count3, uint64 now = GetTimeNow());
	
	/*! @brief Lifetime totals since construction (not limited to the bin window).
	    @param[out] val Total value sum. @param[out] count Total event count. @return true on success. */
	bool getTotal(double& val, uint64& count);

	/*! @brief Render recent history as XML performance bins. @param binMS Output bin size in ms. @param binNum Number of output bins. @return XML text. */
	std::string getPerfXML(uint32 binMS = 1000, uint32 binNum = 60);
	/*! @brief Render recent history as JSON performance bins. @param binMS Output bin size in ms. @param binNum Number of output bins. @return JSON text. */
	std::string getPerfJSON(uint32 binMS = 1000, uint32 binNum = 60);

protected:
	StatEntry* entries;
	utils::Mutex mutex;

	uint32 binTime; //!< Bin duration in µs (constructor's binTimeMS multiplied by 1000).
	uint32 binCount;
	uint32 currentBin;
	uint64 currentBinStart;
	double totalValue;
	uint64 totalCount;
	// uint64 totalTime;

	/*! @brief Advance the circular buffer so the bin containing @p now becomes current, zeroing expired bins.
	    Caller must hold the mutex. A jump of more than binCount bins clears the entire buffer.
	    @param now Time in µs (GetTimeNow() value). @return true on success. */
	bool shiftBins(uint64 now);
};


} // namespace cmlabs

#endif //_MOVINGAVERAGE_H_

