/// <summary>
        /// Request a generic resource from the service server(s).
        /// </summary>
        /// <param name="route">
        /// The API route to make the request to.
        /// </param>
        /// <param name="responseReceived">
        /// Gets set to false if no response was received, otherwise false.
        /// </param>
        /// <param name="noLogging">
        /// Whether or not to log errors. Since HttpWebRequest brilliant throws exceptions for
        /// non-success HTTP status codes, it's nice to be able to control whether or not your
        /// request should have errors logged.
        /// </param>
        /// <returns>
        /// A non-null byte array on success. Null byte array on failure.
        /// </returns>
        public byte[] RequestResource(string resourceUri, out HttpStatusCode code, out bool responseReceived, ResourceOptions options = null, ServiceResource resource = ServiceResource.Custom)
        {
            if (options == null)
            {
                options = new ResourceOptions(); // Instantiate a resource options object with default options.
            }

            responseReceived = true;
            Dictionary <string, object> parameters = new Dictionary <string, object>();

            try
            {
                // Try to send the device name as well. Helps distinguish between clients under the
                // same account.
                string deviceName = string.Empty;

                try
                {
                    deviceName = Environment.MachineName;
                }
                catch
                {
                    deviceName = "Unknown";
                }

                var accessToken = WebServiceUtil.Default.AuthToken;

                //m_logger.Info("RequestResource1: accessToken=" + accessToken);
                IVersionProvider versionProvider = PlatformTypes.New <IVersionProvider>();
                string           version         = versionProvider.GetApplicationVersion().ToString(3);

                // Build out post data with username and identifier.
                parameters.Add("identifier", FingerprintService.Default.Value);
                parameters.Add("device_id", deviceName);

                string postString = null;
                //string postString = string.Format("&identifier={0}&device_id={1}", FingerprintService.Default.Value, Uri.EscapeDataString(deviceName));

                if (options.Parameters != null)
                {
                    foreach (var parameter in options.Parameters)
                    {
                        parameters.Add(parameter.Key, parameter.Value);
                    }
                }

                if (resource == ServiceResource.UserDataSumCheck || resource == ServiceResource.UserConfigSumCheck)
                {
                    m_logger.Info("Sending version {0} to server", version);
                    parameters.Add("app_version", version);
                }

                switch (options.ContentType)
                {
                case "application/x-www-form-urlencoded":
                    postString = string.Join("&", parameters.Select(kv => $"{kv.Key}={kv.Value}"));
                    break;

                case "application/json":
                    postString = Newtonsoft.Json.JsonConvert.SerializeObject(parameters);
                    break;
                }

                if (options.Method == "GET" || options.Method == "DELETE")
                {
                    resourceUri += "?" + postString;

                    if (postString.Contains("app_version"))
                    {
                        m_logger.Info("Sending postString as {0}", resourceUri);
                    }
                }

                var request = GetApiBaseRequest(resourceUri, options);

                m_logger.Debug("WebServiceUtil.Request {0}", request.RequestUri);

                if (StringExtensions.Valid(accessToken))
                {
                    request.Headers.Add("Authorization", string.Format("Bearer {0}", accessToken));
                }
                else if (resource != ServiceResource.RetrieveToken)
                {
                    m_logger.Info("RequestResource1: Authorization failed.");
                    AuthTokenRejected?.Invoke();
                    code = HttpStatusCode.Unauthorized;
                    return(null);
                }

                if (options.Method != "GET" && options.Method != "DELETE")
                {
                    if (postString.Contains("app_version"))
                    {
                        m_logger.Info("Sending {0} to server as {1}", postString, options.Method);
                    }

                    var formData = System.Text.Encoding.UTF8.GetBytes(postString);
                    request.ContentLength = formData.Length;

                    using (var requestStream = request.GetRequestStream())
                    {
                        requestStream.Write(formData, 0, formData.Length);
                        requestStream.Close();
                    }
                }

                m_logger.Info("RequestResource: uri={0}", request.RequestUri);

                // Now that our login form data has been POST'ed, get a response.
                using (var response = (HttpWebResponse)request.GetResponse())
                {
                    // Get the response code as an int so we can range check it.
                    var intCode = (int)response.StatusCode;

                    code = (HttpStatusCode)intCode;

                    try
                    {
                        // Check if response code is considered a success code.
                        if (intCode >= 200 && intCode <= 299)
                        {
                            using (var memoryStream = new MemoryStream())
                            {
                                response.GetResponseStream().CopyTo(memoryStream);

                                // We do this just in case we get something like a 204. The idea here
                                // is that if we return a non-null, the call was a success.
                                var responseBody = memoryStream.ToArray();
                                if (responseBody == null || intCode == 204)
                                {
                                    return(null);
                                }

                                return(responseBody);
                            }
                        }
                        else
                        {
                            m_logger.Info("When requesting resource, got unexpected response code of {0}.", code);
                        }
                    }
                    finally
                    {
                        response.Close();
                        request.Abort();
                    }
                }
            }
            catch (WebException e)
            {
                // KF - Set this to 0 for default. 0's a pretty good indicator of no internet.
                code = 0;

                try
                {
                    using (WebResponse response = e.Response)
                    {
                        if (response == null)
                        {
                            responseReceived = false;
                        }

                        HttpWebResponse httpResponse = (HttpWebResponse)response;
                        m_logger.Error("Error code: {0}", httpResponse.StatusCode);

                        int intCode = (int)httpResponse.StatusCode;

                        code = (HttpStatusCode)intCode;

                        // Auth failure means re-log EXCEPT when requesting deactivation.
                        if ((intCode == 401 || intCode == 403) && resource != ServiceResource.DeactivationRequest)
                        {
                            WebServiceUtil.Default.AuthToken = string.Empty;
                            m_logger.Info("RequestResource2: Authorization failed.");
                            AuthTokenRejected?.Invoke();
                        }
                        else if (intCode > 399 && intCode <= 499 && resource != ServiceResource.DeactivationRequest)
                        {
                            m_logger.Info("Error occurred in RequestResource: {0}", intCode);
                        }

                        using (Stream data = response.GetResponseStream())
                            using (var reader = new StreamReader(data))
                            {
                                string text = reader.ReadToEnd();
                                m_logger.Error(text);
                            }
                    }
                }
                catch { }

                if (!options.NoLogging)
                {
                    m_logger.Error(e.Message);
                    m_logger.Error(e.StackTrace);
                }
            }
            catch (Exception e)
            {
                // XXX TODO - Good default?
                code = HttpStatusCode.InternalServerError;

                if (!options.NoLogging)
                {
                    while (e != null)
                    {
                        m_logger.Error(e.Message);
                        m_logger.Error(e.StackTrace);
                        e = e.InnerException;
                    }
                }
            }

            return(null);
        }
        /// <summary>
        /// Attempts to post the given form-encoded data to the service API.
        /// </summary>
        /// <param name="route">
        /// The API route to make the request to.
        /// </param>
        /// <param name="formEncodedData">
        /// The form encoded data to post.
        /// </param>
        /// <param name="noLogging">
        /// Whether or not to log errors. Since HttpWebRequest brilliant throws exceptions for
        /// non-success HTTP status codes, it's nice to be able to control whether or not your
        /// request should have errors logged.
        /// </param>
        /// <returns>
        /// True if the web service gave a success response, false otherwise.
        /// </returns>
        public bool SendResource(ServiceResource resource, byte[] formEncodedData, out HttpStatusCode code, bool noLogging = true)
        {
            try
            {
                // Try to send the device name as well. Helps distinguish between clients under the
                // same account.
                string deviceName = string.Empty;

                try
                {
                    deviceName = Environment.MachineName;
                }
                catch
                {
                    deviceName = "Unknown";
                }

                var request = GetApiBaseRequest(m_namedResourceMap[resource], new ResourceOptions());

                var accessToken = WebServiceUtil.Default.AuthToken;

                if (StringExtensions.Valid(accessToken))
                {
                    request.Headers.Add("Authorization", string.Format("Bearer {0}", accessToken));
                }
                else
                {
                    m_logger.Info("SendResource1: Authorization failed.");
                    AuthTokenRejected?.Invoke();
                    code = HttpStatusCode.Unauthorized;
                    return(false);
                }

                // Build out post data with username and identifier.
                var formData = System.Text.Encoding.UTF8.GetBytes(string.Format("identifier={0}&device_id={1}&", FingerprintService.Default.Value, Uri.EscapeDataString(deviceName)));

                // Merge all data.
                var finalData = new byte[formData.Length + formEncodedData.Length];
                Array.Copy(formData, finalData, formData.Length);
                Array.Copy(formEncodedData, 0, finalData, formData.Length, formEncodedData.Length);
                formData = finalData;

                // Don't forget to the set the content length to the total length of our form POST data!
                request.ContentLength = formData.Length;

                // Grab the request stream so we can POST our login form data to it.
                using (var requestStream = request.GetRequestStream())
                {
                    // Write and close.
                    requestStream.Write(formData, 0, formData.Length);
                    requestStream.Close();
                }

                // Now that our login form data has been POST'ed, get a response.
                using (var response = (HttpWebResponse)request.GetResponse())
                {
                    // Get the response code as an int so we can range check it.
                    var intCode = (int)response.StatusCode;

                    code = (HttpStatusCode)intCode;

                    try
                    {
                        // Check if response code is considered a success code.
                        if (intCode >= 200 && intCode <= 299)
                        {
                            return(true);
                        }
                    }
                    finally
                    {
                        response.Close();
                        request.Abort();
                    }
                }
            }
            catch (WebException e)
            {
                // XXX TODO - Good default?
                code = HttpStatusCode.InternalServerError;

                try
                {
                    using (WebResponse response = e.Response)
                    {
                        HttpWebResponse httpResponse = (HttpWebResponse)response;
                        m_logger.Error("Error code: {0}", httpResponse.StatusCode);

                        int intCode = (int)httpResponse.StatusCode;

                        if (intCode == 401 || intCode == 403)
                        {
                            WebServiceUtil.Default.AuthToken = string.Empty;
                            m_logger.Info("SendResource2: Authorization failed.");
                            AuthTokenRejected?.Invoke();
                        }
                        else if (intCode > 399 && intCode < 499)
                        {
                            m_logger.Info("SendResource2: Failed with client code {0}", intCode);
                        }

                        using (Stream data = response.GetResponseStream())
                            using (var reader = new StreamReader(data))
                            {
                                string text = reader.ReadToEnd();
                                m_logger.Error(text);
                            }
                    }
                }
                catch { }

                if (noLogging == false)
                {
                    m_logger.Error(e.Message);
                    m_logger.Error(e.StackTrace);
                }
            }
            catch (Exception e)
            {
                // XXX TODO - Good default?
                code = HttpStatusCode.InternalServerError;

                if (noLogging == false)
                {
                    while (e != null)
                    {
                        m_logger.Error(e.Message);
                        m_logger.Error(e.StackTrace);
                        e = e.InnerException;
                    }
                }
            }

            return(false);
        }
        /// <summary>
        /// Request a generic resource from the service server(s).
        /// </summary>
        /// <param name="route">
        /// The API route to make the request to.
        /// </param>
        /// <param name="responseReceived">
        /// Gets set to false if no response was received, otherwise false.
        /// </param>
        /// <param name="noLogging">
        /// Whether or not to log errors. Since HttpWebRequest brilliant throws exceptions for
        /// non-success HTTP status codes, it's nice to be able to control whether or not your
        /// request should have errors logged.
        /// </param>
        /// <returns>
        /// A non-null byte array on success. Null byte array on failure.
        /// </returns>
        public byte[] RequestResource(ServiceResource resource, out HttpStatusCode code, out bool responseReceived, Dictionary <string, string> parameters = null, bool noLogging = false)
        {
            responseReceived = true;

            try
            {
                // Try to send the device name as well. Helps distinguish between clients under the
                // same account.
                string deviceName = string.Empty;

                try
                {
                    deviceName = Environment.MachineName;
                }
                catch
                {
                    deviceName = "Unknown";
                }

                var request = GetApiBaseRequest(m_namedResourceMap[resource]);

                var accessToken = AuthToken;

                //m_logger.Info("RequestResource1: accessToken=" + accessToken);
                m_logger.Info("RequestResource: accessToken length={0}", accessToken == null ? "(null)" : accessToken.Length.ToString());
                m_logger.Info("RequestResource: {0}", resource.ToString());

                if (StringExtensions.Valid(accessToken))
                {
                    request.Headers.Add("Authorization", string.Format("Bearer {0}", accessToken));
                }
                else if (resource != ServiceResource.RetrieveToken)
                {
                    m_logger.Info("RequestResource1: Authorization failed.");
                    AuthTokenRejected?.Invoke();
                    code = HttpStatusCode.Unauthorized;
                    return(null);
                }

                System.Reflection.Assembly assembly = System.Reflection.Assembly.GetExecutingAssembly();
                string version = System.Reflection.AssemblyName.GetAssemblyName(assembly.Location).Version.ToString();

                // Build out post data with username and identifier.
                string postString = string.Format("&identifier={0}&device_id={1}", FingerPrint.Value, Uri.EscapeDataString(deviceName));

                if (parameters != null)
                {
                    foreach (var parameter in parameters)
                    {
                        postString += $"&{parameter.Key}={parameter.Value}";
                    }
                }

                if (resource == ServiceResource.UserDataSumCheck)
                {
                    postString += string.Format("&app_version={0}", version);
                }

                var formData = System.Text.Encoding.UTF8.GetBytes(postString);

                // Don't forget to the set the content length to the total length of our form POST data!
                request.ContentLength = formData.Length;

                // Grab the request stream so we can POST our login form data to it.
                using (var requestStream = request.GetRequestStream())
                {
                    // Write and close.
                    requestStream.Write(formData, 0, formData.Length);
                    requestStream.Close();
                }

                // Now that our login form data has been POST'ed, get a response.
                using (var response = (HttpWebResponse)request.GetResponse())
                {
                    // Get the response code as an int so we can range check it.
                    var intCode = (int)response.StatusCode;

                    code = (HttpStatusCode)intCode;

                    try
                    {
                        // Check if response code is considered a success code.
                        if (intCode >= 200 && intCode <= 299)
                        {
                            using (var memoryStream = new MemoryStream())
                            {
                                response.GetResponseStream().CopyTo(memoryStream);

                                // We do this just in case we get something like a 204. The idea here
                                // is that if we return a non-null, the call was a success.
                                var responseBody = memoryStream.ToArray();
                                if (responseBody == null || intCode == 204)
                                {
                                    return(null);
                                }

                                return(responseBody);
                            }
                        }
                        else
                        {
                            m_logger.Info("When requesting resource, got unexpected response code of {0}.", code);
                        }
                    }
                    finally
                    {
                        response.Close();
                        request.Abort();
                    }
                }
            }
            catch (WebException e)
            {
                // KF - Set this to 0 for default. 0's a pretty good indicator of no internet.
                code = 0;

                try
                {
                    using (WebResponse response = e.Response)
                    {
                        if (response == null)
                        {
                            responseReceived = false;
                        }

                        HttpWebResponse httpResponse = (HttpWebResponse)response;
                        m_logger.Error("Error code: {0}", httpResponse.StatusCode);

                        int intCode = (int)httpResponse.StatusCode;

                        code = (HttpStatusCode)intCode;

                        // Auth failure means re-log EXCEPT when requesting deactivation.
                        if ((intCode == 401 || intCode == 403) && resource != ServiceResource.DeactivationRequest)
                        {
                            AuthToken = string.Empty;
                            m_logger.Info("RequestResource2: Authorization failed.");
                            AuthTokenRejected?.Invoke();
                        }
                        else if (intCode > 399 && intCode <= 499 && resource != ServiceResource.DeactivationRequest)
                        {
                            m_logger.Info("Error occurred in RequestResource: {0}", intCode);
                        }

                        using (Stream data = response.GetResponseStream())
                            using (var reader = new StreamReader(data))
                            {
                                string text = reader.ReadToEnd();
                                m_logger.Error(text);
                            }
                    }
                }
                catch { }

                if (noLogging == false)
                {
                    m_logger.Error(e.Message);
                    m_logger.Error(e.StackTrace);
                }
            }
            catch (Exception e)
            {
                // XXX TODO - Good default?
                code = HttpStatusCode.InternalServerError;

                if (noLogging == false)
                {
                    while (e != null)
                    {
                        m_logger.Error(e.Message);
                        m_logger.Error(e.StackTrace);
                        e = e.InnerException;
                    }
                }
            }



            return(null);
        }