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

	Purpose:

	ZNetClient -- extends ZNetHost to provide a client

	License:
	
	Copyright 2013, 762 Studios
*/

#include <ZNet/ZNetClient.hpp>
#include <ZNet/ZNetPacket.hpp>
#include <SST/SST_Assert.h>
#include <SST/SST_Endian.h>
#include <SST/SST_Time.h>
#include "ZNetPrivate.hpp"

#include <stdio.h> //HACKHACK printf

ZNetClient::ZNetClient()
{
	connectedFlag = false;
}

ZNetClient::~ZNetClient()
{

}

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

int ZNetClient::Update()
{
	bool hitError = false;
	uint8_t data[9000];

	//Only update if we've got a server
	if(server.GetState() != STATE_UNCONNECTED)
	{
		SST_Socket s = server.GetSocket();
		SST_NetResult res;
		do
		{
			SST_NetAddress sender;
			size_t nrReceived;

			res = SST_Net_RecvFrom(s, data, sizeof(data), 0, &sender, &nrReceived);

			if(res == SSTNETRESULT_SUCCESS)
			{
				//OK, so is the packet from the server? If not, ignore it
				if(SST_Net_AddressCompare(&sender, server.GetNetAddress()) != 0)
					continue;

				HandlePacket(data, (uint32_t)nrReceived);
			}
			else if(res == SSTNETRESULT_WOULDBLOCK)
				break;
			else
				hitError = true;

		} while(res == SSTNETRESULT_SUCCESS);
	}
	static const char* str[] = {"UNCONNECTED", "HANDSHAKE", "CONNECTED", "SERVING"};
	printf("MY STATE: %s / %u ack\n", str[server.GetState()], server.GetPacketChannel(0)->GetLocalAck());


	//Send all channel data to server
	if(!this->SendToPeer(&server))
		hitError = true;

	//Send acks
	server.SendAcksForAllChannels();
	server.ProcessLocalAcks();

	if(hitError)
		return -1;

	return HasEvent() ? 1 : 0;
}

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

bool ZNetClient::Connect(SST_Socket s, SST_NetAddress* addr, uint32_t nrChannels, uint32_t userData)
{
	SST_OS_DebugAssert(s != 0, "Invalid socket");
	SST_OS_DebugAssert(addr != NULL, "Invalid address");
	SST_OS_DebugAssert(server.GetState() == STATE_UNCONNECTED, "Must be unconnected, use Reset() first");

	if(nrChannels == 0 || nrChannels > UINT8_MAX)
		return false;


	if(!server.Initialize(this, addr, s, nrChannels))
		return false;

	if(SST_Net_SetNonblock(s, 1) != SSTNETRESULT_SUCCESS)
		return false;

	//CONNECT always occurs on ZNET_SYSCHANNEL
	ZNetPacketChannel* channel = server.GetPacketChannel(ZNET_SYSCHANNEL);

	userData = ZNetPrivate::ZNetByteOrder32(userData);

	//Create a packet, copying the userdata
	ZNetPacket* packet = this->CreatePacket((void*)&userData, sizeof(userData), 0);
	if(packet == NULL)
		return false;

	if(!channel->QueueForSending(packet, ZNETCMD_CONNECT))
	{
		packet->ReleaseReference();
		return false;
	}

	//We're done with this
	packet->ReleaseReference();

	//Note that we're trying!
	server.SetState(STATE_HANDSHAKE);
	return true;
}

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

void ZNetClient::HandlePacket(const uint8_t* data, uint32_t dataSize)
{
	ZBinaryBufferReader reader(data, dataSize, ZNET_BYTEORDER);
	static int COUNT = 0;

	printf("(%u) Handling packet of %u length", ++COUNT, dataSize);
	//Valid packet header?
	if(!ZNetPrivate::ParseWirePacketHeader(&reader))
	{
		printf("...but it is invalid\n");
		return;
	}

	ZNetPrivate::ZNetMessageContainer container;

	int retval;
	uint64_t now = SST_OS_GetMilliTime();
	bool wasValid = true;
	do 
	{
		retval = ZNetPrivate::ReadNextMessage(&reader, &container);
		printf("ReadNextMessage(): %d\n", retval);
		if(retval > 0)
		{
			switch(container.command)
			{
				//Server 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 < server.GetNumberChannels())
					{
						ZNetPacketChannel* channel = server.GetPacketChannel(container.channel);
						int32_t currentPing = server.GetPing();

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

				//Server sent data.
				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 < server.GetNumberChannels())
					{
						ZNetPacketChannel* ch = server.GetPacketChannel(container.channel);

						ch->QueueData(&container);
					}
					else
						wasValid = false;


					break;
				}

				//Server sent a connection response
				case ZNETCMD_CONNRESP:
				{
					printf("ZNETCMD_CONNRESP: code = %u\n", container.parsed.connect.userdata);
					if(container.channel == ZNET_SYSCHANNEL)
					{
						const uint32_t reasonCode = container.parsed.connect.userdata; //slightly less typing

						//TODO: flags? can have server full!!!
						

						//Accepted?
						if(reasonCode == 0)
						{
							if(server.GetState() == STATE_HANDSHAKE)
							{
								//OK, we're good
								server.SetState(STATE_CONNECTED);

								ZNetEvent ev;
								ev.packet = NULL;
								ev.userdata = reasonCode;
								ev.remote = &server;
								ev.type = ZNETEVENT_CONNECT;
								this->AddEvent(&ev);
							}

							//We got it, mark that we did.
							server.GetPacketChannel(ZNET_SYSCHANNEL)->UpdateLocalAck(container.sequence);
						}
						else //Not accepted. In this case, the server doesn't consider us a client, so no acks required.
						{
							ZNetEvent ev;

							ev.packet = NULL;
							ev.userdata = reasonCode;
							ev.remote = &server;
							ev.type = ZNETEVENT_DISCONNECT;
							
							this->AddEvent(&ev);

							//TODO: clear out CONNECT command

						}
					} // if on system channel
					else //else not accepted.
						wasValid = false;
					break;
				}

				case ZNETCMD_DISCONNECT:
					SST_OS_DebugError("Not yet implemented!");
					break;

				//Ignore this command, it doesn't make any sense as a client
				case ZNETCMD_CONNECT:
				default:
					wasValid = false;
					break;

			}
		}

	} while(retval > 0);

	//If all was valid, update last received time
	if(wasValid)
		server.SetLastReceived(now);
	



}