

//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
//_/_/
//_/_/  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 Stats.cpp
    @brief Implementation of the Stats sliding-window sample collector.
    All aggregates are computed on demand by walking the retained sample list
    under the (recursive) mutex; nothing is cached, so getters are O(n) and
    getMedian() additionally sorts the list in place (see the class warning
    in Stats.h about the resulting loss of insertion order). */
#include "MovingAverage.h"
#include "UnitTestFramework.h"

namespace cmlabs {

Stats::Stats(uint32 maxCount) {
	this->maxCount = maxCount;
}

Stats::~Stats() {
	mutex.enter();
	entries.clear();
	mutex.leave();
}

bool Stats::clear() {
	mutex.enter();
	entries.clear();
	mutex.leave();
	return true;
}

bool Stats::add(double val) {
	mutex.enter();
	entries.push_back(val);
	while (entries.size() > maxCount)
		entries.pop_front();
	mutex.leave();
	return true;
}

double Stats::getSum() {
	double result = 0;
	mutex.enter();
	std::list<double>::iterator i, e;
	for (i=entries.begin(), e=entries.end(); i!=e; i++)
		result += *i;
	mutex.leave();
	return result;
}

double Stats::getAverage() {
	double result;
	mutex.enter();
	result = getSum()/entries.size();
	mutex.leave();
	return result;
}

double Stats::getVariance() {
	double variance, average, size;
	mutex.enter();
	if ( (size = (double)entries.size()) <= 1) {
		mutex.leave();
		return 0;
	}
	variance = 0;
	average = getSum()/size;
	std::list<double>::iterator i, e;
	for (i=entries.begin(), e=entries.end(); i!=e; i++)
		variance += (*i-average)*(*i-average);
	variance /= size;
	mutex.leave();
	return variance;
}

double Stats::getMedian() {
	double result;
	uint32 size, half;
	mutex.enter();
	size = (uint32)entries.size();
	if (!size) { mutex.leave(); return 0; }
	else if (size == 1) { result = entries.front(); mutex.leave(); return result; }
	else if (size == 2) { result = (entries.front() + entries.back()) / 2.0; mutex.leave(); return result; }

	half = size / 2;
	entries.sort();
	std::list<double>::iterator i = entries.begin(), e = entries.end();
	if (size % 2) {
		// odd count: the middle element is at index half
		for (uint32 n = 0; n < half; n++)
			i++;
		result = *i;
	}
	else {
		// even count: average the two middle elements at indices half-1 and half
		for (uint32 n = 0; n < half - 1; n++)
			i++;
		result = *i;
		i++;
		result = (result + (*i)) / 2.0;
	}
	mutex.leave();
	return result;
}

double Stats::getStdDev() {
	return sqrt(getVariance());
}

uint32 Stats::getCount() {
	uint32 result;
	mutex.enter();
	result = (uint32)entries.size();
	mutex.leave();
	return result;
}


bool Stats::UnitTest() {
	unittest::progress(10, "populate sample");
	Stats stats;
	stats.add(1);
	stats.add(3);
	stats.add(10);
	stats.add(-5);
	stats.add(8);
	stats.add(100);
	stats.add(10);
	stats.add(1);

	unittest::progress(30, "count and sum");
	if (stats.getCount() != 8) {
		unittest::fail("Stats test: getCount expected 8, got %u", stats.getCount());
		return false;
	}
	if (stats.getSum() != 128) {
		unittest::fail("Stats test: getSum expected 128, got %f", stats.getSum());
		return false;
	}

	unittest::progress(50, "average");
	if (stats.getAverage() != 16) {
		unittest::fail("Stats test: getAverage expected 16, got %f", stats.getAverage());
		return false;
	}

	unittest::progress(65, "median");
	// Sorted sample: {-5, 1, 1, 3, 8, 10, 10, 100}; even count -> mean of the two
	// middle elements (3 and 8) -> 5.5
	if (stats.getMedian() != 5.5) {
		unittest::fail("Stats test: getMedian expected 5.5, got %f", stats.getMedian());
		return false;
	}

	unittest::progress(80, "variance");
	// Population variance (sum of squared deviations / n) = 8252 / 8 = 1031.5
	if (stats.getVariance() != 1031.5) {
		unittest::fail("Stats test: getVariance expected 1031.5, got %f", stats.getVariance());
		return false;
	}

	unittest::progress(90, "standard deviation");
	if (abs(stats.getStdDev() - 32.11697) > 0.0001) {
		unittest::fail("Stats test: getStdDev expected ~32.11697, got %f", stats.getStdDev());
		return false;
	}

	unittest::progress(100, "done");
	return true;
}

void Register_Stats_Tests() {
	UnitTestRunner::instance().registerTest("stats", Stats::UnitTest,
		"Statistics: count, sum, average, median, variance, standard deviation", "core");
}


} // namespace cmlabs
