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

	Purpose:

	ZNetServer -- extends ZNetHost to provide a server

	License:

	Copyright 2013, 762 Studios
*/

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

#include <ZNet/ZNetServer.hpp>
#include <ZNet/ZNetPeer.hpp>
#include <ZNet/ZNetPacket.hpp>
#include <ZUtil/ZBinaryBufferReader.hpp>
#include <SST/SST_Assert.h> //assertions
#include <SST/SST_Time.h>
#include <new> //std::nothrow

#include "ZNetPrivate.hpp"


#include <stdio.h> //TEMP

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

ZNetServer::ZNetServer()
{
	connectCallback = (ZNetConnectCallback)NULL;
	callbackParam = NULL;
	peers = NULL;
	maxClients = 0;
	clientCount = 0;
	listenFlag = false;

	for(size_t i=0; i<ZNET_MAX_SERVER_SOCKETS; i++)
		sockets[i] = 0;
}

ZNetServer::~ZNetServer()
{
	delete[] peers;

	for(uint32_t i=0; i<ZNET_MAX_SERVER_SOCKETS; i++)
	{
		if(sockets[i] != 0)
			SST_Net_Close(sockets[i]);
	}
}

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

bool ZNetServer::Initialize(uint32_t _maxClients, uint32_t _channelCount)
{
	SST_OS_DebugAssert(_maxClients > 0, "Peer count must be at least one");
	SST_OS_DebugAssert(_channelCount > 0, "Channel count must be at least one");

	//Allocate peer classes
	peers = new(std::nothrow) ZNetPeer[_maxClients];
	if(peers == NULL)
		return false;

	this->maxClients = _maxClients;
	this->channelCount = _channelCount;
	
	return true;
}

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

bool ZNetServer::AddSocket(SST_Socket s)
{
	if(s == 0)
		return false;

	//Scan for empty/already existing
	for(uint32_t i=0; i<ZNET_MAX_SERVER_SOCKETS; i++)
	{
		//Already added?
		if(sockets[i] == s)
			return true;
		else if(sockets[i] == 0) //Empty slot?
		{
			//Enable non-blocking mode
			//TODO: once libsst-net support socket sets, use those and update API to support wait times
			if(SST_Net_SetNonblock(s, 1) != SSTNETRESULT_SUCCESS)
				return false;

			sockets[i] = s;
			return true;
		}
	}

	return false;
}

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

int ZNetServer::Update()
{
	bool hitError = false;
	
	uint64_t now = SST_OS_GetMilliTime();

	//Update bandwidth allocation
	outBW.Update(now);

	//Check for packets from each socket
	for(size_t i=0; i<ZNET_MAX_SERVER_SOCKETS; i++)
	{
		SST_NetAddress sender;
		size_t nrRecv;
		uint8_t data[9000];	//On IP networks, jumbo packet size are canonically 9000 bytes.

		//Empty socket slot
		if(sockets[i] == 0)
			continue;

		//Empty network packet queue
		SST_NetResult res;
		do
		{
			res = SST_Net_RecvFrom(sockets[i], data, sizeof(data), 0, &sender, &nrRecv);

			//It is common that no data is available. In this case simply go to the next socket
			if(res == SSTNETRESULT_WOULDBLOCK)
				break;
			if(res == SSTNETRESULT_SUCCESS)
				this->HandlePacket(sockets[i], &sender, data, (uint32_t)nrRecv);
			else if(res == SSTNETRESULT_WOULDBLOCK)
				break;
			else
				hitError = true;

		} while(res == SSTNETRESULT_SUCCESS);

	}

	//TODO: kind of hacky, refactor. should be able to share a lot of code with client
	now = SST_OS_GetMilliTime(); //since we could have processed a lot of data, update timestamp
	for(uint32_t i=0; i<maxClients; i++)
	{
		ZNetPeer* peer = &peers[i];
		if(peer->GetState() != STATE_UNCONNECTED)
		{

			if(!this->SendToPeer(peer))
				hitError = true;

			//printf("Peer %p has %u packets queued\n"
			peer->SendAcksForAllChannels();
			peer->ProcessLocalAcks();
		}
	}



	if(hitError)
		return -1;

	return (HasEvent() ? 1 : 0);
}

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

void ZNetServer::SendPacket(ZNetPacket* packet, ZNetPeer* peer, uint8_t channelId)
{
	SST_OS_DebugAssert(packet != NULL, "Cannot send NULL packet");
	SST_OS_DebugAssert(peer != NULL, "Cannot send to NULL peer");
	SST_OS_DebugAssert(peer >= this->peers && peer <= &this->peers[maxClients], "Peer pointer isn't part of this group!");

	ZNetPacketChannel* channel = peer->GetPacketChannel(channelId); //GetPacketChannel() does assertion checks for valid channels
	channel->QueueForSending(packet, ZNETCMD_DATA);

}

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

void ZNetServer::BroadcastPacket(ZNetPacket* packet, uint8_t channelId)
{
	SST_OS_DebugAssert(packet != NULL, "Cannot send NULL packet");

	for(uint32_t i=0; i<maxClients; i++)
	{
		if(peers[i].GetState() == STATE_CONNECTED)
		{
			peers[i].GetPacketChannel(channelId)->QueueForSending(packet, ZNETCMD_DATA);
		}
	}

}

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

void ZNetServer::HandlePacket(SST_Socket s, const SST_NetAddress* addr, const uint8_t* data, uint32_t dataSize)
{
	ZNetPeer* peer = PeerForAddress(addr);
	ZBinaryBufferReader reader(data, dataSize, ZNET_BYTEORDER);

	//basically: assert if peer != NULL then state != unconnected. otherwise, don't test anything
	SST_OS_DebugAssert(peer != NULL ? (peer->GetState() != STATE_UNCONNECTED) : true, "Peer state is invalid");
	

	//printf("Handling packet of %u length", dataSize);

	//Valid packet header?
	if(!ZNetPrivate::ParseWirePacketHeader(&reader))
	{
		printf("...but it is invalid\n");
		return;
	}
	//printf("\n");


	ZNetPrivate::ZNetMessageContainer container;

	int retval;
	uint64_t now = SST_OS_GetMilliTime();
	bool wasValid = true;
	do 
	{
		retval = ZNetPrivate::ReadNextMessage(&reader, &container);
		if(retval > 0)
		{
			switch(container.command)
			{
				//==============================================================
				//Data received
				case ZNETCMD_DATA:
				{
					printf("ZNETCMD_DATA found, ch = %u, length = %u, offset = %u, maxSize = %u\n",
						container.channel, container.parsed.data.length, container.parsed.data.offset,
						container.parsed.data.maxSize);

					if(container.channel < peer->GetNumberChannels())
					{
						ZNetPacketChannel* ch = peer->GetPacketChannel(container.channel);

						ch->QueueData(&container);
					}

					
					break;
				}

				//==============================================================
				//A new client wants to connect.
				case ZNETCMD_CONNECT:
				{
					printf("ZNETCMD_CONNECT, peer = %p\n", peer);
					//No peer yet
					if(peer == NULL)
					{
						peer = FindEmptyPeerSlot();
						printf("Empty peer slot == %p\n", peer);
						if(peer != NULL)
						{
							uint32_t clientFilterUserData;

							//Any callback function?
							if(this->connectCallback)
								clientFilterUserData = connectCallback(addr, container.parsed.connect.userdata, callbackParam);
							else
								clientFilterUserData = 0; //default to success

							//If the client passed the filter
							if(clientFilterUserData == 0)
							{
								printf("New client connected!\n");
								ZNetPacket* packet = this->CreatePacket(NULL, sizeof(uint32_t), 0);
								if(packet != NULL)
								{
									memset(packet->GetData(), 0, sizeof(uint32_t));

									if(peer->Initialize(this, addr, s, this->channelCount))
									{
										printf("Queueing ZNETCMD_CONNRESP");
										peer->GetPacketChannel(ZNET_SYSCHANNEL)->QueueForSending(packet, ZNETCMD_CONNRESP);
										packet->ReleaseReference();

										ZNetEvent ev;

										ev.packet = NULL;
										ev.userdata = container.parsed.connect.userdata;
										ev.remote = peer;
										ev.type = ZNETEVENT_CONNECT;

										AddEvent(&ev);
									}
								}
							}
							else //Did not pass the filter. Directly send a response since we won't be using channels.
								this->TrySendConnResp(s, addr, clientFilterUserData, 0);
						}
						else //Server is full
						{
							//It's non-critical, so if we don't have the BW, ignore it
							this->TrySendConnResp(s, addr, 0, ZNETCMDFLAGS_FULL);
						}
						
					} // else peer is known
					/*
						There is no "else{}" after this. If the peer is already known to us, that means
						we've already queued a ZNETCMD_CONNRESP command in return. Once the remote host
						receives it, they will stop spamming us with CONNECT.
					*/
	
					break;
				} //case CONNECT
				//==============================================================

				//Client sent how far it is. We can safely act on this now because it does not generate any events.
				//ACKs themselves do not utilize sequence numbers (they always have 0)
				case ZNETCMD_ACK:
				{
					printf("ZNETCMD_ACK: CH %u: highestRecv = %u, highestSent = %u\n", container.channel, container.parsed.ack.highestReceived, container.parsed.ack.highestSent);
					//Ensure it is a valid ack
					if(container.channel < peer->GetNumberChannels())
					{
						ZNetPacketChannel* channel = peer->GetPacketChannel(container.channel);
						int32_t currentPing = peer->GetPing();

						//Update ACKs, calculating an adjusted ping
						channel->UpdateRemoteAck(container.parsed.ack.highestReceived, &currentPing);
						peer->SetPing(currentPing);
					}
					else
						wasValid = false;
					break;
				}

				default:
					SST_OS_DebugError("Not yet implemented!");
					break;

			}
		}

	} while(retval > 0);

	//If all was valid, update last received time
	if(wasValid && peer)
		peer->SetLastReceived(now);

}

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

void ZNetServer::TrySendConnResp(SST_Socket s, const SST_NetAddress* addr, uint32_t reasonCode, uint32_t flags)
{
	if(outBW.TryAllocate(ZNetPrivate::WireSizeForSimpleCommand(ZNETCMD_CONNRESP)))
	{
		uint8_t data[128];

		ZBinaryBufferWriter writer(data, sizeof(data), ZNET_BYTEORDER);

		ZNetPrivate::WriteWirePacketHeader(&writer);
		ZNetPrivate::WriteWireMessageHeader(&writer, 0, flags, ZNETCMD_CONNRESP, 0); //always ch0seq0
		writer.WriteU32(reasonCode);

		//Send packet
		ZNetPrivate::SendAll(s, addr, data, (uint32_t)writer.GetOffset());
	}
}

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

ZNetPeer* ZNetServer::PeerForAddress(const SST_NetAddress* addr) const
{
	//TODO: At some point (maxClients > K), a hashtable would be faster. Unfortunately, ZHashMap does not have an allocation failure policy

	for(uint32_t i=0; i<maxClients; i++)
	{
		ZNetPeer* peer = &peers[i];

		if(peer->GetState() != STATE_UNCONNECTED)
		{
			if(SST_Net_AddressCompare(peer->GetNetAddress(), addr) == 0)
				return peer;
		}
	}

	//Does not match anyone
	return NULL;
}

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

ZNetPeer* ZNetServer::FindEmptyPeerSlot()
{
	for(uint32_t i=0; i<maxClients; i++)
	{
		if(peers[i].GetState() == STATE_UNCONNECTED)
			return &peers[i];
	}

	return NULL;
}