

//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
//_/_/
//_/_/  This file is part of PSYCLONE 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/
//_/_/
//_/_/  PSYCLONE 2.0 is released under the GNU Lesser General Public License 
//_/_/  (LGPL) version 3.0, and is subject to the following
//_/_/  
//_/_/  - 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. 
//_/_/    
//_/_/  A commercial license for PSYCLONE 2.0 is also available to 
//_/_/  any interested party from the copyright holder.
//_/_/  
//_/_/  You can redistribute this copy of PSYCLONE 2.0 and/or modify it under 
//_/_/  the terms of the GNU General Public License as published by the Free 
//_/_/  Software Foundation, either version 3 of the License, or (at your 
//_/_/  option) any later version.
//_/_/  
//_/_/  You should have received a copy of the GNU General Public License
//_/_/  and the GNU Lesser General Public License along with PSYCLONE 2.0.  
//_/_/  If not, see <http://www.gnu.org/licenses/>.
//_/_/  
//_/_/  PSYCLONE 2.0 is distributed in the hope that it will be useful,
//_/_/  but WITHOUT ANY WARRANTY; without even the implied warranty of
//_/_/  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//_/_/  GNU General Public License for more details.
//_/_/
//_/_/  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 "PsyTest.h"

namespace cmlabs {






// *************************************** 
// ************** Time Test ************** 
// *************************************** 

int8 PsyTest_TimeMaster(PsyAPI* api) {

	char* myName = new char[256];
	api->getModuleName(myName, 256);

	api->logPrint(1,"[%s] started running...", myName);

	DataMessage* inMsg;
	int64 counter = 0;
	uint64 start = 0, tLocalSent, tRemoteSent, tLocalReceived;
	const char* triggerName;
	int64 count = 10000;
	int64 nodes = 1;
	double last = 0;

	int64 val;
	if (api->hasParameter("Count"))
		if (api->getParameter("Count", val))
			count = val;
	if (api->hasParameter("Nodes"))
		if (api->getParameter("Nodes", val))
			nodes = val;

	while (api->shouldContinue()) {
		if ( inMsg = api->waitForNewMessage(100, triggerName)) {
			if ( (stricmp(triggerName, "Ready") == 0) || (stricmp(triggerName, "Start") == 0) ) {
				api->logPrint(1,"[%s] starting %u count test...", myName, count);
				start = GetTimeNow();
				api->postOutputMessage("OutputMsg");
				counter = 1;
			}
			else if (stricmp(triggerName, "InputMsg") == 0) {

				tLocalSent = start;
				tLocalReceived = api->getCurrentMessageReceiveTime();
				tRemoteSent = inMsg->getSendTime();

				if (tLocalSent >= tRemoteSent)
					api->logPrint(0, "Module %u time is behind by at least %s", inMsg->getFrom(), PrintTimeDifString(tLocalSent-tRemoteSent).c_str() );
				else if (tRemoteSent >= tLocalReceived)
					api->logPrint(0, "Module %u time is ahead by at least %s", inMsg->getFrom(), PrintTimeDifString(tRemoteSent-tLocalReceived).c_str());
				else
					api->logPrint(1, "Module %u time is ok, msg time %s = out: %s + back: %s", inMsg->getFrom(),
					PrintTimeDifString(tLocalReceived-tLocalSent).c_str(),
					PrintTimeDifString(tRemoteSent-tLocalSent).c_str(),
					PrintTimeDifString(tLocalReceived-tRemoteSent).c_str());
			}
			else {
				api->logPrint(1,"[%s] got other message type '%s'...", myName, api->typeToText(inMsg->getType()).c_str());
			}
		}
		else if (counter > count) {
			// we are done, do nothing
		}
		else if (GetTimeAgeMS(start) > 3000) {
			if (counter == count) {
				api->postOutputMessage("Done");
				counter++;
			}
			else {
				api->postOutputMessage("OutputMsg");
				start = GetTimeNow();
				counter++;
			}
		}
	}

	delete [] myName;
	return 0;
}

int8 PsyTest_TimeSlave(PsyAPI* api) {
	DataMessage* inMsg;
	const char* triggerName;
	while (api->shouldContinue()) {
		if (inMsg = api->waitForNewMessage(100, triggerName))
			api->postOutputMessage();
	}
	return 0;
}



















// *************************************** 
// *********** Ping Pong Test ************ 
// *************************************** 

int8 PsyTest_PingMaster(PsyAPI* api) {

	char* myName = new char[256];
	api->getModuleName(myName, 256);

	api->logPrint(1,"[%s] started running...", myName);

	DataMessage* inMsg, *outMsg;
	int64 counter = 0, start, end;
	int64 t = 0;
	const char* triggerName;
	bool print = false;
	int64 count = 10000;
	int64 cycles = 10;
	int64 nodes = 2;
	double current, last = 0;

	int64 val;
	if (api->hasParameter("Cycles"))
		if (api->getParameter("Cycles", val))
			cycles = val;
	if (api->hasParameter("Count"))
		if (api->getParameter("Count", val))
			count = val;
	if (api->hasParameter("Nodes"))
		if (api->getParameter("Nodes", val))
			nodes = val;

	int64 totalMessageCount = cycles*count;

	while (api->shouldContinue()) {
		if ( inMsg = api->waitForNewMessage(100, triggerName)) {
			outMsg = new DataMessage();
			if ( (stricmp(triggerName, "Ready") == 0) || (stricmp(triggerName, "Start") == 0) ) {
				api->logPrint(1,"[%s] starting %u cycles test...", myName, cycles);
				start = GetTimeNow();
				print = true;
				t = 0;
				counter = 0;
			}
			else if (stricmp(triggerName, "InputMsg") == 0) {
				t += GetTimeAge(inMsg->getCreatedTime());
				// api->logPrint(2,"[%s] got ball message type '%s'...", myName, api->typeToText(inMsg->getType()).c_str());
				if (!counter++)
					start = GetTimeNow();
				else if (counter % totalMessageCount == 0) {
					end = GetTimeNow();
					current = ((double)(end-start))/(((nodes > 1) ? nodes : 2) *count);
					if (!last)
						api->logPrint(1,"[%s] got msg %llu, avg msg time: %.1fus, avg msg age: %.1fus...",
							myName, counter, current, ((double)t/count));
					else if (last > current)
						api->logPrint(1,"[%s] got msg %llu, avg msg time: %.1fus, avg msg age: %.1fus (%.1fus faster)...",
							myName, counter, current, ((double)t/count), last-current);
					else
						api->logPrint(1,"[%s] got msg %llu, avg msg time: %.1fus, avg msg age: %.1fus (%.1fus slower)...",
							myName, counter, current, ((double)t/count), current-last);
					api->postOutputMessage("Done", outMsg);
					outMsg = NULL;
				}
				else if (counter % count == 0) {
					end = GetTimeNow();
					current = ((double)(end-start))/(((nodes > 1) ? nodes : 2)*count);
					if (print) {
						if (!last)
							api->logPrint(1,"[%s] got msg %llu, avg msg time: %.1fus, avg msg age: %.1fus...",
								myName, counter, current, ((double)t/count));
						else if (last > current)
							api->logPrint(1,"[%s] got msg %llu, avg msg time: %.1fus, avg msg age: %.1fus (%.1fus faster)...",
								myName, counter, current, ((double)t/count), last-current);
						else
							api->logPrint(1,"[%s] got msg %llu, avg msg time: %.1fus, avg msg age: %.1fus (%.1fus slower)...",
								myName, counter, current, ((double)t/count), current-last);
					}
					last = current;
					t = 0;
					start = GetTimeNow();
				}
			}
			else {
				api->logPrint(1,"[%s] got other message type '%s'...", myName, api->typeToText(inMsg->getType()).c_str());
				delete(outMsg);
				outMsg = NULL;
			}
			if (outMsg) {
				api->postOutputMessage("OutputMsg", outMsg);
				// utils::Sleep(5);
			}
		}
	}

	delete [] myName;
	return 0;
}

int8 PsyTest_PingSlave(PsyAPI* api) {

	char* myName = new char[256];
	api->getModuleName(myName, 256);

	api->logPrint(1,"[%s] started running...", myName);

	const char* triggerName;
	DataMessage* inMsg;
	while (api->shouldContinue()) {
		if ( inMsg = api->waitForNewMessage(100, triggerName))
			api->postOutputMessage("OutputMsg");
	}

	delete [] myName;
	return 0;
}


















// *************************************** 
// ************* Signal Test ************* 
// *************************************** 

int8 PsyTest_SignalMaster(PsyAPI* api) {
	DataMessage* inMsg;
	const char* triggerName;

	char* myName = new char[256];
	api->getModuleName(myName, 256);
	api->logPrint(1,"[%s] started running...", myName);

	bool print = false;
	int64 counter = 0, start, end;
	int64 t = 0;
	uint64 count = 10000;
	uint64 cycles = 10;
	uint64 nodes = 1;
	double current, last = 0;

	int64 val;
	if (api->hasParameter("Cycles"))
		if (api->getParameter("Cycles", val))
			cycles = (uint64)val;
	if (api->hasParameter("Count"))
		if (api->getParameter("Count", val))
			count = (uint64)val;
	if (api->hasParameter("Nodes"))
		if (api->getParameter("Nodes", val))
			nodes = (uint64)val;

	int64 totalMessageCount = cycles*count;

	uint64 lastTime = GetTimeNow() - 1000000;
	bool done = false;

	while (api->shouldContinue()) {
		if ( inMsg = api->waitForNewMessage(0, triggerName)) {
			if ( (stricmp(triggerName, "Ready") == 0) || (stricmp(triggerName, "Start") == 0) ) {
				api->logPrint(1,"[%s] emitting signals (count: %u cycles: %u)...", myName, count, cycles);
				api->emitSignal("Output");
				print = true;
				t = 0;
				counter = 0;
				utils::Sleep(10);
				start = GetTimeNow();
			}
		}
		else if (inMsg = api->waitForSignal("Input", 100, lastTime)) {
			t += GetTimeAge(lastTime = inMsg->getCreatedTime());
			if (!counter++)
				start = GetTimeNow();
			else if (counter >= totalMessageCount) {
				end = GetTimeNow();
				current = ((double)(end-start))/(2*count); // per trip in round trip
				if (!last)
					api->logPrint(1,"[%s] finished, got signal %llu, avg signal time: %.1fus, avg signal age: %.1fus...",
						myName, counter, current, ((double)t/count));
				else if (last > current)
					api->logPrint(1,"[%s] finished, got signal %llu, avg signal time: %.1fus, avg signal age: %.1fus (%.1fus faster)...",
						myName, counter, current, ((double)t/count), last-current);
				else
					api->logPrint(1,"[%s] finished, got signal %llu, avg signal time: %.1fus, avg signal age: %.1fus (%.1fus slower)...",
						myName, counter, current, ((double)t/count), current-last);
				api->postOutputMessage("Done");
				done = true;
			}
			else if (counter % count == 0) {
				end = GetTimeNow();
				current = ((double)(end-start))/(2*count);
				if (print) {
					if (!last)
						api->logPrint(1,"[%s] cycle, got signal %llu, avg signal time: %.1fus, avg signal age: %.1fus...",
							myName, counter, current, ((double)t/count));
					else if (last > current)
						api->logPrint(1,"[%s] cycle, got signal %llu, avg signal time: %.1fus, avg signal age: %.1fus (%.1fus faster)...",
							myName, counter, current, ((double)t/count), last-current);
					else
						api->logPrint(1,"[%s] cycle, got signal %llu, avg signal time: %.1fus, avg signal age: %.1fus (%.1fus slower)...",
							myName, counter, current, ((double)t/count), current-last);
				}
				last = current;
				t = 0;
				start = GetTimeNow();
			}
			if (!done)
				api->emitSignal("Output");
		}
	}

	delete [] myName;
	return 0;
}

int8 PsyTest_SignalSlave(PsyAPI* api) {
	DataMessage* inMsg;
	const char* triggerName;

	char* myName = new char[256];
	api->getModuleName(myName, 256);
	api->logPrint(1,"[%s] started running...", myName);

//	utils::Sleep(100);

	uint64 lastTime = GetTimeNow() - 1000000;
	while (api->shouldContinue()) {
		if ( inMsg = api->waitForNewMessage(0, triggerName)) {
		}
		else if (inMsg = api->waitForSignal("Input", 100, lastTime)) {
			lastTime = inMsg->getCreatedTime();
			// api->postOutputMessage();
			api->emitSignal("Output");
			// utils::Sleep(10);
		}
	}
	delete [] myName;
	return 0;
}
























// *************************************** 
// ************ Retrieve Test ************
// *************************************** 

int8 PsyTest_RetrieveTest(PsyAPI* api) {

	char* myName = new char[256];
	api->getModuleName(myName, 256);

	int64 steps = 10;
	int64 cycles;
	if (api->hasParameter("Cycles"))
		if (api->getParameter("Cycles", cycles))
			steps = cycles;

	api->logPrint(1,"[%s] started running, %u steps...", myName, steps);

	int64 val;
	DataMessage* inMsg, *outMsg;
	const char* triggerName;
	uint64 start = GetTimeNow();

	std::list<DataMessage*> retrievedMsgs;
	std::list<DataMessage*>::iterator i, e;

	while (api->shouldContinue()) {
		if ( inMsg = api->waitForNewMessage(100, triggerName)) {
			if ( (stricmp(triggerName, "Ready") == 0) || (stricmp(triggerName, "Start") == 0) ) {
				api->logPrint(1,"[%s] got start message type '%s'...", myName, api->typeToText(inMsg->getType()).c_str());
				for (val = 0; val < 10; val++) {
					outMsg = new DataMessage();
					outMsg->setInt("count", val);
					api->logPrint(2,"[%s] Posting msg %llu   %s...", myName, val, PrintTimeString(outMsg->getCreatedTime()).c_str());
					api->postOutputMessage("OutputMsg", outMsg);
				}
				start = GetTimeNow();
			}
		}

		uint8 status;
		if (GetTimeAgeMS(start) > 1000) {
			for (uint32 nn=0; nn<steps; nn++) {
				status = api->retrieve(retrievedMsgs, "r1");
				if (status == QUERY_SUCCESS)
					api->logPrint(1,"[%s] Successfully retrieved %u messages...", myName, retrievedMsgs.size());
				else if (status == QUERY_TIMEOUT)
					api->logPrint(1,"[%s] Retrieve 'r1' timed out...", myName);
				else
					api->logPrint(1,"[%s] Retrieve 'r1' failed (%u)...", myName, status);

				for (i = retrievedMsgs.begin(), e = retrievedMsgs.end(); i != e; i++) {
					//if ((*i)->getInt("count", val))
					//	api->logPrint(1,"Count: %u", val);
					delete(*i);
				}
				retrievedMsgs.clear();

				status = api->retrieveIntegerParam(retrievedMsgs, "r2", 2, 8, 4);
				if (status == QUERY_SUCCESS)
					api->logPrint(1,"[%s] Successfully retrieved %u messages...", myName, retrievedMsgs.size());
				else if (status == QUERY_TIMEOUT)
					api->logPrint(1,"[%s] Retrieve 'r2' timed out...", myName);
				else
					api->logPrint(1,"[%s] Retrieve 'r2' failed (%u)...", myName, status);

				for (i = retrievedMsgs.begin(), e = retrievedMsgs.end(); i != e; i++) {
					//if ((*i)->getInt("count", val))
					//	api->logPrint(1,"Count: %u", val);
					delete(*i);
				}
				retrievedMsgs.clear();
				utils::Sleep(100);

				status = api->retrieveIntegerParam(retrievedMsgs, "r3", 0, 100, 100);
				if (status == QUERY_SUCCESS)
					api->logPrint(1,"[%s] Successfully retrieved %u messages...", myName, retrievedMsgs.size());
				else if (status == QUERY_TIMEOUT)
					api->logPrint(1,"[%s] Retrieve 'r3' timed out...", myName);
				else
					api->logPrint(1,"[%s] Retrieve 'r3' failed (%u)...", myName, status);

				for (i = retrievedMsgs.begin(), e = retrievedMsgs.end(); i != e; i++) {
					//if ((*i)->getInt("count", val))
					//	api->logPrint(1,"Count: %u", val);
					delete(*i);
				}
				retrievedMsgs.clear();

				uint32 datasize = 0;
				char* data = NULL;
				char* result = NULL;
				status = api->queryCatalog(&result, datasize, "MyFiles", "test", "read");
				if (status == QUERY_SUCCESS)
					api->logPrint(1,"[%s] Successfully retrieved %u bytes from file catalog...", myName, datasize);
				else if (status == QUERY_TIMEOUT)
					api->logPrint(1,"[%s] Retrieve 'read' timed out for file catalog...", myName);
				else
					api->logPrint(1,"[%s] Retrieve 'read' failed (%u) for file catalog...", myName, status);
				delete [] result;

				data = utils::StringFormat(datasize, "Hello1 World");
				status = api->queryCatalog(&result, datasize, "MyFiles", "test", "write", data, datasize);
				if (status == QUERY_SUCCESS)
					api->logPrint(1,"[%s] Successfully wrote %u bytes to file catalog...", myName, datasize);
				else if (status == QUERY_TIMEOUT)
					api->logPrint(1,"[%s] Retrieve 'write' timed out for file catalog...", myName);
				else
					api->logPrint(1,"[%s] Retrieve 'write' failed (%u) for file catalog...", myName, status);
				delete [] data;
				delete [] result;

				status = api->queryCatalog(&result, datasize, "MyData", "test", "read");
				if (status == QUERY_SUCCESS)
					api->logPrint(1,"[%s] Successfully read %u bytes from data catalog...", myName, datasize);
				else if (status == QUERY_TIMEOUT)
					api->logPrint(1,"[%s] Retrieve 'read' timed out from data catalog...", myName);
				else
					api->logPrint(1,"[%s] Retrieve 'read' failed (%u) from data catalog, ok if starting with a fresh catalog ...", myName, status);
				delete [] result;

				data = utils::StringFormat(datasize, "Hello2 World");
				status = api->queryCatalog(&result, datasize, "MyData", "test", "write", data, datasize);
				if (status == QUERY_SUCCESS)
					api->logPrint(1,"[%s] Successfully wrote %u bytes to data catalog...", myName, datasize);
				else if (status == QUERY_TIMEOUT)
					api->logPrint(1,"[%s] Retrieve 'write' timed out to data catalog...", myName);
				else
					api->logPrint(1,"[%s] Retrieve 'write' failed to data catalog (%u)...", myName, status);
				delete [] data;
				delete [] result;

				status = api->queryCatalog(&result, datasize, "MyData", "test", "read");
				if (status == QUERY_SUCCESS)
					api->logPrint(1,"[%s] Successfully read %u bytes from data catalog...", myName, datasize);
				else if (status == QUERY_TIMEOUT)
					api->logPrint(1,"[%s] Retrieve 'read' timed out from data catalog...", myName);
				else
					api->logPrint(1,"[%s] Retrieve 'read' failed (%u) from data catalog...", myName, status);
				delete [] result;
			}

			api->postOutputMessage("Done");

			start = 0;
		}
	}

	delete [] myName;
	return 0;
}

} // namespace cmlabs
