#include "stdafx.h"
#include "analysis.h"
#include <queue>
#include <iostream>
#include <time.h>
#include <vector>
#include <algorithm>
#include <sstream>
#include "email.h"

static const time_t GARBAGE_TIME = 30; // seconds

typedef std::vector<SPacketPtr> PacketVec;

struct SGarbagePacket
{
	time_t when;
	SPacketPtr pPacket;

	bool operator<( const SGarbagePacket& rhs ) const
	{
		return when > rhs.when;
	}
};

std::priority_queue<SGarbagePacket> garbagePackets;
PacketVec encodingPackets;
PacketVec decodingPackets;

std::string FormatOutput(std::ostringstream *out)
{
	std::string output(out->str());

	std::string newOutput;
	int encPos = output.find("encoding: yes");
	int decPos = output.find("encoding: no");
	int beginDec = decPos;

	int size = output.size();
	int colWidth = 30;
	int errorWidth = 50;

	while(decPos < size)
	{
		int endln1 = output.find("\n", encPos);
		if(endln1 == std::string::npos)
			break;
		int endln2 = output.find("\n", decPos);
		if(endln2 == std::string::npos)
			break;
		std::string subA;
		if(encPos < beginDec)
		{
			subA = output.substr(encPos, endln1 - encPos);
			newOutput.append(subA);
		}
		if(subA.find("message") == std::string::npos || subA.find("begin") != std::string::npos)
		{
			newOutput.append(" ");
			for(int k = 0; k < colWidth-subA.size()-1; k++)
				newOutput.append(" ");
			std::string subB = output.substr(decPos, endln2 - decPos);
			newOutput.append(subB);
			decPos = endln2 + 1;
			if(subA.compare(subB) != 0)
			{
				newOutput.erase(newOutput.size()-1, 1);
				newOutput.append("             diff!");
			}
			newOutput.append("\n");
		}
		else
			newOutput.append("\n");
		encPos = endln1 + 1;
	}

	int lastPos = 0;
	while((lastPos = newOutput.find("end:", lastPos)) != std::string::npos)
	{
		int lineEnd = newOutput.find("\n", lastPos);

		newOutput.replace(lineEnd-1, 1, " ");

		if(lineEnd != std::string::npos)
		{
			lastPos = lineEnd+1;
			newOutput.insert(lastPos, " \n");
		}
		else break;
	}

	newOutput.append("\n ************************************************************** \n");
	newOutput.append("Original data: \n");
	newOutput.append("\n");
	newOutput.append(output);

	return newOutput;
}

void AddPacket( SPacketPtr pPacket)
{
	if (pPacket->coding.empty())
		return;

	if (pPacket->isEncoding)
		encodingPackets.push_back(pPacket);
	else
		decodingPackets.push_back(pPacket);

	SGarbagePacket pkt;
	pkt.pPacket = pPacket;
	pkt.when = time(NULL);
	garbagePackets.push( pkt );
}

void RemovePackets( PacketVec& v )
{
	if (v.empty())
		return;

	SCOPED_GLOBAL_LOCK;
	PacketVec enc, dec;

	std::sort(encodingPackets.begin(), encodingPackets.end());
	std::sort(decodingPackets.begin(), decodingPackets.end());
	std::sort(v.begin(), v.end());

	std::set_difference( encodingPackets.begin(), encodingPackets.end(), v.begin(), v.end(), back_inserter(enc) );
	std::set_difference( decodingPackets.begin(), decodingPackets.end(), v.begin(), v.end(), back_inserter(dec) );

	std::cout << "Released " << ((encodingPackets.size() - enc.size()) + (decodingPackets.size() - dec.size())) << " packets" << std::endl;

	decodingPackets = dec;
	encodingPackets = enc;
}

void GarbageCollector( void * )
{
	CThread::SetName("Encoding Garbage Collector");

	PacketVec removePackets;
	while (true)
	{
		Sleep(10000);
		SCOPED_GLOBAL_LOCK;
		time_t now = time(NULL);
		removePackets.resize(0);
		while (!garbagePackets.empty() && garbagePackets.top().when + GARBAGE_TIME <= now)
		{
			removePackets.push_back(garbagePackets.top().pPacket);
			garbagePackets.pop();
		}
		RemovePackets( removePackets );
	}
}

template <class T>
bool VecContains( const std::vector<T>& a, const std::vector<T>& b )
{
	for (int i=0; i<a.size(); i++)
		for (int j=0; j<b.size(); j++)
			if (0 == stricmp(a[i].c_str(), b[j].c_str()))
				return true;
	return false;
}

bool VerifyEncoding( const SPacket& a, const SPacket& b )
{
	if (a.coding.size() != b.coding.size())
		return false;
	for (size_t i=0; i<a.coding.size(); i++)
	{
		if (a.coding[i].nTot != b.coding[i].nTot)
			return false;
		if (a.coding[i].nSym != b.coding[i].nSym)
			return false;
		if (a.coding[i].nLow != b.coding[i].nLow)
			return false;
	}
	return true;
}

template <class T>
std::ostream& operator<<( std::ostream& out, const std::vector<T>& p )
{
	for (size_t i=0; i<p.size(); i++)
	{
		if (i)
			out << ' ';
		out << p[i];
	}
	return out;
}

std::ostream& operator<<( std::ostream& out, const SPacket& p )
{
	out << "channel : " << p.channel << std::endl;
	out << "id      : " << p.uniqueId << std::endl;
	out << "encoding: " << (p.isEncoding? "yes" : "no") << std::endl;
	out << std::endl;

	size_t j = 0;
	for (size_t i=0; i<p.coding.size(); i++)
	{
		while (j < p.annotations.size() && p.annotations[j].index <= i)
		{
			out << p.annotations[j].annotation << ":" << std::endl;
			j ++;
		}
		out << "  " << p.coding[i].nTot << " " << p.coding[i].nLow << " " << p.coding[i].nSym << std::endl;
	}

	return out;
}

void ComparePackets( std::ostream& out, const SPacket& p0, const SPacket& p1 )
{
	size_t minSize = min(p0.coding.size(), p1.coding.size());
	size_t j0 = 0, j1 = 0;
	for (size_t i=0; i<minSize; i++)
	{
		while (j0 < p0.annotations.size() && p0.annotations[j0].index <= i)
		{
			out << "0: " << p0.annotations[j0].annotation << ":" << std::endl;
			j0 ++;
		}
		while (j1 < p1.annotations.size() && p1.annotations[j1].index <= i)
		{
			out << "1: " << p1.annotations[j1].annotation << ":" << std::endl;
			j1 ++;
		}
		if (p0.coding[i] != p1.coding[i])
			out << "**";
		else
			out << "  ";
		out << "  " << p0.coding[i].nTot << " " << p0.coding[i].nLow << " " << p0.coding[i].nSym << " / " 
			<< p1.coding[i].nTot << " " << p1.coding[i].nLow << " " << p1.coding[i].nSym << std::endl;
	}
	for (size_t i=minSize; i<p0.coding.size(); i++)
	{
		while (j0 < p0.annotations.size() && p0.annotations[j0].index <= i)
		{
			out << "0: " << p0.annotations[j0].annotation << ":" << std::endl;
			j0 ++;
		}
		out << "**  " << p0.coding[i].nTot << " " << p0.coding[i].nLow << " " << p0.coding[i].nSym << " / . . ." << std::endl;
	}
	for (size_t i=minSize; i<p1.coding.size(); i++)
	{
		while (j1 < p1.annotations.size() && p1.annotations[j1].index <= i)
		{
			out << "1: " << p1.annotations[j1].annotation << ":" << std::endl;
			j1 ++;
		}
		out << "**  . . . / " << p1.coding[i].nTot << " " << p1.coding[i].nLow << " " << p1.coding[i].nSym << std::endl;
	}
}

std::string LongToIP(ULONG value, char ip[4])
{
	std::string output;
	ip = (char*)(&value);
	char buffer[5];
	int val = ip[0];
	if(val < 0)
		val = 128 + (val + 128);
	itoa(val, buffer, 10);
	output.append(buffer);
	output.append(".");
	val = ip[1];
	if(val < 0)
		val = 128 + (val + 128);
	itoa(val, buffer, 10);
	output.append(buffer);
	output.append(".");
	val = ip[2];
	if(val < 0)
		val = 128 + (val + 128);
	itoa(val, buffer, 10);
	output.append(buffer);
	output.append(".");
	val = ip[3];
	if(val < 0)
		val = 128 + (val + 128);
	itoa(val, buffer, 10);
	output.append(buffer);
	return output;
}

void Analyser(void*)
{
	CThread::SetName("Encoding Analyser");

	PacketVec enc, dec, remove;
	while (true)
	{
		Sleep(15000);
		{
			SCOPED_GLOBAL_LOCK;

			if (encodingPackets.empty() || decodingPackets.empty())
				continue;
			std::cout << "Running analysis of " << encodingPackets.size() << " encoded packets, and " << decodingPackets.size() << " decoded packets" << std::endl;
			enc = encodingPackets;
			dec = decodingPackets;
		}

		remove.resize(0);

		int notFound = 0;
		int butFound = 0;
		int errors = 0;

		ULONG crashEncoder, crashDecoder;
		crashEncoder = crashDecoder = 0;

		std::ostringstream out;
		for (PacketVec::iterator iter = enc.begin(); iter != enc.end(); ++iter)
		{
			SPacketPtr pPktEnc = *iter;
			bool found = false;
			for (PacketVec::iterator iter2 = dec.begin(); iter2 != dec.end(); ++iter2)
			{
				SPacketPtr pPktDec = *iter2;
				if (pPktEnc->session == pPktDec->session && pPktEnc->uniqueId == pPktDec->uniqueId && pPktEnc->channel == pPktDec->channel)
				{
					if (!VerifyEncoding(*pPktDec, *pPktEnc))
					{
						crashEncoder = pPktEnc->senderAddress;
						crashDecoder = pPktDec->senderAddress;
						errors ++;
						out << "Verification failed on network packet (seq=" << pPktEnc->uniqueId << ")\n\n";
						ComparePackets( out, *pPktEnc, *pPktDec );
						//out << *pPktEnc << '\n' << '\n' << *pPktDec << '\n';
						out << "\n\n\n";
					}
					found = true;
					remove.push_back(pPktDec);
					remove.push_back(pPktEnc);
					break;
				}
			}
			butFound += found;
			notFound += !found;
		}
		if (errors)
		{
			char buffer[4];
			std::string msg("Encoder Address : ");
			msg.append(LongToIP(crashEncoder, buffer));
			msg.append("\nDecoder Address : ");
			msg.append(LongToIP(crashDecoder, buffer));
			msg.append("\n\n");
			Email( msg.append(out.str()) );
		}

		enc.resize(0);
		dec.resize(0);

		SCOPED_GLOBAL_LOCK;
		std::cout << notFound << " encoded packets not found" << std::endl;
		std::cout << butFound << " encoded packets found" << std::endl;
		std::cout << errors << " incorrectly encoded packets found" << std::endl;
		RemovePackets(remove);
	}
}
