/// <summary>
        /// Synchronizes the contacts.
        /// </summary>
        /// <param name="configuration">The configuration of the synchronization process.</param>
        private static async Task SynchronizeContactsAsync(Configuration configuration)
        {
            try
            {
                System.Console.WriteLine($"Synchronizing contacts started...");
                System.Console.WriteLine();

                // Reads the content of the file
                string contactSyncStatesFile = null;
                if (File.Exists(configuration.ContactSyncStatesFileName))
                {
                    System.Console.WriteLine($"Loading contact sync states from file \"{configuration.ContactSyncStatesFileName}\"...");
                    contactSyncStatesFile = File.ReadAllText(configuration.ContactSyncStatesFileName);
                    System.Console.WriteLine($"Contact sync states loaded.");
                    System.Console.WriteLine();
                }

                // Returns the parsed file
                IEnumerable <ContactSyncState> contactSyncStates = string.IsNullOrWhiteSpace(contactSyncStatesFile) ? new List <ContactSyncState>() : JsonConvert.DeserializeObject <IEnumerable <ContactSyncState> >(contactSyncStatesFile);

                // Creates the services for synchronization
                using (SevDeskService sevDeskService = new SevDeskService(configuration.SevDeskApiToken))
                {
                    using (ExchangeService exchangeService = new ExchangeService(configuration.AzureActiveDirectoryTenant, configuration.AzureClientId, configuration.AzureClientSecret))
                    {
                        // Configures progress reporting
                        Progress <string> progress = new Progress <string>(status => System.Console.WriteLine(status));

                        // Synchronizes the contacts
                        SyncService syncService = new SyncService(sevDeskService, exchangeService);
                        contactSyncStates = await syncService.SynchronizeContactsAsnyc(contactSyncStates, configuration.SevDeskContactCategories, configuration.AzureActiveDirectoryUsers, progress);
                    }
                }

                // Saves the file
                System.Console.WriteLine();
                System.Console.WriteLine($"Saving contact sync states to file \"{configuration.ContactSyncStatesFileName}\"...");
                File.WriteAllText(configuration.ContactSyncStatesFileName, JsonConvert.SerializeObject(contactSyncStates));
                System.Console.WriteLine($"Contact sync states saved.");

                System.Console.WriteLine();
                System.Console.WriteLine($"Synchronizing contacts finished.");
                System.Console.WriteLine();
                System.Console.WriteLine();
            }
            catch (Exception e)
            {
                System.Console.WriteLine();
                throw new InvalidOperationException($"Could not synchronize contacts: {e.Message}", e);
            }
        }
        /// <summary>
        /// Asks the user for the API token for sevDesk.
        /// </summary>
        /// <param name="configuration">The configuration of the synchronization process.</param>
        private static async Task AskForSevDeskApiTokenAsync(Configuration configuration)
        {
            System.Console.WriteLine("1. sevDesk - API Token");
            System.Console.WriteLine("You can get the API token for sevDesk in the user management. Simply click on a user and scroll down to the \"Token\" text field.");
            System.Console.WriteLine();

            // Gets the token from the user
            string sevDeskApiToken = null;

            do
            {
                System.Console.Write("sevDESK API token: ");
                sevDeskApiToken = System.Console.ReadLine();

                // Validates the input
                try
                {
                    System.Console.WriteLine("Validating API token...");
                    using (SevDeskService sevDeskService = new SevDeskService(sevDeskApiToken))
                    {
                        await sevDeskService.GetUsersAsync();
                    }
                    System.Console.WriteLine("API token is valid.");
                }
                catch (Exception e)
                {
                    System.Console.WriteLine($"The sevDESK API token is invalid: {e.Message}");
                    System.Console.WriteLine("Please try a different API token.");
                    System.Console.WriteLine();
                }
            }while (string.IsNullOrWhiteSpace(sevDeskApiToken));

            // Stores the API token
            configuration.SevDeskApiToken = sevDeskApiToken;
            System.Console.WriteLine();
            System.Console.WriteLine();
        }
 /// <summary>
 /// Initializes a new <see cref="SyncService" /> instance.
 /// </summary>
 public SyncService(SevDeskService sevDeskService, ExchangeService exchangeService)
 {
     // Stores the services for further use
     this.SevDeskService  = sevDeskService;
     this.ExchangeService = exchangeService;
 }
        /// <summary>
        /// Asks the user for the contact categories that should be synchronized.
        /// </summary>
        /// <param name="configuration">The configuration of the synchronization process.</param>
        private static async Task AskForSevDeskContactCategoriesAsync(Configuration configuration)
        {
            System.Console.WriteLine("2. sevDesk - Select Categories for Contacts");
            System.Console.WriteLine("Please select the categories of the contacts that should be synchronized with the Exchange Online accounts. Leave the input blank if contacts of all categories should be synchronized; otherwise, provide the numbers of the selection separated by commas.");
            System.Console.WriteLine();

            // Gets the categories from the sevDesk API
            List <string> categoryIds = new List <string>();

            try
            {
                // Downloads all categories
                System.Console.WriteLine("Getting categories from sevDesk...");
                using (SevDeskService sevDeskService = new SevDeskService(configuration.SevDeskApiToken))
                {
                    List <Category> categories   = new List <Category>();
                    CategoryList    categoryList = null;
                    do
                    {
                        categoryList = await sevDeskService.GetCategoriesAsync(categories.Count);

                        categories.AddRange(categoryList);
                    }while (categoryList.Count > categories.Count);

                    // Sorts out the categories
                    List <ContactCategory> contactCategories = categories.OfType <ContactCategory>().ToList();

                    // Prints out the categories
                    System.Console.WriteLine();
                    for (int i = 1; i <= contactCategories.Count; i++)
                    {
                        System.Console.WriteLine($"[{i}]\t{contactCategories[i - 1].Name}");
                    }

                    // Gets the list of categories
                    System.Console.WriteLine();
                    do
                    {
                        System.Console.Write("Categories: ");
                        string categoriesString = System.Console.ReadLine();

                        // Checks whether nothing has been provided
                        if (string.IsNullOrWhiteSpace(categoriesString))
                        {
                            categoryIds = null;
                            break;
                        }

                        // Cycles through the numbers
                        foreach (string categoryString in categoriesString.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList())
                        {
                            if (!int.TryParse(categoryString, out int number) || number < 1 || number > contactCategories.Count())
                            {
                                System.Console.WriteLine("Please provide a valid list of categories.");
                                System.Console.WriteLine();
                                continue;
                            }

                            // Adds the ID of the category
                            categoryIds.Add(contactCategories[number - 1].Id);
                        }
                    }while (categoryIds != null && !categoryIds.Any());
                }
            }
            catch (Exception e)
            {
                System.Console.WriteLine($"Categories could not be retrieved: {e.Message}");
                System.Console.WriteLine("Please try a different API token.");
                System.Console.WriteLine();
                System.Console.WriteLine();

                // Redirects the user to the first step
                configuration.SevDeskApiToken = null;
                await Program.AskForSevDeskApiTokenAsync(configuration);

                await Program.AskForSevDeskContactCategoriesAsync(configuration);

                return;
            }

            // Stores the category IDs
            configuration.SevDeskContactCategories = categoryIds;
            System.Console.WriteLine();
            System.Console.WriteLine();
        }