#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;
}
