#include <all_far.h>


#include "Int.h"

static DWORD tmWait = 0;

int Connection::nb_waitstate(SOCKET *peer, int state,DWORD tmW)
{
	fd_set           _readfds;
	fd_set           _writefds;
	fd_set           _excptfds;
	fd_set           *readfds,*writefds,*excptfds;
	timeval          timeout;
	DWORD        tmB,tmE,tm,tmCheck;
	int              res;
	double           tmDiff;
	BOOL             rc = FALSE;
	String           buff;
	HANDLE           hStart = NULL;
	FPPeriod         hTm;

	if(*peer == INVALID_SOCKET)
		goto Exit;

	if(tmW != MAX_DWORD)
		hTm.Create(tmW);

	timeout.tv_sec  = 0;
	timeout.tv_usec = 10000;
	FP_PeriodReset(hIdle);
	GET_TIME(tmB);
	hStart = Opt.IdleStartPeriod ? FP_PeriodCreate(Opt.IdleStartPeriod) : NULL;

	while(true)
	{
//Check full timeout
		GET_TIME(tmE);
		tmDiff = CMP_TIME(tmE,tmB);

		if(tmDiff > Opt.WaitTimeout)
		{
			Log(("global timeout finished %08X-%08X=%3.3f > %d",tmE,tmB,tmDiff,Opt.WaitTimeout));
			ErrorCode = ERROR_TIMEOUT;
			goto Exit;
		}

		//Empty
		FD_ZERO(&_readfds);
		FD_ZERO(&_writefds);
		FD_ZERO(&_excptfds);
		FD_SET(*peer, &_readfds);
		FD_SET(*peer, &_writefds);
		FD_SET(*peer, &_excptfds);

		if(state == ws_connect || state == ws_write)
		{
			readfds  = NULL;
			writefds = &_writefds;
			excptfds = &_excptfds;
		}
		else if(state == ws_read || state == ws_accept)
		{
			readfds  = &_readfds;
			writefds = NULL;
			excptfds = &_excptfds;
		}
		else if(state == ws_error)
		{
			readfds  = NULL;
			writefds = NULL;
			excptfds = &_excptfds;
		}
		else
		{
			readfds  = &_readfds;
			writefds = &_writefds;
			excptfds = &_excptfds;
		}

//Select
		// in windows nfds ignored
		res = select( (*peer)+1, readfds, writefds, excptfds, &timeout);
//Idle
		static bool inIdle = false;
		static DWORD idleB;

		if(Opt.WaitIdle && Opt.WaitCounter &&
		        (!inIdle || CMP_TIME(tmE,idleB) > (1.0 / Opt.WaitCounter)))
		{
			WINPORT(Sleep)(Opt.WaitIdle);
			idleB  = tmE;
			inIdle = true;
		}

//DISPATCH SELECT
		if(res == 0)
		{
			if(hTm.Handle && hTm.End())
			{
				rc = RPL_TIMEOUT;
				goto Exit;
			}

			if(Opt.ShowIdle && hIdle && FP_PeriodEnd(hIdle) &&
			        (!hStart || FP_PeriodEnd(hStart)))
			{
				if(hStart)
				{
					FP_PeriodDestroy(hStart);
					hStart = NULL;
				}

				if(brk_flag)
					buff.printf("%s (%d%s) ",
					            FP_GetMsg(MWaiting), (int)tmDiff, FP_GetMsg(MSeconds));
				else
					buff.printf("%s (%.1f%s): %2d%% ",
					            FP_GetMsg(MWaiting), tmDiff, FP_GetMsg(MSeconds), (int)(tmDiff*100/Opt.WaitTimeout)+1);

				WINPORT(SetLastError)(ERROR_SUCCESS);
				IdleMessage(buff.c_str(),Opt.IdleColor);
			}
		}
		else

//Write
			if(writefds && FD_ISSET(*peer, writefds))
			{
				rc = TRUE;
				goto Exit;
			}
			else

//Read
				if(readfds && FD_ISSET(*peer, readfds))
				{
					rc = TRUE;
					goto Exit;
				}
				else

//Error
					if(res < 0 || (res != 0 && FD_ISSET(*peer,excptfds)))
					{
						socklen_t sz = sizeof(SocketError);
						getsockopt(*peer,SOL_SOCKET,SO_ERROR,(char FAR*)&SocketError,&sz);
						Log(("!select: res=%d err: %s",res,GetSocketErrorSTR(SocketError)));
						goto Exit;
					}

//Check cancel
		GET_TIME(tm);
		tmCheck = tm;

		if(CMP_TIME(tm,tmWait) > 0.3 /*&& CMP_TIME(tmCheck,tmB) > 1*/)
		{
			/*
			      BOOL mayAsk = FtpSetBreakable(this,-1) ||                                  // isBreakable
			                    ( (data_peer != INVALID_SOCKET && *peer == data_peer) ||   // Queryed data connection
			                      (data_peer == INVALID_SOCKET) );                         // Data conn does not exist
			*/
			BOOL mayAsk = FtpSetBreakable(this,-1) &&
			              (CurrentState != fcsProcessFile || CMP_TIME(tmCheck,tmB) > 3);

			if(mayAsk)
			{
				tmWait = tm;

				if(CheckForEsc(data_peer == INVALID_SOCKET))
				{
					brk_flag  = TRUE;
					ErrorCode = ERROR_CANCELLED;
					Log(("ESC: Conn breaked by user"));
					goto Exit;
				}
			}
		}
	}/*waiting while(true)*/

Exit:

	if(hStart)
		FP_PeriodDestroy(hStart);

	return rc;
}

BOOL Connection::nb_connect(SOCKET *peer, struct sockaddr FAR* addr, int addrlen)
{
	if(connect(*peer, addr, addrlen) == 0)
		return TRUE;

	int err = errno;
	if(IS_SOCKET_NONBLOCKING_ERR(err)) {
		return nb_waitstate(peer, ws_connect);
	}else
		return FALSE;
}

int Connection::nb_recv(SOCKET *peer, LPVOID buf, int len, int flags,DWORD tm)
{
	int rc = nb_waitstate(peer, ws_read, tm);

	if(rc == RPL_TIMEOUT)
		return RPL_TIMEOUT;
	else if(rc != 0)
		return recv(*peer, (char*)buf, len, flags);
	else
	{
		Log(("!nb_waitstate"));
		return SOCKET_ERROR;
	}
}

int Connection::nb_send(SOCKET *peer, LPCVOID buf, int len, int flags)
{
	int result = 0;

	while(result == 0)
	{
		result = send(*peer, (char*)buf, len, flags);

		if(result == SOCKET_ERROR && IS_SOCKET_NONBLOCKING_ERR(errno))
		{
#if 0
			fd_set  _readfds;
			timeval timeout;

			do
			{
				FD_ZERO(&_readfds);
				FD_SET(*peer, &_readfds);
				timeout.tv_sec = 0;
				timeout.tv_usec = 10000; /* 10 ms */
				result = select((*peer) + 1, &_readfds, 0, 0, &timeout);

				if(result == 1)
				{
					if(!nb_waitstate(peer, ws_write))
						return SOCKET_ERROR;

					break;
				}
				else if(result == 0)
					continue;
				else
					return SOCKET_ERROR;
			}
			while(true);

#else

			if(!nb_waitstate(peer, ws_write))
				return SOCKET_ERROR;

#endif
			result = 0;
		}
	}

	return result;
}
