/*******************************************************************************
 * libretroshare/src/dht: connectstatebox.cc                                   *
 *                                                                             *
 * libretroshare: retroshare core library                                      *
 *                                                                             *
 * Copyright 2011-2011 by Robert Fernie <drbob@lunamutt.com>                   *
 *                                                                             *
 * This program is free software: you can redistribute it and/or modify        *
 * it under the terms of the GNU Lesser General Public License as              *
 * published by the Free Software Foundation, either version 3 of the          *
 * License, or (at your option) any later version.                             *
 *                                                                             *
 * This program is distributed in the hope that it will be useful,             *
 * but WITHOUT ANY WARRANTY; without even the implied warranty of              *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the                *
 * GNU Lesser General Public License for more details.                         *
 *                                                                             *
 * You should have received a copy of the GNU Lesser General Public License    *
 * along with this program. If not, see <https://www.gnu.org/licenses/>.       *
 *                                                                             *
 *******************************************************************************/

#include "dht/connectstatebox.h"

#include "util/rsrandom.h"
#include "util/rsstring.h"

#include <iostream>

/**
 *
 * #define TESTING_PERIODS	1
 * #define DEBUG_CONNECTBOX	1
 *
 **/


/* Have made the PROXY Attempts + MAX_TIME much larger, 
 * have have potential for this to take a while.
 */

#ifdef TESTING_PERIODS
	#define FAILED_WAIT_TIME	(1800)  // 5 minutes.
	#define TCP_WAIT_TIME		(10)   // 1/6 minutes.
	#define DIRECT_MAX_WAIT_TIME	(30)   // 1/6 minutes.
	#define PROXY_BASE_WAIT_TIME	(10)   // 30 // 1/6 minutes.
	#define PROXY_MAX_WAIT_TIME	(30)  // 120 // 1/6 minutes.
	#define RELAY_MAX_WAIT_TIME	(30)   // 1/6 minutes.
	#define REVERSE_WAIT_TIME	(30)   // 1/2 minutes.

	#define MAX_DIRECT_ATTEMPTS	(3)
	#define MAX_PROXY_ATTEMPTS	(3)
	#define MAX_RELAY_ATTEMPTS	(3)

	#define MAX_DIRECT_FAILED_ATTEMPTS	(1)
	#define MAX_PROXY_FAILED_ATTEMPTS	(2)
	#define MAX_RELAY_FAILED_ATTEMPTS	(2)
#else
	#define FAILED_WAIT_TIME	(1800) // 30 minutes.
	#define TCP_WAIT_TIME		(60)   // 1 minutes.
	#define DIRECT_MAX_WAIT_TIME	(60)   // 1 minutes.

	#define PROXY_BASE_WAIT_TIME	(30)   // 1/2 minutes.
	#define PROXY_MAX_WAIT_TIME	(120)   // 1 minutes.

	#define RELAY_MAX_WAIT_TIME	(60)   // 1 minutes.
	#define REVERSE_WAIT_TIME	(300)  // 5 minutes.

	#define MAX_DIRECT_ATTEMPTS	(5)
	#define MAX_PROXY_ATTEMPTS	(10)
	#define MAX_RELAY_ATTEMPTS	(5)

	#define MAX_DIRECT_FAILED_ATTEMPTS	(3)
	#define MAX_PROXY_FAILED_ATTEMPTS	(3)
	#define MAX_RELAY_FAILED_ATTEMPTS	(3)

#endif

PeerConnectStateBox::PeerConnectStateBox()
{
	//mPeerId = id;
	rstime_t now = time(NULL);
	mState = CSB_START;
	mNetState = CSB_NETSTATE_UNKNOWN;
	mStateTS = now;
	mNoAttempts = 0;
        mNoFailedAttempts = 0;
        mNextAttemptTS = now;
        mAttemptLength = 0;
	mProxyPortFlags = 0;
	mProxyPortChoice = false;
	mProxyPortTS = 0;


}


std::string NetStateAsString(uint32_t netstate)
{
	std::string str;
	switch(netstate)
	{
		case CSB_NETSTATE_FORWARD:
			str = "Forwarded";
			break;
			
		case CSB_NETSTATE_STABLENAT:
			str = "StableNat";
			break;
			
		case CSB_NETSTATE_EXCLUSIVENAT:
			str = "ExclusiveNat";
			break;
			
		case CSB_NETSTATE_FIREWALLED:
			str = "Firewalled";
			break;
		default:
			str = "Unknown NetState";
			break;
	}
	return str;
}

std::string StateAsString(uint32_t state)
{
	std::string str;
	switch(state)
	{
		case CSB_START:
			str = "Start:";
			break;

		case CSB_TCP_WAIT:
			str = "TCP Wait:";
			break;
			
		case CSB_DIRECT_ATTEMPT:
			str = "Direct Attempt:";
			break;
			
		case CSB_DIRECT_WAIT:
			str = "Direct Wait:";
			break;
			
		case CSB_PROXY_ATTEMPT:
			str = "Proxy Attempt:";
			break;

		case CSB_PROXY_WAIT:
			str = "Proxy Wait:";
			break;

		case CSB_RELAY_ATTEMPT:
			str = "Relay Attempt:";
			break;

		case CSB_RELAY_WAIT:
			str = "Relay Wait:";
			break;

		case CSB_REVERSE_WAIT:
			str = "Reverse Wait:";
			break;

		case CSB_FAILED_WAIT:
			str = "Failed Wait:";
			break;

		case CSB_CONNECTED:
			str = "Connected:";
			break;

		default:
			str = "Unknown State:";
			break;
	}

	return str;
}


std::string UpdateAsString(uint32_t update)
{
	std::string str;
	switch(update)
	{
		case CSB_UPDATE_NONE:
			str = "none";
			break;
			
		case CSB_UPDATE_CONNECTED:
			str = "Connected";
			break;
			
		case CSB_UPDATE_DISCONNECTED:
			str = "Disconnected";
			break;

		case CSB_UPDATE_AUTH_DENIED:
			str = "Auth Denied";
			break;

		case CSB_UPDATE_FAILED_ATTEMPT:
			str = "Failed Attempt:";
			break;

		case CSB_UPDATE_MODE_UNAVAILABLE:
			str = "Mode Unavailable:";
			break;

		default:
			str = "Unknown Update";
			break;
	}
	return str;
}

void PeerConnectStateBox::errorMsg(std::ostream &out, std::string msg, uint32_t updateParam)
{
	out << "PeerConnectStateBox::ERROR " << msg;
	out << " NetState: " << NetStateAsString(mNetState);
	out << " State: " << StateAsString(mState);
	out << " Update: " << UpdateAsString(updateParam);
	out << " for peer: " << mPeerId;
	out << std::endl;
}


void PeerConnectStateBox::stateMsg(std::ostream &out, std::string msg, uint32_t updateParam)
{
	out << "PeerConnectStateBox::MSG " << msg;
	out << " NetState: " << NetStateAsString(mNetState);
	out << " State: " << StateAsString(mState);
	out << " Update: " << UpdateAsString(updateParam);
	out << " for peer: " << mPeerId;
	out << std::endl;
}

std::string PeerConnectStateBox::connectState() const
{
	std::string str = StateAsString(mState);
	rstime_t now = time(NULL);
	std::string out;
	rs_sprintf(out, "%s(%lu/%lu) for %ld secs", str.c_str(), mNoAttempts, mNoFailedAttempts, now - mStateTS);
	if ( (mState == CSB_CONNECTED) || (mState == CSB_DIRECT_ATTEMPT) ||
		(mState == CSB_PROXY_ATTEMPT) || (mState == CSB_RELAY_ATTEMPT) ||
		(mState == CSB_FAILED_WAIT) )
	{
		rs_sprintf_append(out, " Last Attempt: %ld", mAttemptLength);
	}
	else
	{
		rs_sprintf_append(out, " LA: %ld", mAttemptLength);
		rs_sprintf_append(out, " NextAttempt: %ld", mNextAttemptTS - now);
	}

	return out;
}


uint32_t convertNetStateToInternal(RsNetworkMode netmode, RsNatHoleMode nathole, RsNatTypeMode nattype)
{
	uint32_t connNet = CSB_NETSTATE_UNKNOWN;
		
	if (netmode == RsNetworkMode::EXTERNALIP)
	{
		connNet = CSB_NETSTATE_FORWARD;
	}
	else if ((nathole != RsNatHoleMode::UNKNOWN) && (nathole != RsNatHoleMode::NONE))
	{
		connNet = CSB_NETSTATE_FORWARD;
	}
	else if (netmode == RsNetworkMode::BEHINDNAT)
	{
		if ((nattype == RsNatTypeMode::RESTRICTED_CONE) ||
		    (nattype == RsNatTypeMode::FULL_CONE))
		{
			connNet = CSB_NETSTATE_STABLENAT;
		}
		else if (nattype == RsNatTypeMode::DETERM_SYM)
		{
			connNet = CSB_NETSTATE_EXCLUSIVENAT; 
		}
		else
		{
			connNet = CSB_NETSTATE_FIREWALLED;
		}
	}
	return connNet;
}

bool shouldUseProxyPortInternal(uint32_t netstate)
{
	if (netstate == CSB_NETSTATE_FORWARD)
	{
		return false;
	}
	return true;
}

bool PeerConnectStateBox::shouldUseProxyPort(RsNetworkMode netmode, RsNatHoleMode nathole, RsNatTypeMode nattype)
{
	uint32_t netstate = convertNetStateToInternal(netmode, nathole, nattype);
	return shouldUseProxyPortInternal(netstate);
}

uint32_t PeerConnectStateBox::calcNetState(RsNetworkMode netmode, RsNatHoleMode nathole, RsNatTypeMode nattype)
{
	uint32_t netstate = convertNetStateToInternal(netmode, nathole, nattype);
	return netstate;
}


uint32_t PeerConnectStateBox::connectCb(uint32_t cbtype, RsNetworkMode netmode, RsNatHoleMode nathole, RsNatTypeMode nattype)
{
	uint32_t netstate = convertNetStateToInternal(netmode, nathole, nattype);

#ifdef 	DEBUG_CONNECTBOX
	std::cerr << "PeerConnectStateBox::connectCb(";
	if (cbtype == CSB_CONNECT_DIRECT)
	{
		std::cerr << "DIRECT";
	}
	else
	{
		std::cerr << "UNREACHABLE";
	}
	std::cerr << "," << NetStateAsString(netstate) << ")";
	std::cerr << std::endl;
#endif

	if (netstate != mNetState)
	{
#ifdef 	DEBUG_CONNECTBOX
		std::cerr << "PeerConnectStateBox::connectCb() WARNING Changing NetState from: ";
		std::cerr << " from: " << NetStateAsString(mNetState);
		std::cerr << " to: " << NetStateAsString(netstate);
		std::cerr << " for peer: " << mPeerId;
		std::cerr << std::endl;
#endif

		mNetState = netstate;
	}

	if (cbtype == CSB_CONNECT_DIRECT)
	{
		return connectCb_direct();
	}
	else
	{
		return connectCb_unreachable();
	}
}

	
uint32_t PeerConnectStateBox::connectCb_direct()
{
	uint32_t retval = 0;
	rstime_t now = time(NULL);

	switch(mState)
	{

		case CSB_DIRECT_ATTEMPT:
		{
			errorMsg(std::cerr, "mState == DIRECT_ATTEMPT", 0);
			retval = CSB_ACTION_WAIT;
		}
			break;

		case CSB_PROXY_ATTEMPT:
		{
			errorMsg(std::cerr, "mState == PROXY_ATTEMPT", 0);
			retval = CSB_ACTION_WAIT;
		}
			break;

		case CSB_RELAY_ATTEMPT:
		{
			errorMsg(std::cerr, "mState == RELAY_ATTEMPT", 0);
			retval = CSB_ACTION_WAIT;
		}
			break;

		case CSB_FAILED_WAIT:
		{
			/* if too soon */
			//if (now - mStateTS < FAILED_WAIT_TIME) 
			if (mNextAttemptTS > now)
			{
				/* same state */
				retval = CSB_ACTION_WAIT;
				break;
			}
		}	/* FALLTHROUGH TO START CASE */
		/* fallthrough */
		default:
		case CSB_REVERSE_WAIT:
		case CSB_PROXY_WAIT:
		case CSB_RELAY_WAIT:
		{
			if (mState != CSB_FAILED_WAIT)
			{
				/* ERROR */
				errorMsg(std::cerr, "mState != FAILED_WAIT", 0);

			}

		}	/* FALLTHROUGH TO START CASE */
		/* fallthrough */
		case CSB_START:
		{
			/* starting up the connection */
			mState = CSB_TCP_WAIT;
			retval = CSB_ACTION_TCP_CONN;
			mStateTS = now;
			mNoAttempts = 0;
			mNoFailedAttempts = 0;
			mNextAttemptTS = now + TCP_WAIT_TIME;

		}
			break;
		case CSB_TCP_WAIT:
		{
			/* if too soon */
			//if (now - mStateTS < TCP_WAIT_TIME)
			if (mNextAttemptTS > now)
			{
				/* same state */
				retval = CSB_ACTION_WAIT;
			}
			else
			{
				/* try again */
				mState = CSB_DIRECT_ATTEMPT;
				retval = CSB_ACTION_DIRECT_CONN | CSB_ACTION_DHT_PORT;
				mStateTS = now;
				mNoAttempts = 0;
				mNoFailedAttempts = 0;
			}
		}
			break;

		case CSB_DIRECT_WAIT:
		{
			/* if too soon */
			//if (now - mStateTS < DIRECT_WAIT_TIME)
			if (mNextAttemptTS > now)
			{
				/* same state */
				retval = CSB_ACTION_WAIT;
			}
			else if ((mNoAttempts >= MAX_DIRECT_ATTEMPTS) ||
				 (mNoFailedAttempts >= MAX_DIRECT_FAILED_ATTEMPTS))
			{
				/* if too many attempts */
				/* no RELAY attempt => FAILED_WAIT */
				mState = CSB_FAILED_WAIT;
				retval = CSB_ACTION_WAIT;
				mStateTS = now;
				mNoAttempts = 0;
				mNoFailedAttempts = 0;
				mNextAttemptTS = now + FAILED_WAIT_TIME;
			}
			else
			{
				/* try again */
				mState = CSB_DIRECT_ATTEMPT;
				retval = CSB_ACTION_DIRECT_CONN | CSB_ACTION_DHT_PORT;
				mStateTS = now;
				mNoAttempts++;
			}
		}
			break;

		case CSB_CONNECTED:
		{
				retval = CSB_ACTION_WAIT;
		}
			break;
	}

	return retval;

}


uint32_t PeerConnectStateBox::connectCb_unreachable()
{
	uint32_t retval = 0;

	uint32_t proxyPortMode = CSB_ACTION_PROXY_PORT;
	if (!shouldUseProxyPortInternal(mNetState))
	{
		proxyPortMode = CSB_ACTION_DHT_PORT;
	}

	rstime_t now = time(NULL);

	switch(mState)
	{
		case CSB_DIRECT_ATTEMPT:
		{
			errorMsg(std::cerr, "mState == DIRECT_ATTEMPT", 0);
			retval = CSB_ACTION_WAIT;
		}
			break;

		case CSB_PROXY_ATTEMPT:
		{
			errorMsg(std::cerr, "mState == PROXY_ATTEMPT", 0);
			retval = CSB_ACTION_WAIT;
		}
			break;

		case CSB_RELAY_ATTEMPT:
		{
			errorMsg(std::cerr, "mState == RELAY_ATTEMPT", 0);
			retval = CSB_ACTION_WAIT;
		}
			break;

		case CSB_FAILED_WAIT:
		{
			/* if too soon */
			//if (now - mStateTS < FAILED_WAIT_TIME)
			if (mNextAttemptTS > now)
			{
				/* same state */
#ifdef 	DEBUG_CONNECTBOX
				stateMsg(std::cerr, "too soon, no action", 0);
#endif
				retval = CSB_ACTION_WAIT;
				break;
			}
		}	/* FALLTHROUGH TO START CASE */
		/* fallthrough */
		default:
		case CSB_DIRECT_WAIT:
		{
			if (mState != CSB_FAILED_WAIT)
			{
				/* ERROR */
				errorMsg(std::cerr, "mState != FAILED_WAIT", 0);

			}

		}	/* FALLTHROUGH TO START CASE */
		/* fallthrough */
		case CSB_START:
		{
			/* starting up the connection */
			mState = CSB_TCP_WAIT;
			retval = CSB_ACTION_WAIT;  /* NO POINT TRYING A TCP_CONN */
			mStateTS = now;
			mNoAttempts = 0;
			mNoFailedAttempts = 0;
			mNextAttemptTS = now + TCP_WAIT_TIME;

		}
			break;
		case CSB_TCP_WAIT:
		{
			/* if too soon */
			if (mNextAttemptTS > now)
			{
				/* same state */
				retval = CSB_ACTION_WAIT;
			}
			else
			{
				/* starting up the connection */
				if (mState != CSB_NETSTATE_FIREWALLED)
				{
#ifdef 	DEBUG_CONNECTBOX
					stateMsg(std::cerr, "not Firewalled => PROXY_ATTEMPT", 0);
#endif
					mState = CSB_PROXY_ATTEMPT;
					retval = CSB_ACTION_PROXY_CONN | proxyPortMode;
				}
				else
				{
#ifdef 	DEBUG_CONNECTBOX
					stateMsg(std::cerr, "Firewalled => RELAY_ATTEMPT", 0);
#endif
					mState = CSB_RELAY_ATTEMPT;
					retval = CSB_ACTION_RELAY_CONN | CSB_ACTION_DHT_PORT;

				}

				mStateTS = now;
				mNoAttempts = 0;
				mNoFailedAttempts = 0;
			}

		}
			break;
		case CSB_PROXY_WAIT:
		{
			/* if too soon */
			if (mNextAttemptTS > now)
			{
				/* same state */
#ifdef 	DEBUG_CONNECTBOX
				stateMsg(std::cerr, "too soon, no action", 0);
#endif
				retval = CSB_ACTION_WAIT;
			}
			else if ((mNoAttempts >= MAX_PROXY_ATTEMPTS) ||
				 (mNoFailedAttempts >= MAX_PROXY_FAILED_ATTEMPTS))
			{
				/* if too many attempts */
				/* switch to RELAY attempt */
#ifdef 	DEBUG_CONNECTBOX
				stateMsg(std::cerr, "too many PROXY => RELAY_ATTEMPT", 0);
#endif
				mState = CSB_RELAY_ATTEMPT;
				retval = CSB_ACTION_RELAY_CONN | CSB_ACTION_DHT_PORT;
				mStateTS = now;
				mNoAttempts = 0;
				mNoFailedAttempts = 0;
			}
			else
			{
				/* try again */
#ifdef 	DEBUG_CONNECTBOX
				stateMsg(std::cerr, "PROXY_ATTEMPT try again", 0);
#endif
				mState = CSB_PROXY_ATTEMPT;
				retval = CSB_ACTION_PROXY_CONN | proxyPortMode;
				mStateTS = now;
				mNoAttempts++;
			}
		}
			break;

		case CSB_REVERSE_WAIT:
		{
			/* if too soon */
			//if (now - mStateTS < REVERSE_WAIT_TIME)
			if (mNextAttemptTS > now)
			{
				/* same state */
#ifdef 	DEBUG_CONNECTBOX
				stateMsg(std::cerr, "too soon, no action", 0);
#endif
				retval = CSB_ACTION_WAIT;
			}
			else 
			{
#ifdef 	DEBUG_CONNECTBOX
				stateMsg(std::cerr, "timeout => RELAY_ATTEMPT", 0);
#endif
				/* switch to RELAY attempt */
				mState = CSB_RELAY_ATTEMPT;
				retval = CSB_ACTION_RELAY_CONN | CSB_ACTION_DHT_PORT;
				mStateTS = now;
				mNoAttempts = 0;
				mNoFailedAttempts = 0;
			}
		}
			break;

		case CSB_RELAY_WAIT:
		{
			/* if too soon */
			if (mNextAttemptTS > now)
			{
				/* same state */
#ifdef 	DEBUG_CONNECTBOX
				stateMsg(std::cerr, "too soon, no action", 0);
#endif
				retval = CSB_ACTION_WAIT;
			}
			else if ((mNoAttempts >= MAX_RELAY_ATTEMPTS) ||
				 (mNoFailedAttempts >= MAX_RELAY_FAILED_ATTEMPTS))
			{
				/* if too many attempts */
				/* switch to RELAY attempt */
#ifdef 	DEBUG_CONNECTBOX
				stateMsg(std::cerr, "too many RELAY => FAILED_WAIT", 0);
#endif
				mState = CSB_FAILED_WAIT;
				retval = CSB_ACTION_WAIT;
				mStateTS = now;
				mNoAttempts = 0;
				mNoFailedAttempts = 0;
				mNextAttemptTS = now + FAILED_WAIT_TIME;
			}
			else
			{
				/* try again */
#ifdef 	DEBUG_CONNECTBOX
				stateMsg(std::cerr, "RELAY_ATTEMPT try again", 0);
#endif
				mState = CSB_RELAY_ATTEMPT;
				retval = CSB_ACTION_RELAY_CONN | CSB_ACTION_DHT_PORT;
				mStateTS = now;
				mNoAttempts++;
			}
		}
			break;

		case CSB_CONNECTED:
		{
#ifdef 	DEBUG_CONNECTBOX
				stateMsg(std::cerr, "connected => no action", 0);
#endif
				retval = CSB_ACTION_WAIT;
		}
			break;
	}

	return retval;
}



uint32_t PeerConnectStateBox::updateCb(uint32_t update)
{
	/* The Error Callback doesn't trigger another connection.
	 * but can change the connection state
	 *
	 * Possible Errors:
	 * 1) AUTH DENIED.  (fatal) 
	 * 2) MODE UNAVILABLE 
	 * 3) RETRY ATTEMPT
	 * 4) FAILED ATTEMPT
	 * 5) CONNECTION
	 * 6) DISCONNECTED.
	 *
	 * Fitting these into the states:
		case CSB_START:
			CONNECTION => CSB_CONNECTED
			error if: AUTH DENIED, MODE UNAVAILABLE, FAILED ATTEMPT, RETRY ATTEMPT, DISCONNECTED
		case CSB_CONNECTED:
			CONNECTION => CSB_CONNECTED
			DISCONNECTED => CSB_START
			error if: AUTH DENIED, MODE UNAVAILABLE, FAILED ATTEMPT, RETRY ATTEMPT

		case CSB_FAILED_WAIT:
			CONNECTION => CSB_CONNECTED
			error if: AUTH DENIED, MODE UNAVAILABLE, FAILED ATTEMPT, RETRY ATTEMPT, DISCONNECTED
		case CSB_REVERSE_WAIT:
			CONNECTION => CSB_CONNECTED
			error if: AUTH DENIED, MODE UNAVAILABLE, FAILED ATTEMPT, RETRY ATTEMPT, DISCONNECTED
		case CSB_DIRECT_ATTEMPT:
			CONNECTION => CSB_CONNECTED
			AUTH DENIED => CSB_FAILED_WAIT
			FAILED ATTEMPT => stay here.
			RETRY ATTEMPT => stay here
			MODE UNAVAILABLE - probably error => CSB_FAILED_WAIT
			error if: MODE UNAVAILABLE, DISCONNECTED

		case CSB_PROXY_ATTEMPT:
			CONNECTION => CSB_CONNECTED
			AUTH DENIED => CSB_FAILED_WAIT
			FAILED ATTEMPT => stay here.
			RETRY ATTEMPT => stay here
			MODE_UNAVAILABLE => CSB_REVERSE_WAIT | CSB_RELAY_ATTEMPT
			error if: DISCONNECTED

		case CSB_RELAY_ATTEMPT:
			CONNECTION => CSB_CONNECTED
			AUTH DENIED => CSB_FAILED_WAIT
			FAILED ATTEMPT => stay here.
			RETRY ATTEMPT => stay here
			MODE_UNAVAILABLE => CSB_FAILED_WAIT
			error if: DISCONNECTED

		case CSB_DIRECT_WAIT:
			CONNECTION => CSB_CONNECTED
			error if: AUTH DENIED, MODE UNAVAILABLE, FAILED ATTEMPT, RETRY ATTEMPT, DISCONNECTED
		case CSB_PROXY_WAIT:
			CONNECTION => CSB_CONNECTED
			error if: AUTH_DENIED, MODE UNAVAILABLE, FAILED ATTEMPT, RETRY ATTEMPT, DISCONNECTED
		case CSB_RELAY_ATTEMPT:
			CONNECTION => CSB_CONNECTED
			error if: AUTH_DENIED, MODE UNAVAILABLE, FAILED ATTEMPT, RETRY ATTEMPT, DISCONNECTED
	 */

	/* DO Connect / Disconnect Updates ... very specific! */
	rstime_t now = time(NULL);
	switch(update)
	{
		case CSB_UPDATE_CONNECTED:
		{
			if ((mState == CSB_DIRECT_ATTEMPT) || (mState == CSB_PROXY_ATTEMPT) || (mState == CSB_RELAY_ATTEMPT))
			{
				mAttemptLength = now - mStateTS;
			}
			else
			{
				mAttemptLength = 0;
			}
	
#ifdef 	DEBUG_CONNECTBOX
			stateMsg(std::cerr, "=> CONNECTED", update);
#endif
			mState = CSB_CONNECTED;
			mStateTS = now;

			return 0;
		}
			break;
		case CSB_UPDATE_DISCONNECTED:
		{
			if (mState != CSB_CONNECTED)
			{
				/* ERROR, ignore (as already in disconnected state) */
				errorMsg(std::cerr, "mState != CSB_CONNECTED", update);

			}
			else
			{
#ifdef 	DEBUG_CONNECTBOX
				stateMsg(std::cerr, "=> START", update);
#endif
				/* move to START state */
				mState = CSB_START;
				mStateTS = now;
				return 1;
			}
				
			return 0;
		}
			break;
	}


	/* Now catch errors for feedback when we should be WAITING */
	switch(mState)
	{
		default:
		case CSB_DIRECT_WAIT:
		case CSB_PROXY_WAIT:
		case CSB_RELAY_WAIT:
		case CSB_REVERSE_WAIT:
		case CSB_FAILED_WAIT:
		case CSB_START:
		case CSB_CONNECTED: /* impossible */
		{
			/* ERROR */
			/* shouldn't receive anything here! */
			errorMsg(std::cerr, "shouldnt get anything", update);

			return 0;
		}
			break;

		case CSB_DIRECT_ATTEMPT:
		case CSB_PROXY_ATTEMPT:
		case CSB_RELAY_ATTEMPT:
		{
			/* OKAY */

		}
			break;
	}


	switch(update)
	{
		/* if AUTH_DENIED ... => FAILED_WAIT */
		case CSB_UPDATE_AUTH_DENIED:
		{
#ifdef 	DEBUG_CONNECTBOX
			stateMsg(std::cerr, "=> FAILED WAIT", update);
#endif
			mState = CSB_FAILED_WAIT;
			mStateTS = now;
			mAttemptLength = now - mStateTS;
			mNextAttemptTS = now + FAILED_WAIT_TIME;

			return 1;
		}
			break;
		/* if standard FAIL => stay where we are */
		case CSB_UPDATE_RETRY_ATTEMPT:
		{
#ifdef 	DEBUG_CONNECTBOX
			stateMsg(std::cerr, "RETRY FAIL => switch to wait state", update);
#endif
			mAttemptLength = now - mStateTS;
			switch(mState)
			{
				case CSB_DIRECT_ATTEMPT:
					mState = CSB_DIRECT_WAIT;
					mStateTS = now;
					mNextAttemptTS = now + RSRandom::random_u32() % DIRECT_MAX_WAIT_TIME;
					break;
				case CSB_PROXY_ATTEMPT:
					mState = CSB_PROXY_WAIT;
					mStateTS = now;
					mNextAttemptTS = now + PROXY_BASE_WAIT_TIME + (RSRandom::random_u32() % (PROXY_MAX_WAIT_TIME - PROXY_BASE_WAIT_TIME));
					break;
				case CSB_RELAY_ATTEMPT:
					mState = CSB_RELAY_WAIT;
					mStateTS = now;
					mNextAttemptTS = now + RSRandom::random_u32() % RELAY_MAX_WAIT_TIME;
					break;
				default:
#ifdef 	DEBUG_CONNECTBOX
					stateMsg(std::cerr, "RETRY FAIL, but unusual state", update);
#endif
					break;
			}
					
			return 1;
		}
		/* if standard FAIL => stay where we are */
		case CSB_UPDATE_FAILED_ATTEMPT:
		{
#ifdef 	DEBUG_CONNECTBOX
			stateMsg(std::cerr, "STANDARD FAIL => switch to wait state", update);
#endif
			mNoFailedAttempts++;
			mAttemptLength = now - mStateTS;
			switch(mState)
			{
				case CSB_DIRECT_ATTEMPT:
					mState = CSB_DIRECT_WAIT;
					mStateTS = now;
					mNextAttemptTS = now + RSRandom::random_u32() % DIRECT_MAX_WAIT_TIME;
					break;
				case CSB_PROXY_ATTEMPT:
					mState = CSB_PROXY_WAIT;
					mStateTS = now;
					mNextAttemptTS = now + PROXY_BASE_WAIT_TIME + (RSRandom::random_u32() % (PROXY_MAX_WAIT_TIME - PROXY_BASE_WAIT_TIME));
					break;
				case CSB_RELAY_ATTEMPT:
					mState = CSB_RELAY_WAIT;
					mStateTS = now;
					mNextAttemptTS = now + RSRandom::random_u32() % RELAY_MAX_WAIT_TIME;
					break;
				default:
#ifdef 	DEBUG_CONNECTBOX
					stateMsg(std::cerr, "STANDARD FAIL, but unusual state", update);
#endif
					break;
			}
					
			return 1;
		}
			break;

		/* finally MODE_UNAVAILABLE */
		case CSB_UPDATE_MODE_UNAVAILABLE:
		{
			mAttemptLength = now - mStateTS;
			if (mState == CSB_PROXY_ATTEMPT)
			{
				if (mNetState == CSB_NETSTATE_FORWARD)
				{
#ifdef 	DEBUG_CONNECTBOX
					stateMsg(std::cerr, "as FORWARDED => REVERSE_WAIT", update);
#endif
					mState = CSB_REVERSE_WAIT;
					mStateTS = now;
					mNextAttemptTS = now + REVERSE_WAIT_TIME;
				}
				else
				{
#ifdef 	DEBUG_CONNECTBOX
					stateMsg(std::cerr, "as !FORWARDED => RELAY_ATTEMPT", update);
#endif
					mState = CSB_RELAY_WAIT;
					mNoAttempts = 0;
					mStateTS = now;
					mNextAttemptTS = now + RSRandom::random_u32() % RELAY_MAX_WAIT_TIME;
				}
				return 1;
			}
			else
			{
#ifdef 	DEBUG_CONNECTBOX
				stateMsg(std::cerr, "MODE UNAVAIL => FAILED_WAIT", update);
#endif
				mState = CSB_FAILED_WAIT;
				mStateTS = now;
				mNextAttemptTS = now + FAILED_WAIT_TIME;

				if ((mState == CSB_DIRECT_ATTEMPT)
					|| (mState == CSB_PROXY_ATTEMPT))
				{
					/* OKAY */
					return 1;
				}
				else
				{
					/* ERROR */
					errorMsg(std::cerr, "strange MODE", update);

					return 0;
				}
			}
		}
			break;

		default:
		{
			/* ERROR */
			errorMsg(std::cerr, "impossible default", update);
		}
			break;
	}
				
	/* if we get here... ERROR */
	errorMsg(std::cerr, "if we get here => ERROR", update);


	return 0;
}










bool PeerConnectStateBox::storeProxyPortChoice(uint32_t flags, bool useProxyPort)
{
	mProxyPortFlags = flags;
	mProxyPortChoice = useProxyPort;
	mProxyPortTS = time(NULL);

	return useProxyPort;
}

bool PeerConnectStateBox::getProxyPortChoice()
{
#ifdef 	DEBUG_CONNECTBOX
	rstime_t now = time(NULL);

	std::cerr << "PeerConnectStateBox::getProxyPortChoice() Using ConnectLogic Info from: ";
	std::cerr << now-mProxyPortTS << " ago. Flags: " << mProxyPortFlags;
	std::cerr << " UseProxyPort? " << mProxyPortChoice;
	std::cerr << std::endl;
#endif

	return mProxyPortChoice;
}



