

//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
//_/_/
//_/_/  This file is part of CMSDK version 2.0.0.170915_Beta4 (15 September 2017)
//_/_/  
//_/_/  developed by
//_/_/  
//_/_/  Communicative Machines Ltd. 
//_/_/    http://cmlabs.com/
//_/_/  Copyright (c) 2017
//_/_/  
//_/_/  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.
//_/_/
//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
#include "MovingAverage.h"

namespace cmlabs {

MovingAverage::MovingAverage(uint32 binTimeMS, uint16 binCount) {
	mutex.enter(200, __FUNCTION__);
	this->binTime = binTimeMS * 1000;
	this->binCount = binCount;
	entries = (StatEntry*)malloc(binCount*sizeof(StatEntry));
	memset(entries, 0, binCount*sizeof(StatEntry));
	currentBin = 0;
	currentBinStart = 0;
	totalValue = 0;
	totalCount = 0;
	mutex.leave();
}

MovingAverage::~MovingAverage() {
	mutex.enter(200, __FUNCTION__);
	free(entries);
	entries = NULL;
	mutex.leave();
}

bool MovingAverage::shiftBins(uint64 now) {
	// assume mutex is already locked
	// assume we already checked that data has been added

	int64 binShift = ( now - currentBinStart) / binTime;
	if (binShift < 0) {
		// This is a past time, don't change current bin
	}
	else if (binShift > binCount) {
		// We have missed all of our bins, reset everything except total
		// The computer was probably in sleep mode...
		currentBinStart = now;
		memset(entries, 0, binCount*sizeof(StatEntry));
		currentBin = 0;
	}
	else {
		// Shift the current bin forwards, wrap around as needed
		while (binShift > 0) {
			if (++currentBin >= binCount)
				currentBin = 0;
			entries[currentBin].count = 0;
			entries[currentBin].value = 0;
			binShift--;
			currentBinStart += binTime;
		}
	}
	return true;
}


bool MovingAverage::add(double val, uint32 count, uint64 time) {
	// We assume that time is close enough to 'now' so we don't have to do another GetTimeNow()

	mutex.enter(200, __FUNCTION__);

	if (!currentBinStart)
		currentBinStart = time;
	else {
		int64 binShift = ( time - currentBinStart) / binTime;
		if (binShift < 0) {
			// This is a past entry, put it into the old bin entry
			if (abs(binShift) < binCount) {
				int64 oldBin = (int64)currentBin + binShift; // negative binShift
				if (oldBin < 0)
					oldBin += binCount;
				entries[(uint64)oldBin].value += val;
				entries[(uint64)oldBin].count += count;
				totalValue += val;
				totalCount += count;
				mutex.leave();
				return true;
			}
			else {
				// too old, just add to total
				totalValue += val;
				totalCount += count;
				mutex.leave();
				return true;
			}
		}
		else if (binShift > binCount) {
			// We have missed all of our bins, reset everything except total
			// The computer was probably in sleep mode...
			currentBinStart = time;
			memset(entries, 0, binCount*sizeof(StatEntry));
			currentBin = 0;
		}
		else {
			// Shift the current bin forwards, wrap around as needed
			while (binShift > 0) {
				if (++currentBin >= binCount)
					currentBin = 0;
				entries[currentBin].count = 0;
				entries[currentBin].value = 0;
				binShift--;
				currentBinStart += binTime;
			}
		}
	}

	entries[currentBin].value += val;
	entries[currentBin].count += count;
	totalValue += val;
	totalCount += count;

	mutex.leave();
	return true;
}

bool MovingAverage::getAverage(uint32 ms, double& val, uint64& count, uint64 now) {
	if (!getSum(ms, val, count, now))
		return false;

	if (count)
		val = val / count;
	return true;
}

bool MovingAverage::getAverageMulti(
	uint32 ms1, uint32 ms2, uint32 ms3,
	double& avg1, uint64& count1,
	double& avg2, uint64& count2,
	double& avg3, uint64& count3, uint64 now) {

	double val1 = 0, val2 = 0, val3 = 0;
	if (!getSumMulti(ms1, ms2, ms3, val1, count1, val2, count2, val3, count3, now))
		return false;

	avg1 = avg2 = avg3 = 0;
	if (count1)
		avg1 = val1 / count1;
	if (count2)
		avg2 = val2 / count2;
	if (count3)
		avg3 = val3 / count3;

	return true;
}

bool MovingAverage::getSum(uint32 ms, double& val, uint64& count, uint64 now) {
	uint64 bins = ((uint64)ms*1000)/binTime;
	if (bins == 0)
		bins = 1;
	else if (bins > binCount-1)
		bins = binCount-1;
	
	val = 0;
	count = 0;

	mutex.enter(200, __FUNCTION__);

	if (!currentBinStart) {
		mutex.leave();
		return true;
	}
	else if (!shiftBins(now)) {
		mutex.leave();
		return false;
	}

	StatEntry* entry = entries + currentBin;
	// Never include the current bin as it is still being built
	if (entry == entries)
		entry = entries + binCount;
	entry--;
	for (uint64 n=0; n<bins; n++) {
		val += entry->value;
		count += entry->count;
		if (entry == entries)
			entry = entries + binCount;
		entry--;
	}

	mutex.leave();
	return true;
}

bool MovingAverage::getSumMulti(
	uint32 ms1, uint32 ms2, uint32 ms3,
	double& sum1, uint64& count1,
	double& sum2, uint64& count2,
	double& sum3, uint64& count3, uint64 now) {

	uint64 bins1 = ((uint64)ms1*1000)/binTime;
	if (bins1 == 0) bins1 = 1; else if (bins1 > binCount-1) bins1 = binCount-1;
	uint64 bins2 = ((uint64)ms2*1000)/binTime;
	if (bins2 == 0) bins2 = 1; else if (bins2 > binCount-1) bins2 = binCount-1;
	uint64 bins3 = ((uint64)ms3*1000)/binTime;
	if (bins3 == 0) bins3 = 1; else if (bins3 > binCount-1) bins3 = binCount-1;
	
	uint64 bins = clmax(clmax(bins1, bins2), bins3);
	sum1 = sum2 = sum3 = 0;
	count1 = count2 = count3 = 0;

	mutex.enter(200, __FUNCTION__);

	if (!currentBinStart) {
		mutex.leave();
		return true;
	}
	else if (!shiftBins(now)) {
		mutex.leave();
		return false;
	}

	StatEntry* entry = entries + currentBin;
	// Never include the current bin as it is still being built
	if (entry == entries)
		entry = entries + binCount;
	entry--;
	for (uint64 n=0; n<bins; n++) {
		if (n<bins1) {
			sum1 += entry->value;
			count1 += entry->count;
		}
		if (n<bins2) {
			sum2 += entry->value;
			count2 += entry->count;
		}
		if (n<bins3) {
			sum3 += entry->value;
			count3 += entry->count;
		}
		if (entry == entries)
			entry = entries + binCount;
		entry--;
	}

	mutex.leave();
	return true;

}


bool MovingAverage::getTotal(double& val, uint64& count) {
	mutex.enter(200, __FUNCTION__);
	val = totalValue;
	count = totalCount;
	mutex.leave();
	return true;
}

bool MovingAverage::getThroughput(uint32 ms, double& valPerSec, double& countPerSec, uint64 now) {
	double val;
	uint64 count;
	if (!getSum(ms, val, count, now))
		return false;

	double sec = (double)ms/1000.0;
	if (sec > 0) {
		valPerSec = val / sec;
		countPerSec = count / sec;
	}
	return true;
}

bool MovingAverage::getThroughputMulti(uint32 ms1, uint32 ms2, uint32 ms3, double& valPerSec1, double& countPerSec1, double& valPerSec2, double& countPerSec2, double& valPerSec3, double& countPerSec3, uint64 now ) {

	double val1 = 0, val2 = 0, val3 = 0;
	uint64 count1 = 0, count2 = 0, count3 = 0;
	if (!getSumMulti(ms1, ms2, ms3, val1, count1, val2, count2, val3, count3, now))
		return false;

	double sec = (double)ms1/1000.0;
	if (sec > 0) {
		valPerSec1 = val1 / sec;
		countPerSec1 = count1 / sec;
	}
	
	sec = (double)ms2/1000.0;
	if (sec > 0) {
		valPerSec2 = val2 / sec;
		countPerSec2 = count2 / sec;
	}

	sec = (double)ms3/1000.0;
	if (sec > 0) {
		valPerSec3 = val3 / sec;
		countPerSec3 = count3 / sec;
	}

	return true;
}



//bool MovingAverage::getThroughputMultiAlt(uint32 ms1, uint32 ms2, uint32 ms3, double& valPerSec1, double& countPerSec1, double& valPerSec2, double& countPerSec2, double& valPerSec3, double& countPerSec3 ) {
//	if (!getThroughput(ms1, valPerSec1, countPerSec1))
//		return false;
//	if (!getThroughput(ms2, valPerSec2, countPerSec2))
//		return false;
//	if (!getThroughput(ms3, valPerSec3, countPerSec3))
//		return false;
//	return true;
//}






bool MovingAverage::UnitTest() {
	MovingAverage stats(100, 60*10);

	uint32 innerCount = 100000;
	uint32 outerCount = 30;

	uint64 start, now = GetTimeNow();
	uint32 n, m;
	double val, v1, v2, v3;
	uint64 count;
	double c1, c2, c3;

	//start = GetTimeNow();
	//for (m=0; m<100; m++) {
	//	stats.add(1000, now);
	//	now += 100000;
	//}
	//stats.getAverage(1000, val, count);
	//printf("[%fus] Average %f count: %llu\n", ((double)GetTimeAge(start))/100, val, count);
	//return true;
	//for (n=0; n<outerCount; n++) {
	//	start = GetTimeNow();
	//	for (m=0; m<innerCount; m++) {
	//		stats.add(2.222, now);
	//	}
	//	printf("Time per simple add: %.3fus\n", ((double)GetTimeAge(start))/innerCount);
	//}
	for (n=0; n<outerCount; n++) {
		start = GetTimeNow();
		for (m=0; m<innerCount; m++) {
			stats.add(1024, 1, now);
			now += 10;
		}
		printf("Time per timed add: %.3fus\n", ((double)GetTimeAge(start))/innerCount);
	}

	printf("\n");

	now = GetTimeNow();

	start = GetTimeNow();
	for (n=0; n<outerCount; n++)
		stats.getAverage(1000, val, count, now);
	printf("[%fus] Average %f count: %llu\n", ((double)GetTimeAge(start))/outerCount, val, count);
	
	start = GetTimeNow();
	for (n=0; n<outerCount; n++)
		stats.getSum(1000, val, count, now);
	printf("[%fus] Sum %f count: %llu\n", ((double)GetTimeAge(start))/outerCount, val, count);

	start = GetTimeNow();
	for (n=0; n<outerCount; n++)
		stats.getThroughput(1000, v1, c1, now);
	printf("[%fus] 1sec %s %.3f msg/s\n",
		((double)GetTimeAge(start))/outerCount,
		utils::BytifyRate(v1).c_str(), c1);

	start = GetTimeNow();
	for (n=0; n<outerCount; n++)
		stats.getThroughputMulti(1000, 10000, 30000, v1, c1, v2, c2, v3, c3, now);
	printf("[%fus] 1sec %s %.3f msg/s 10sec %s %.3f msg/s 30sec %s %.3f msg/s\n",
		((double)GetTimeAge(start))/outerCount,
		utils::BytifyRate(v1).c_str(),
		c1,
		utils::BytifyRate(v2).c_str(),
		c2,
		utils::BytifyRate(v3).c_str(),
		c3);

	return true;
}





} // namespace cmlabs
