/// <summary>
        /// Starts the "process" to connect to the master server. Relevant connection-values parameters can be set via parameters.
        /// </summary>
        /// <remarks>
        /// The process to connect includes several steps: the actual connecting, establishing encryption, authentification
        /// (of app and optionally the user) and joining a lobby (if AutoJoinLobby is true).
        ///
        /// Instead of providing all these parameters, you can also set the individual properties of a client before calling Connect().
        ///
        /// Users can connect either anonymously or use "Custom Authentication" to verify each individual player's login.
        /// Custom Authentication in Photon uses external services and communities to verify users. While the client provides a user's info,
        /// the service setup is done in the Photon Cloud Dashboard.
        /// The parameter authValues will set this.CustomAuthenticationValues and use them in the connect process.
        ///
        /// To connect to the Photon Cloud, a valid AppId must be provided. This is shown in the Photon Cloud Dashboard.
        /// https://cloud.exitgames.com/dashboard
        /// Connecting to the Photon Cloud might fail due to:
        /// - Network issues (OnStatusChanged() StatusCode.ExceptionOnConnect)
        /// - Region not available (OnOperationResponse() for OpAuthenticate with ReturnCode == ErrorCode.InvalidRegion)
        /// - Subscription CCU limit reached (OnOperationResponse() for OpAuthenticate with ReturnCode == ErrorCode.MaxCcuReached)
        /// More about the connection limitations:
        /// http://doc.exitgames.com/photon-cloud/SubscriptionErrorCases/#cat-references
        /// </remarks>
        /// <param name="serverAddress">Set a master server address instead of using the default. Uses default if null or empty.</param>
        /// <param name="appId">Your application's name or the AppID assigned by Photon Cloud (as listed in Dashboard). Uses default if null or empty.</param>
        /// <param name="appVersion">Can be used to separate users by their client's version (useful to add features without breaking older clients). Uses default if null or empty.</param>
        /// <param name="playerName">Optional name for this player. Provide a unique (!) userID to use the Friend Finding feature. Uses default if null or empty.</param>
        /// <param name="authValues">Set the AuthParameters property to use Custom Authentication (see above). Attempts anonymous auth if null.</param>
        /// <returns>If the operation could be send (can be false for bad server urls).</returns>
        public bool Connect(string serverAddress, string appId, string appVersion, string playerName, AuthenticationValues authValues)
        {
            if (!string.IsNullOrEmpty(serverAddress))
            {
                this.MasterServerAddress = serverAddress;
            }

            if (!string.IsNullOrEmpty(appId))
            {
                this.AppId = appId;
            }

            if (!string.IsNullOrEmpty(appVersion))
            {
                this.AppVersion = appVersion;
            }

            if (!string.IsNullOrEmpty(playerName))
            {
                this.PlayerName = playerName;
            }

            this.CustomAuthenticationValues = authValues;

            return this.Connect();
        }
        /// <summary>
        /// Starts the "process" to connect to the master server. Relevant connection-values parameters can be set via parameters.
        /// </summary>
        /// <remarks>
        /// The process to connect includes several steps: the actual connecting, establishing encryption, authentification
        /// (of app and optionally the user) and joining a lobby (if AutoJoinLobby is true).
        ///
        /// Instead of providing all these parameters, you can also set the individual properties of a client before calling Connect().
        ///
        /// Users can connect either anonymously or use "Custom Authentication" to verify each individual player's login.
        /// Custom Authentication in Photon uses external services and communities to verify users. While the client provides a user's info,
        /// the service setup is done in the Photon Cloud Dashboard.
        /// The parameter authValues will set this.AuthValues and use them in the connect process.
        ///
        /// To connect to the Photon Cloud, a valid AppId must be provided. This is shown in the Photon Cloud Dashboard.
        /// https://cloud.photonengine.com/dashboard
        /// Connecting to the Photon Cloud might fail due to:
        /// - Network issues (OnStatusChanged() StatusCode.ExceptionOnConnect)
        /// - Region not available (OnOperationResponse() for OpAuthenticate with ReturnCode == ErrorCode.InvalidRegion)
        /// - Subscription CCU limit reached (OnOperationResponse() for OpAuthenticate with ReturnCode == ErrorCode.MaxCcuReached)
        /// More about the connection limitations:
        /// http://doc.photonengine.com/photon-cloud/SubscriptionErrorCases/#cat-references
        /// </remarks>
        /// <param name="masterServerAddress">Set a master server address instead of using the default. Uses default if null or empty.</param>
        /// <param name="appId">Your application's name or the AppID assigned by Photon Cloud (as listed in Dashboard). Uses default if null or empty.</param>
        /// <param name="appVersion">Can be used to separate users by their client's version (useful to add features without breaking older clients). Uses default if null or empty.</param>
        /// <param name="nickName">Optional name for this player.</param>
        /// <param name="authValues">Authentication values for this user. Optional. If you provide a unique userID it is used for FindFriends.</param>
        /// <returns>If the operation could be send (can be false for bad server urls).</returns>
        public bool Connect(string masterServerAddress, string appId, string appVersion, string nickName, AuthenticationValues authValues)
        {
            if (!string.IsNullOrEmpty(masterServerAddress))
            {
                this.MasterServerAddress = masterServerAddress;
            }

            if (!string.IsNullOrEmpty(appId))
            {
                this.AppId = appId;
            }

            if (!string.IsNullOrEmpty(appVersion))
            {
                this.AppVersion = appVersion;
            }

            if (!string.IsNullOrEmpty(nickName))
            {
                this.NickName = nickName;
            }

            this.AuthValues = authValues;

            return this.Connect();
        }
        /// <summary>
        /// Sends this app's appId and appVersion to identify this application server side.
        /// This is an async request which triggers a OnOperationResponse() call.
        /// </summary>
        /// <remarks>
        /// This operation makes use of encryption, if that is established before.
        /// See: EstablishEncryption(). Check encryption with IsEncryptionAvailable.
        /// This operation is allowed only once per connection (multiple calls will have ErrorCode != Ok).
        /// </remarks>
        /// <param name="appId">Your application's name or ID to authenticate. This is assigned by Photon Cloud (webpage).</param>
        /// <param name="appVersion">The client's version (clients with differing client appVersions are separated and players don't meet).</param>
        /// <param name="authValues">Optional authentication values. The client can set no values or a UserId or some parameters for Custom Authentication by a server.</param>
        /// <param name="regionCode">Optional region code, if the client should connect to a specific Photon Cloud Region.</param>
        /// <param name="getLobbyStatistics">Set to true on Master Server to receive "Lobby Statistics" events.</param>
        /// <returns>If the operation could be sent (has to be connected).</returns>
        public virtual bool OpAuthenticate(string appId, string appVersion, AuthenticationValues authValues, string regionCode, bool getLobbyStatistics)
        {
            if (this.DebugOut >= DebugLevel.INFO)
            {
                this.Listener.DebugReturn(DebugLevel.INFO, "OpAuthenticate()");
            }

            Dictionary<byte, object> opParameters = new Dictionary<byte, object>();
            if (getLobbyStatistics)
            {
                // must be sent in operation, even if a Token is available
                opParameters[ParameterCode.LobbyStats] = true;
            }

            if (authValues != null && authValues.Token != null)
            {
                opParameters[ParameterCode.Secret] = authValues.Token;
                return this.OpCustom(OperationCode.Authenticate, opParameters, true, (byte) 0, false);
            }

            opParameters[ParameterCode.AppVersion] = appVersion;
            opParameters[ParameterCode.ApplicationId] = appId;

            if (!string.IsNullOrEmpty(regionCode))
            {
                opParameters[ParameterCode.Region] = regionCode;
            }

            if (authValues != null)
            {

                if (!string.IsNullOrEmpty(authValues.UserId))
                {
                    opParameters[ParameterCode.UserId] = authValues.UserId;
                }

                if (authValues.AuthType != CustomAuthenticationType.None)
                {
                    opParameters[ParameterCode.ClientAuthenticationType] = (byte) authValues.AuthType;
                    if (!string.IsNullOrEmpty(authValues.Token))
                    {
                        opParameters[ParameterCode.Secret] = authValues.Token;
                    }
                    else
                    {
                        if (!string.IsNullOrEmpty(authValues.AuthGetParameters))
                        {
                            opParameters[ParameterCode.ClientAuthenticationParams] = authValues.AuthGetParameters;
                        }
                        if (authValues.AuthPostData != null)
                        {
                            opParameters[ParameterCode.ClientAuthenticationData] = authValues.AuthPostData;
                        }
                    }
                }
            }

            return this.OpCustom(OperationCode.Authenticate, opParameters, true, (byte)0, this.IsEncryptionAvailable);
        }