void FOnlineSessionInfoNull::Init(const FOnlineSubsystemNull& Subsystem)
{
	// Read the IP from the system
	bool bCanBindAll;
	HostAddr = ISocketSubsystem::Get()->GetLocalHostAddr(*GLog, bCanBindAll);

	// The below is a workaround for systems that set hostname to a distinct address from 127.0.0.1 on a loopback interface.
	// See e.g. https://www.debian.org/doc/manuals/debian-reference/ch05.en.html#_the_hostname_resolution
	// and http://serverfault.com/questions/363095/why-does-my-hostname-appear-with-the-address-127-0-1-1-rather-than-127-0-0-1-in
	// Since we bind to 0.0.0.0, we won't answer on 127.0.1.1, so we need to advertise ourselves as 127.0.0.1 for any other loopback address we may have.
	uint32 HostIp = 0;
	HostAddr->GetIp(HostIp); // will return in host order
	// if this address is on loopback interface, advertise it as 127.0.0.1
	if ((HostIp & 0xff000000) == 0x7f000000)
	{
		HostAddr->SetIp(0x7f000001);	// 127.0.0.1
	}

	// Now set the port that was configured
	HostAddr->SetPort(GetPortFromNetDriver(Subsystem.GetInstanceName()));

	FGuid OwnerGuid;
	FPlatformMisc::CreateGuid(OwnerGuid);
	SessionId = FUniqueNetIdString(OwnerGuid.ToString());
}
bool FOnlineIdentityAmazon::Login(int32 LocalUserNum, const FOnlineAccountCredentials& AccountCredentials)
{
	bool bWasSuccessful = false;
	if (!bHasLoginOutstanding && AmazonEndpoint.Len() && RedirectUrl.Len() && ClientId.Len())
	{
		State = FString::FromInt(FMath::Rand() % 100000);
		const FString& Command = FString::Printf(TEXT("%s?scope=profile&response_type=code&redirect_uri=%s&client_id=%s&state=%s"), *AmazonEndpoint, *RedirectUrl, *ClientId, *State);
		// This should open the browser with the command as the URL
		bHasLoginOutstanding = bWasSuccessful = FPlatformMisc::OsExecute(TEXT("open"), *Command);
		if (!bWasSuccessful)
		{
			UE_LOG(LogOnline, Error, TEXT("RegisterUser() : Failed to execute command %s "), *Command);
		}
		else
		{
			// keep track of local user requesting registration
			LocalUserNumPendingLogin = LocalUserNum;
		}
	}
	else
	{
		UE_LOG(LogOnline, Error, TEXT("RegisterUser() : OnlineSubsystemAmazon is improperly configured in DefaultEngine.ini"));
	}
	if (!bWasSuccessful)
	{
		TriggerOnLoginCompleteDelegates(LocalUserNum, false, FUniqueNetIdString(TEXT("")), FString(TEXT("RegisterUser() failed")));
	}
	return bWasSuccessful;
}
bool FOnlineIdentityGooglePlay::Login(int32 LocalUserNum, const FOnlineAccountCredentials& AccountCredentials)
{
    bool bStartedLogin = false;
    if (bLoggedIn)
    {
        // already logged in so just report all is ok!
        // Now logged in
        bStartedLogin = true;

        static const int32 MAX_TEXT_LINE_LEN = 32;
        TCHAR Line[MAX_TEXT_LINE_LEN + 1] = { 0 };
        int32 Len = FCString::Snprintf(Line, MAX_TEXT_LINE_LEN, TEXT("%d"), LocalUserNum);

        const FString PlayerId(Line);
        UniqueNetId = MakeShareable(new FUniqueNetIdString(PlayerId));
        TriggerOnLoginCompleteDelegates(LocalUserNum, true, *UniqueNetId, TEXT(""));
    }
    else if (!PendingConnectRequest.IsConnectionPending)
    {
        // Kick the login sequence...
        bStartedLogin = true;
        PendingConnectRequest.IsConnectionPending = true;
    }
    else
    {
        TriggerOnLoginCompleteDelegates(LocalUserNum, false, FUniqueNetIdString(TEXT("")), FString("Already trying to login"));
    }

    return bStartedLogin;
}
void FOnlineIdentityAmazon::TickLogin(float DeltaTime)
{
	if (bHasLoginOutstanding)
	{
		LastCheckElapsedTime += DeltaTime;
		TotalCheckElapsedTime += DeltaTime;
		// See if enough time has elapsed in order to check for completion
		if (LastCheckElapsedTime > 1.f ||
			// Do one last check if we're getting ready to time out
			TotalCheckElapsedTime > MaxCheckElapsedTime)
		{
			LastCheckElapsedTime = 0.f;
			FString Title;
			if (FPlatformMisc::GetWindowTitleMatchingText(TEXT("accessToken"), Title))
			{
				bHasLoginOutstanding = false;
				FUserOnlineAccountAmazon User;
				if (ParseLoginResults(Title, User))
				{
					TSharedRef<FUserOnlineAccountAmazon> UserRef(new FUserOnlineAccountAmazon(User.UserId, User.SecretKey, User.AuthTicket));
					UserAccounts.Add(User.UserId, UserRef);					
					TriggerOnLoginCompleteDelegates(LocalUserNumPendingLogin, true, *UserRef->GetUserId(), FString());
				}
				else
				{
					TriggerOnLoginCompleteDelegates(LocalUserNumPendingLogin, false, FUniqueNetIdString(TEXT("")), FString(TEXT("RegisterUser() failed to parse the user registration results")));
				}
			}
			// Trigger the delegate if we hit the timeout limit
			else if (TotalCheckElapsedTime > MaxCheckElapsedTime)
			{
				bHasLoginOutstanding = false;
				TriggerOnLoginCompleteDelegates(LocalUserNumPendingLogin, false, FUniqueNetIdString(TEXT("")), FString(TEXT("RegisterUser() timed out without getting the data")));
			}
		}
		// Reset our time trackers if we are done ticking for now
		if (!bHasLoginOutstanding)
		{
			LastCheckElapsedTime = 0.f;
			TotalCheckElapsedTime = 0.f;
		}
	}
}
bool FOnlineIdentityNull::Login(int32 LocalUserNum, const FOnlineAccountCredentials& AccountCredentials)
{
	FString ErrorStr;
	TSharedPtr<FUserOnlineAccountNull> UserAccountPtr;
	
	// valid local player index
	if (LocalUserNum < 0 || LocalUserNum >= MAX_LOCAL_PLAYERS)
	{
		ErrorStr = FString::Printf(TEXT("Invalid LocalUserNum=%d"), LocalUserNum);
	}
	else if (AccountCredentials.Id.IsEmpty())
	{
		ErrorStr = FString::Printf(TEXT("Invalid account id=%s"), *AccountCredentials.Id);
	}
	else
	{
		TSharedPtr<FUniqueNetId>* UserId = UserIds.Find(LocalUserNum);
		if (UserId == NULL)
		{
			FString RandomUserId = GenerateRandomUserId(LocalUserNum);

			FUniqueNetIdString NewUserId(RandomUserId);
			UserAccountPtr = MakeShareable(new FUserOnlineAccountNull(RandomUserId));
			UserAccountPtr->UserAttributes.Add(TEXT("id"), RandomUserId);

			// update/add cached entry for user
			UserAccounts.Add(NewUserId, UserAccountPtr.ToSharedRef());

			// keep track of user ids for local users
			UserIds.Add(LocalUserNum, UserAccountPtr->GetUserId());
		}
		else
		{
			const FUniqueNetIdString* UniqueIdStr = (FUniqueNetIdString*)(UserId->Get());
			TSharedRef<FUserOnlineAccountNull>* TempPtr = UserAccounts.Find(*UniqueIdStr);
			check(TempPtr);
			UserAccountPtr = *TempPtr;
		}
	}

	if (!ErrorStr.IsEmpty())
	{
		UE_LOG_ONLINE(Warning, TEXT("Login request failed. %s"), *ErrorStr);
		TriggerOnLoginCompleteDelegates(LocalUserNum, false, FUniqueNetIdString(), ErrorStr);
		return false;
	}

	TriggerOnLoginCompleteDelegates(LocalUserNum, true, *UserAccountPtr->GetUserId(), ErrorStr);
	return true;
}
bool FOnlineIdentityLeet::Login(int32 LocalUserNum, const FOnlineAccountCredentials& AccountCredentials)
{
	UE_LOG_ONLINE(Display, TEXT("FOnlineIdentityLeet::Login"));
	FString ErrorStr;

	if (bHasLoginOutstanding)
	{
		ErrorStr = FString::Printf(TEXT("Registration already pending for user %d"),
			LocalUserNumPendingLogin);
	}
	else if (!(LoginUrl.Len() && LoginRedirectUrl.Len() && ClientId.Len()))
	{
		ErrorStr = FString::Printf(TEXT("OnlineSubsystemLeet is improperly configured in DefaultEngine.ini LeetEndpoint=%s RedirectUrl=%s ClientId=%s"),
			*LoginUrl, *LoginRedirectUrl, *ClientId);
	}
	else
	{
		// random number to represent client generated state for verification on login
		State = FString::FromInt(FMath::Rand() % 100000);
		// auth url to spawn in browser
		const FString& Command = FString::Printf(TEXT("%s?redirect_uri=%s&client_id=%s&state=%s&response_type=token"),
			*LoginUrl, *LoginRedirectUrl, *ClientId, *State);
		UE_LOG_ONLINE(Display, TEXT("FOnlineIdentityLeet::Login - %s"), *LoginUrl);
		// This should open the browser with the command as the URL
		if (FPlatformMisc::OsExecute(TEXT("open"), *Command))
		{
			// keep track of local user requesting registration
			LocalUserNumPendingLogin = LocalUserNum;
			bHasLoginOutstanding = true;
		}
		else
		{
			ErrorStr = FString::Printf(TEXT("Failed to execute command %s"),
				*Command);
		}
	}

	if (!ErrorStr.IsEmpty())
	{
		UE_LOG(LogOnline, Error, TEXT("RegisterUser() failed: %s"),
			*ErrorStr);
		TriggerOnLoginCompleteDelegates(LocalUserNum, false, FUniqueNetIdString(TEXT("")), ErrorStr);
		return false;
	}
	return true;
}
bool FOnlineIdentityNull::Logout(int32 LocalUserNum)
{
	TSharedPtr<FUniqueNetId> UserId = GetUniquePlayerId(LocalUserNum);
	if (UserId.IsValid())
	{
		// remove cached user account
		UserAccounts.Remove(FUniqueNetIdString(*UserId));
		// remove cached user id
		UserIds.Remove(LocalUserNum);
		// not async but should call completion delegate anyway
		TriggerOnLogoutCompleteDelegates(LocalUserNum, true);

		return true;
	}
	else
	{
		UE_LOG_ONLINE(Warning, TEXT("No logged in user found for LocalUserNum=%d."),
			LocalUserNum);
		TriggerOnLogoutCompleteDelegates(LocalUserNum, false);
	}
	return false;
}
/**
* Ticks the registration process handling timeouts, etc.
*
* @param DeltaTime the amount of time that has elapsed since last tick
*/
void FOnlineIdentityLeet::TickLogin(float DeltaTime)
{
	
	if (bHasLoginOutstanding)
	{
		//UE_LOG_ONLINE(Display, TEXT("FOnlineIdentityLeet::TickLogin bHasLoginOutstanding"));
		LastCheckElapsedTime += DeltaTime;
		TotalCheckElapsedTime += DeltaTime;
		// See if enough time has elapsed in order to check for completion
		if (LastCheckElapsedTime > 1.f ||
			// Do one last check if we're getting ready to time out
			TotalCheckElapsedTime > MaxCheckElapsedTime)
		{
			LastCheckElapsedTime = 0.f;
			FString Title;
			
			// Find the browser window we spawned which should now be titled with the redirect url
			if (FPlatformMisc::GetWindowTitleMatchingText(*LoginRedirectUrl, Title))
			{
				UE_LOG_ONLINE(Display, TEXT("FOnlineIdentityLeet::TickLogin GetWindowTitleMatchingText"));
				bHasLoginOutstanding = false;

				// Parse access token from the login redirect url
				FString AccessToken;
				if (FParse::Value(*Title, TEXT("access_token="), AccessToken) &&
					!AccessToken.IsEmpty())
				{
					UE_LOG_ONLINE(Display, TEXT("FOnlineIdentityLeet::TickLogin Found access_token"));
					// strip off any url parameters and just keep the token itself
					FString AccessTokenOnly;
					if (AccessToken.Split(TEXT("&"), &AccessTokenOnly, NULL))
					{
						AccessToken = AccessTokenOnly;
					}
					// kick off http request to get user info with the new token
					TSharedRef<class IHttpRequest> HttpRequest = FHttpModule::Get().CreateRequest();
					LoginUserRequests.Add(&HttpRequest.Get(), FPendingLoginUser(LocalUserNumPendingLogin, AccessToken));

					FString MeUrl = TEXT("https://leetsandbox.appspot.com/me?access_token=`token");

					HttpRequest->OnProcessRequestComplete().BindRaw(this, &FOnlineIdentityLeet::MeUser_HttpRequestComplete);
					HttpRequest->SetURL(MeUrl.Replace(TEXT("`token"), *AccessToken, ESearchCase::IgnoreCase));
					HttpRequest->SetHeader(TEXT("Content-Type"), TEXT("application/json"));
					HttpRequest->SetVerb(TEXT("GET"));
					HttpRequest->ProcessRequest();
				}
				else
				{
					TriggerOnLoginCompleteDelegates(LocalUserNumPendingLogin, false, FUniqueNetIdString(TEXT("")),
						FString(TEXT("RegisterUser() failed to parse the user registration results")));
				}
			}
			// Trigger the delegate if we hit the timeout limit
			else if (TotalCheckElapsedTime > MaxCheckElapsedTime)
			{
				bHasLoginOutstanding = false;
				TriggerOnLoginCompleteDelegates(LocalUserNumPendingLogin, false, FUniqueNetIdString(TEXT("")),
					FString(TEXT("RegisterUser() timed out without getting the data")));
			}
		}
		// Reset our time trackers if we are done ticking for now
		if (!bHasLoginOutstanding)
		{
			LastCheckElapsedTime = 0.f;
			TotalCheckElapsedTime = 0.f;
		}
	}
}
void FOnlineIdentityLeet::MeUser_HttpRequestComplete(FHttpRequestPtr HttpRequest, FHttpResponsePtr HttpResponse, bool bSucceeded)
{
	UE_LOG_ONLINE(Display, TEXT("FOnlineIdentityLeet::MeUser_HttpRequestComplete"));
	bool bResult = false;
	FString ResponseStr, ErrorStr;
	FUserOnlineAccountLeet User;

	FPendingLoginUser PendingRegisterUser = LoginUserRequests.FindRef(HttpRequest.Get());
	// Remove the request from list of pending entries
	LoginUserRequests.Remove(HttpRequest.Get());

	if (bSucceeded &&
		HttpResponse.IsValid())
	{
		ResponseStr = HttpResponse->GetContentAsString();
		if (EHttpResponseCodes::IsOk(HttpResponse->GetResponseCode()))
		{
			UE_LOG(LogOnline, Verbose, TEXT("RegisterUser request complete. url=%s code=%d response=%s"),
				*HttpRequest->GetURL(), HttpResponse->GetResponseCode(), *ResponseStr);

			if (User.FromJson(ResponseStr))
			{
				if (!User.UserId.IsEmpty())
				{
					// copy and construct the unique id
					TSharedRef<FUserOnlineAccountLeet> UserRef(new FUserOnlineAccountLeet(User));
					UserRef->UserIdPtr = MakeShareable(new FUniqueNetIdString(User.UserId));
					// update/add cached entry for user
					UserAccounts.Add(User.UserId, UserRef);
					// update the access token
					UserRef->AuthTicket = PendingRegisterUser.AccessToken;
					// keep track of user ids for local users
					UserIds.Add(PendingRegisterUser.LocalUserNum, UserRef->GetUserId());

					bResult = true;
				}
				else
				{
					ErrorStr = FString::Printf(TEXT("Missing user id. payload=%s"),
						*ResponseStr);
				}
			}
			else
			{
				ErrorStr = FString::Printf(TEXT("Invalid response payload=%s"),
					*ResponseStr);
			}
		}
		else
		{
			ErrorStr = FString::Printf(TEXT("Invalid response. code=%d error=%s"),
				HttpResponse->GetResponseCode(), *ResponseStr);
		}
	}
	else
	{
		ErrorStr = TEXT("No response");
	}
	if (!ErrorStr.IsEmpty())
	{
		UE_LOG(LogOnline, Warning, TEXT("RegisterUser request failed. %s"), *ErrorStr);
	}

	TriggerOnLoginCompleteDelegates(PendingRegisterUser.LocalUserNum, bResult, FUniqueNetIdString(User.UserId), ErrorStr);
}
void FOnlineAchievementsGameCircle::QueryAchievements(const FUniqueNetId& PlayerId, const FOnQueryAchievementsCompleteDelegate& Delegate)
{
	if(AndroidSubsystem->GetCallbackManager() == nullptr || AndroidSubsystem->GetIdentityGameCircle()->GetLoginStatus(0) != ELoginStatus::LoggedIn)
	{
		Delegate.ExecuteIfBound(PlayerId, false);
		return;
	}

	AmazonGames::AchievementsClientInterface::getAchievements(new FOnlineGetAchievementsCallback(AndroidSubsystem, FUniqueNetIdString(PlayerId), Delegate));
}