/// <summary>
        /// Gets details of a user from the LMS.
        /// </summary>
        /// <param name="userId_or_email">The user's ID or email address.</param>
        /// <param name="forceAsUseId">If set to true, search will be for a user by their ID only (even if it contains @. otherwise this is auto-determined)</param>
        /// <returns>The user's details. Note that not all fields are returned from the LMS.</returns>
        public NetDimensionsUser GetUser(string userIdOrEmail, bool forceAsUseId = false)
        {
            NetDimensionsUser user          = null;
            string            serviceApiUrl = string.Format(NetDimensionsConstants.ApiGetUsers, this.LmsUrl);

            using (WebClient apiWebClient = new WebClient())
            {
                apiWebClient.Headers[HttpRequestHeader.ContentType] = "text/plain";
                apiWebClient.Credentials = new NetworkCredential(this.UserAuthUser, this.UserAuthPass);
                if (!userIdOrEmail.Contains("@") || forceAsUseId)
                {
                    apiWebClient.QueryString.Add("userId", userIdOrEmail);
                }
                else
                {
                    apiWebClient.QueryString.Add("email", userIdOrEmail);
                }

                apiWebClient.QueryString.Add("format", "json");
                string jsonResponse = Encoding.UTF8.GetString(apiWebClient.DownloadData(serviceApiUrl));

                dynamic dynamicResponseObject = JObject.Parse(jsonResponse);
                if ((dynamicResponseObject != null) && (dynamicResponseObject["users"] != null) && (dynamicResponseObject["users"].Count > 0) && (dynamicResponseObject["users"][0] != null))
                {
                    dynamic jsonUser = dynamicResponseObject["users"][0];
                    user = JsonConvert.DeserializeObject <NetDimensionsUser>(jsonUser.ToString());

                    if (jsonUser["attributes"] != null && jsonUser["attributes"] is JArray && ((JArray)jsonUser["attributes"]).Count == 8)
                    {
                        dynamic userAttributes = jsonUser["attributes"];
                        user.UserAttr1 = userAttributes[0]["description"].ToString();
                        user.UserAttr2 = userAttributes[1]["description"].ToString();
                        user.UserAttr3 = userAttributes[2]["description"].ToString();
                        user.UserAttr4 = userAttributes[3]["description"].ToString();
                        user.UserAttr5 = userAttributes[4]["description"].ToString();
                        user.UserAttr6 = userAttributes[5]["description"].ToString();
                        user.UserAttr7 = userAttributes[6]["description"].ToString();
                        user.UserAttr8 = userAttributes[7]["description"].ToString();
                    }
                }
            }
            return(user);
        }
        /// <summary>
        /// Constructs the CSV string of multiple user details and action to perform (add/update/delete) for API calls.
        /// </summary>
        /// <param name="action">The action to perform (e.g. add/update/delete).</param>
        /// <param name="users">The user object to perform the action on.</param>
        /// <returns>CSV string of multiple user details for API calls.</returns>
        public static string GetCsv(string action, NetDimensionsUser[] users)
        {
            StringBuilder csvSb = new StringBuilder();
            if (users.Length == 0)
            {
                return string.Empty;
            }

            PropertyInfo[] properties = typeof(NetDimensionsUser).GetProperties();
            string[] csvHeaders = new string[properties.Length + 1];
            csvHeaders[0] = "Actions";
            string[][] csvData = new string[users.Length][];
            int actualHeaderIndex = 1;
            foreach (PropertyInfo property in properties)
            {
                string fieldName = property.Name;
                object[] attributes = property.GetCustomAttributes(typeof(CsvFieldAttribute), true);
                if (attributes.Length == 0)
                {
                    continue;
                }

                CsvFieldAttribute att = (CsvFieldAttribute)attributes[0];
                if ((att != null) && (!string.IsNullOrWhiteSpace(att.CsvFieldHeader)))
                {
                    fieldName = att.CsvFieldHeader;
                }

                csvHeaders[actualHeaderIndex] = fieldName;
                for (int userRow = 0; userRow < users.Length; userRow++)
                {
                    if (csvData[userRow] == null)
                    {
                        csvData[userRow] = new string[properties.Length + 1];
                        csvData[userRow][0] = action;
                    }

                    object propValue = property.GetValue(users[userRow], null);
                    if (propValue != null)
                    {
                        string valueString;
                        if (property.PropertyType == typeof(DateTime?))
                        {
                            if (((DateTime?)propValue).HasValue)
                            {
                                valueString = ((DateTime?)propValue).Value.ToString(
                                    "dd-MMM-yyyy",
                                    CultureInfo.CreateSpecificCulture("en-US"))
                                    .ToLower();
                            }
                            else
                            {
                                valueString = string.Empty;
                            }
                        }
                        else if (property.PropertyType == typeof(bool))
                        {
                            valueString = ((bool)propValue) ? "Y" : "N";
                        }
                        else if (property.PropertyType == typeof(RegionInfo))
                        {
                            if ((RegionInfo)propValue != null)
                            {
                                valueString = ((RegionInfo)propValue).ThreeLetterISORegionName;
                            }
                            else
                            {
                                valueString = string.Empty;
                            }
                        }
                        else if (property.PropertyType == typeof(CultureInfo))
                        {
                            if (((CultureInfo)propValue) == null)
                            {
                                valueString = new CultureInfo("en-US").Name.Replace("-", "_");
                            }
                            else
                            {
                                valueString = ((CultureInfo)propValue).Name.Replace("-", "_");
                            }
                        }
                        else
                        {
                            if ((propValue != null) && (!string.IsNullOrWhiteSpace(propValue.ToString())))
                            {
                                valueString = "\"" + propValue.ToString() + "\"";
                            }
                            else
                            {
                                valueString = string.Empty;
                            }
                        }

                        csvData[userRow][actualHeaderIndex] = valueString;
                    }
                }

                actualHeaderIndex++;
            }

            csvSb.Append(string.Join(",", csvHeaders));
            csvSb.Append("\n");
            for (int u = 0; u < users.Length; u++)
            {
                if (u > 0)
                {
                    csvSb.Append("\n");
                }

                csvSb.Append(string.Join(",", csvData[u]));
            }

            return csvSb.ToString();
        }
        /// <summary>
        /// Creates users in the LMS. Also adds their related organizations if needed, and assigns the users to them.
        /// </summary>
        /// <param name="users">The users to create.</param>
        /// <returns>The results of the users creation. Including any errors and warnings from the LMS.</returns>
        public UserActionResults CreateUsers(NetDimensionsUser[] users)
        {
            if (users.Length == 0)
            {
                return(null);
            }
            if ((users.Any(u => string.IsNullOrWhiteSpace(u.UserID))) ||
                (users.Any(u => string.IsNullOrWhiteSpace(u.LastName)) ||
                 (users.Any(u => string.IsNullOrWhiteSpace(u.FirstName))) ||
                 (users.Any(u => string.IsNullOrWhiteSpace(u.Password))) ||
                 (users.Any(u => string.IsNullOrWhiteSpace(u.Email)))))
            {
                throw new ArgumentException("To create a user, the following fields are mandatory: UserID, LastName, FirstName, Password, Email");
            }

            UserActionResults result = null;

            string serviceApiUrl = string.Format(NetDimensionsConstants.ApiUsersCsv, this.LmsUrl);
            string usersCsv      = NetDimensionsUser.GetCsv(NetDimensionsConstants.UserActions.AddOrUpdate, users);

            using (WebClient apiWebClient = new WebClient())
            {
                apiWebClient.Headers[HttpRequestHeader.ContentType] = "text/plain";
                apiWebClient.Credentials = new NetworkCredential(this.UserAuthUser, this.SysAuthKey);

                string csvResponse = Encoding.UTF8.GetString(apiWebClient.UploadData(
                                                                 serviceApiUrl,
                                                                 "POST",
                                                                 Encoding.ASCII.GetBytes(usersCsv)));

                result = new UserActionResults(csvResponse);
            }

            Dictionary <NetDimensionsOrganization, List <string> > organizationUsers = new Dictionary <NetDimensionsOrganization, List <string> >();

            foreach (NetDimensionsUser user in users)
            {
                if (user.Organization != null)
                {
                    NetDimensionsOrganization organizationInList = organizationUsers.Keys.FirstOrDefault(o => o.Code == user.Organization.Code);
                    if (organizationInList == null)
                    {
                        organizationUsers.Add(user.Organization, new List <string>());
                    }
                    organizationInList = organizationUsers.Keys.FirstOrDefault(o => o.Code == user.Organization.Code);
                    if (string.IsNullOrWhiteSpace(organizationInList.Description))
                    {
                        organizationInList.Description = user.Organization.Description;
                    }

                    if (!organizationUsers[organizationInList].Contains(user.UserID))
                    {
                        organizationUsers[organizationInList].Add(user.UserID);
                    }
                }
            }
            foreach (KeyValuePair <NetDimensionsOrganization, List <string> > organization in organizationUsers)
            {
                this.CreateOrganization(organization.Key.Code, organization.Key.Description);
                this.AddUsersToOrganization(organization.Value.ToArray(), organization.Key.Code);
            }
            return(result);
        }