private static async Task WriteTimetableAsync(Person person, Settings settings, int line)
        {
            var service = new GoogleCalendarService(person.Email);

            var calendar = await service.GetTimetableCalendarIdAsync() ?? await service.CreateTimetableCalendarAsync();

            ConsoleHelper.WriteProgress(line, 1);

            var fields   = "id,summary,location,start(dateTime),end(dateTime)";
            var existing = await service.GetFutureEvents(calendar, DateTime.Today, fields);

            var expected = CreateExpectedEvents(person, settings);

            ConsoleHelper.WriteProgress(line, 2);

            var comparer = new EventComparer();

            var obsolete = existing.Except(expected, comparer);
            var missing  = expected.Except(existing, comparer);

            await service.InsertEventsAsync(calendar, missing);

            await service.DeleteEventsAsync(calendar, obsolete);

            ConsoleHelper.WriteProgress(line, 3);
        }
        private static async Task MainAsync()
        {
#if !DEBUG
            try
            {
#endif
            Console.Clear();
            Console.CursorVisible = false;

            Console.WriteLine("TIMETABLE CALENDAR GENERATOR\n");

            var settings = await LoadSettingsAsync();

            var students = await LoadStudentsAsync();

            var teachers = await LoadTeachersAsync();

            GoogleCalendarService.Configure(settings.ServiceAccountKey);

            Console.WriteLine("\nSetting up calendars:");

            var tasks     = new List <Task>();
            var throttler = new SemaphoreSlim(initialCount: simultaneousRequests);

            var people = students.Concat(teachers).ToList();

            Console.SetBufferSize(Console.BufferWidth, Math.Max(headerHeight + people.Count + 1, Console.BufferHeight));

            for (var i = 0; i < people.Count; i++)
            {
                var countLocal = i;
                await Task.Delay(10);

                await throttler.WaitAsync();

                var person = people[countLocal];
                tasks.Add(Task.Run(async() =>
                {
                    try
                    {
                        var line = countLocal + headerHeight;
                        ConsoleHelper.WriteDescription(line, $"({countLocal + 1}/{people.Count}) {person.Email}");
                        ConsoleHelper.WriteProgress(line, 0);
                        for (var attempt = 1; attempt <= maxAttempts; attempt++)
                        {
                            try
                            {
                                await WriteTimetableAsync(person, settings, line);
                                break;
                            }
                            catch (Google.GoogleApiException) when(attempt < maxAttempts)
                            {
                                var backoff = retryFirst * (int)Math.Pow(retryExponent, attempt - 1);
                                ConsoleHelper.WriteStatus(line, $"Error. Retrying ({attempt} of {maxAttempts - 1})...", ConsoleColor.DarkYellow);
                                await Task.Delay(backoff);
                            }
                            catch (Exception exc)
                            {
                                ConsoleHelper.WriteStatus(line, $"Failed. {exc.Message}", ConsoleColor.Red);
                                break;
                            }
                        }
                    }
                    finally
                    {
                        throttler.Release();
                    }
                }));
            }
            await Task.WhenAll(tasks);

            Console.SetCursorPosition(0, headerHeight + people.Count);
            Console.WriteLine("\nCalendar generation complete.\n");
#if !DEBUG
        }

        catch (Exception exc)
        {
            ConsoleHelper.WriteError(exc.Message);
        }
#endif
            if (waitForInput)
            {
                Console.ReadKey();
            }
        }