/*
	ZNetPeer.cpp
	Author: Patrick Baggett	<ptbaggett@762studios.com>
	Created: 7/18/2013

	Purpose:

	ZNet peer class, representing a remote host

	License:
	
	Copyright 2013, 762 Studios
*/

/*************************************************************************/

#include <ZNet/ZNetPeer.hpp>
#include <ZNet/ZNetPacketChannel.hpp>
#include <ZNet/ZNetHost.hpp>
#include "ZNetPrivate.hpp"
#include <SST/SST_OS.h>
#include <new>


/*************************************************************************/

ZNetPeer::ZNetPeer()
{
	lastValidIncoming = 0;
	lastOutgoingAck = 0;
	socketCopy = 0;
	channels = NULL;
	userdata = NULL;
	nrChannels = 0;
	ping = -1;
	state = STATE_UNCONNECTED;
}

/*************************************************************************/

bool ZNetPeer::Initialize(ZNetHost* _host, const SST_NetAddress* newAddr, SST_Socket s, uint32_t _nrChannels)
{
	SST_OS_DebugAssert(_host != NULL, "Host may not be NULL");
	SST_OS_DebugAssert(channels == NULL, "This should have been NULL; memory leak!");

	channels = new(std::nothrow) ZNetPacketChannel[_nrChannels];
	if(channels == NULL)
		return false;

	//Initialize channel ID (new[] doesn't allow non-default constructors)
	for(uint32_t i=0; i<_nrChannels; i++)
	{
		channels[i].SetChannelId(i);
		channels[i].SetHost(_host);
	}

	//Store other properties
	memcpy(&addr, newAddr, sizeof(SST_NetAddress));
	nrChannels = _nrChannels;
	socketCopy = s;
	userdata = NULL;
	lastValidIncoming = SST_OS_GetMilliTime();
	state = STATE_HANDSHAKE;

	//OK
	return true;
}

/*************************************************************************/

void ZNetPeer::Deinitialize()
{
	for(uint32_t i=0; i<nrChannels; i++)
		channels[i].Deinitialize();

	delete[] channels;
}

/*************************************************************************/

ZNetPacketChannel* ZNetPeer::GetPacketChannel(uint32_t chId)
{
	//Really, this is trivial, but I don't want asserts to be conditional on whether
	//the user of the API has enabled them vs how the library was built.
	SST_OS_DebugAssert(chId < nrChannels, "Invalid channel number");

	return &channels[chId];
}

/*************************************************************************/

void ZNetPeer::ProcessLocalAcks()
{
	for(uint32_t i=0; i<nrChannels; i++)
		channels[i].ProcessLocalAcks();
}

/*************************************************************************/

void ZNetPeer::SendAcksForAllChannels()
{
	uint8_t data[9000];
	uint32_t mtu = this->GetPacketChannel(ZNET_SYSCHANNEL)->GetHost()->GetMTU();
	bool unsent = true;

	uint64_t now = SST_OS_GetMilliTime();

	//TODO: maybe not the best idea...? it will start out by sending an ACK 0...kinda silly I guess
	if(lastOutgoingAck == 0 ||
		((ping > 0) && (now > lastOutgoingAck + (uint64_t)ping) && (now - lastOutgoingAck > 50)) ) //TODO: does a minimum ack latency of 50 make any sense? in the cases where ping is realllly low (e.g. <= 1msec) don't need to spam?
	{
		lastOutgoingAck = now;
	}
	else
		return;


	ZBinaryBufferWriter writer(data, mtu, ZNET_BYTEORDER);
	
	//Write the write header
	ZNetPrivate::WriteWirePacketHeader(&writer);

	//Write an ACK point for each channel
	for(uint32_t i=0; i<nrChannels; i++)
	{
		ZNetPacketChannel* channel = GetPacketChannel(i);
		uint32_t remain = mtu - (uint32_t)writer.GetOffset();

		//Can we fit another ack?
		if(remain > ZNetPrivate::WireSizeForSimpleCommand(ZNETCMD_ACK))
		{
			//TODO: flags??
			ZNetPrivate::WriteWireMessageHeader(&writer, i, 0, ZNETCMD_ACK, 0);

			writer.WriteU16(channel->GetLocalAck());
			writer.WriteU16(channel->GetHighestSent());

			unsent = true;
		}
		else
		{
			ZNetPrivate::SendAll(socketCopy, &addr, data, (uint32_t)writer.GetOffset());
			unsent = false;

			//Restart
			writer.Rewind();
			ZNetPrivate::WriteWirePacketHeader(&writer);
		}

	}

	if(unsent)
		ZNetPrivate::SendAll(socketCopy, &addr, data, (uint32_t)writer.GetOffset());
}

/*************************************************************************/
