using Quantum.Entities;
using Quantum.Server.Entities;
using Quantum.Server.Plugins;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;

namespace Quantum.Server.Plugin.Example
{
    [PluginDetails("{24705922-D437-4AA6-B4B2-1F8C8344BA6B}", "Text File Writer Plug-in", "Writes changes made to the team's data to a text file")]
    public class TextFileWriterPlugin : Plugins.Plugin
    {
        #region Description Helper Methods

        static IEnumerable<string> DescribeCreateOrUpdate<TKey, TEntity>(IndexedFrame originalState, IReadOnlyDictionary<TKey, TEntity> original, IndexedFrame newState, IReadOnlyDictionary<TKey, TEntity> createOrUpdate) =>
            (createOrUpdate ?? Enumerable.Empty<KeyValuePair<TKey, TEntity>>()).Select(kv =>
            {
                string header;
                var details = new List<string>();
                if (original.TryGetValue(kv.Key, out var originalEntity))
                    switch (originalEntity)
                    {
                        case Quantum.Entities.Attribute originalAttribute:
                            {
                                header = $"updated {(originalAttribute.IsEnumeration ? "list " : string.Empty)}property \"{originalAttribute.Name}\" ({kv.Key:N})";
                                var newAttribute = kv.Value as Quantum.Entities.Attribute;
                                if (originalAttribute.Name != newAttribute.Name)
                                    details.Add($"changed name to \"{newAttribute.Name}\"");
                                if (originalAttribute.Notes != newAttribute.Notes)
                                    details.Add($"changed notes from \"{originalAttribute.Notes}\" to \"{newAttribute.Notes}\"");
                            }
                            break;
                        case string originalAttributeValue:
                            {
                                var key = (AttributeObjectCompositeKey)(object)kv.Key;
                                header = $"updated property \"{(newState.Attributes.TryGetValue(key.AttributeId, out var newAttribute) ? newAttribute : originalState.Attributes.TryGetValue(key.AttributeId, out var originalAttribute) ? originalAttribute : throw new Exception("Failed to find referenced attribute")).Name}\" ({key.AttributeId:N}) value for work item \"{(newState.Items.TryGetValue(key.ObjectId, out var newItem) ? newItem : originalState.Items.TryGetValue(key.ObjectId, out var originalItem) ? originalItem : throw new Exception("Failed to find referenced item")).Name}\" ({key.ObjectId:N})";
                                var newAttributeValue = kv.Value as string;
                                if (originalAttributeValue != newAttributeValue)
                                    details.Add($"from \"{originalAttributeValue}\" to \"{newAttributeValue}\"");
                            }
                            break;
                        case Guid originalAttributeValue:
                            {
                                var key = (AttributeObjectCompositeKey)(object)kv.Key;
                                header = $"updated list property \"{(newState.Attributes.TryGetValue(key.AttributeId, out var newAttribute) ? newAttribute : originalState.Attributes.TryGetValue(key.AttributeId, out var originalAttribute) ? originalAttribute : throw new Exception("Failed to find referenced attribute")).Name}\" ({key.AttributeId:N}) value for work item \"{(newState.Items.TryGetValue(key.ObjectId, out var newItem) ? newItem : originalState.Items.TryGetValue(key.ObjectId, out var originalItem) ? originalItem : throw new Exception("Failed to find referenced item")).Name}\" ({key.ObjectId:N})";
                                var newAttributeValue = (Guid)(object)kv.Value;
                                if (originalAttributeValue != newAttributeValue)
                                    details.Add($"from \"{(newState.EnumerationValues.TryGetValue(originalAttributeValue, out var newEnumerationValueFrom) ? newEnumerationValueFrom : originalState.EnumerationValues.TryGetValue(originalAttributeValue, out var originalEnumerationValueFrom) ? originalEnumerationValueFrom : throw new Exception("Failed to find referenced enumeration value")).CorrelatedEntity.Name}\" to \"{(newState.EnumerationValues.TryGetValue(newAttributeValue, out var newEnumerationValueTo) ? newEnumerationValueTo : originalState.EnumerationValues.TryGetValue(newAttributeValue, out var originalEnumerationValueTo) ? originalEnumerationValueTo : throw new Exception("Failed to find referenced enumeration value")).CorrelatedEntity.Name}\"");
                            }
                            break;
                        case AttributeCorrelation<EnumerationValue> originalEnumerationValue:
                            {
                                var newEnumerationValue = kv.Value as AttributeCorrelation<EnumerationValue>;
                                header = $"updated list value \"{originalEnumerationValue.CorrelatedEntity.Name}\" ({kv.Key:N}) for list property \"{(newState.Attributes.TryGetValue(originalEnumerationValue.AttributeId, out var newEnumeratedAttribute) ? newEnumeratedAttribute : originalState.Attributes.TryGetValue(originalEnumerationValue.AttributeId, out var originalEnumeratedAttribute) ? originalEnumeratedAttribute : throw new Exception("Failed to find referenced attribute")).Name}\" ({originalEnumerationValue.AttributeId:N})";
                                if (originalEnumerationValue.CorrelatedEntity.Name != newEnumerationValue.CorrelatedEntity.Name)
                                    details.Add($"changed name to \"{newEnumerationValue.CorrelatedEntity.Name}\"");
                            }
                            break;
                        case ItemPersonCorrelation<Interest> originalInterest:
                            {
                                header = $"updated assignment of \"{(newState.Items.TryGetValue(originalInterest.ItemId, out var newItem) ? newItem : originalState.Items.TryGetValue(originalInterest.ItemId, out var originalItem) ? originalItem : throw new Exception("Failed to find referenced item")).Name}\" ({originalInterest.ItemId:N}) to \"{(newState.People.TryGetValue(originalInterest.PersonId, out var newPerson) ? newPerson : originalState.People.TryGetValue(originalInterest.PersonId, out var originalPerson) ? originalPerson : throw new Exception("Failed to find referenced person")).Name}\" ({originalInterest.PersonId:N})";
                                var newInterest = kv.Value as ItemPersonCorrelation<Interest>;
                                if (originalInterest.CorrelatedEntity.Estimate != newInterest.CorrelatedEntity.Estimate)
                                    details.Add($"changed estimate from \"{originalInterest.CorrelatedEntity.Estimate}\" to \"{newInterest.CorrelatedEntity.Estimate}\"");
                                if (originalInterest.CorrelatedEntity.Due != newInterest.CorrelatedEntity.Due)
                                    details.Add($"changed due date and time from \"{originalInterest.CorrelatedEntity.Due:O}\" to \"{newInterest.CorrelatedEntity.Due:O}\"");
                                if (originalInterest.CorrelatedEntity.Complete != newInterest.CorrelatedEntity.Complete)
                                    details.Add($"changed complete date and time from \"{originalInterest.CorrelatedEntity.Complete:O}\" to \"{newInterest.CorrelatedEntity.Complete:O}\"");
                            }
                            break;
                        case Item originalItem:
                            {
                                header = $"updated work item \"{originalItem.Name}\" ({kv.Key:N})";
                                var newItem = kv.Value as Item;
                                if (originalItem.Name != newItem.Name)
                                    details.Add($"changed name to \"{newItem.Name}\"");
                                if (originalItem.Notes != newItem.Notes)
                                    details.Add($"changed notes from \"{originalItem.Notes}\" to \"{newItem.Notes}\"");
                            }
                            break;
                        case ItemPersonCorrelation<Period> originalPeriod:
                            {
                                header = $"updated time slice for \"{(newState.People.TryGetValue(originalPeriod.PersonId, out var newPerson) ? newPerson : originalState.People.TryGetValue(originalPeriod.PersonId, out var originalPerson) ? originalPerson : throw new Exception("Failed to find referenced person")).Name}\" ({originalPeriod.PersonId:N}) logged under \"{(newState.Items.TryGetValue(originalPeriod.ItemId, out var newItem) ? newItem : originalState.Items.TryGetValue(originalPeriod.ItemId, out var originalItem) ? originalItem : throw new Exception("Failed to find referenced item")).Name}\" ({originalPeriod.ItemId:N})";
                                var newPeriod = kv.Value as ItemPersonCorrelation<Period>;
                                if (originalPeriod.ItemId != newPeriod.ItemId)
                                    details.Add($"changed work item to \"{(newState.Items.TryGetValue(newPeriod.ItemId, out var newPeriodNewItem) ? newPeriodNewItem : originalState.Items.TryGetValue(newPeriod.ItemId, out var newPeriodOriginalItem) ? newPeriodOriginalItem : throw new Exception("Failed to find referenced item")).Name}\" ({newPeriod.ItemId:N})");
                                if (originalPeriod.CorrelatedEntity.Start != newPeriod.CorrelatedEntity.Start)
                                    details.Add($"changed end date and time from \"{originalPeriod.CorrelatedEntity.Start:O}\" to \"{newPeriod.CorrelatedEntity.Start:O}\"");
                                if (originalPeriod.CorrelatedEntity.End != newPeriod.CorrelatedEntity.End)
                                    details.Add($"changed end date and time from \"{originalPeriod.CorrelatedEntity.End:O}\" to \"{newPeriod.CorrelatedEntity.End:O}\"");
                                if (originalPeriod.CorrelatedEntity.Notes != newPeriod.CorrelatedEntity.Notes)
                                    details.Add($"changed notes from \"{originalPeriod.CorrelatedEntity.Notes}\" to \"{newPeriod.CorrelatedEntity.Notes}\"");
                            }
                            break;
                        case Person originalPerson:
                            {
                                header = $"updated person \"{originalPerson.Name}\" ({kv.Key:N})";
                                var newPerson = kv.Value as Person;
                                if (originalPerson.Name != newPerson.Name)
                                    details.Add($"changed name to \"{newPerson.Name}\"");
                                if (originalPerson.WentAway != newPerson.WentAway)
                                    details.Add($"changed away date and time from \"{originalPerson.WentAway:O}\" to \"{newPerson.WentAway:O}\"");
                            }
                            break;
                        default:
                            throw new NotSupportedException();
                    }
                else
                    switch (kv.Value)
                    {
                        case Quantum.Entities.Attribute newAttribute:
                            {
                                header = $"created {(newAttribute.IsEnumeration ? "list " : string.Empty)}property \"{newAttribute.Name}\" ({kv.Key:N})";
                                if (newAttribute.Notes != null)
                                    details.Add($"set notes to \"{newAttribute.Notes}\"");
                            }
                            break;
                        case string newAttributeValue:
                            {
                                var key = (AttributeObjectCompositeKey)(object)kv.Key;
                                header = $"created property \"{(newState.Attributes.TryGetValue(key.AttributeId, out var newAttribute) ? newAttribute : originalState.Attributes.TryGetValue(key.AttributeId, out var originalAttribute) ? originalAttribute : throw new Exception("Failed to find referenced attribute")).Name}\" ({key.AttributeId:N}) value for work item \"{(newState.Items.TryGetValue(key.ObjectId, out var newItem) ? newItem : originalState.Items.TryGetValue(key.ObjectId, out var originalItem) ? originalItem : throw new Exception("Failed to find referenced item")).Name}\" ({key.ObjectId:N}) set to \"{newAttributeValue}\"";
                            }
                            break;
                        case Guid newAttributeValue:
                            {
                                var key = (AttributeObjectCompositeKey)(object)kv.Key;
                                header = $"updated list property \"{(newState.Attributes.TryGetValue(key.AttributeId, out var newAttribute) ? newAttribute : originalState.Attributes.TryGetValue(key.AttributeId, out var originalAttribute) ? originalAttribute : throw new Exception("Failed to find referenced attribute")).Name}\" ({key.AttributeId:N}) value for work item \"{(newState.Items.TryGetValue(key.ObjectId, out var newItem) ? newItem : originalState.Items.TryGetValue(key.ObjectId, out var originalItem) ? originalItem : throw new Exception("Failed to find referenced item")).Name}\" ({key.ObjectId:N}) set to \"{(newState.EnumerationValues.TryGetValue(newAttributeValue, out var newEnumerationValueTo) ? newEnumerationValueTo : originalState.EnumerationValues.TryGetValue(newAttributeValue, out var originalEnumerationValueTo) ? originalEnumerationValueTo : throw new Exception("Failed to find referenced enumeration value")).CorrelatedEntity.Name}\"";
                            }
                            break;
                        case AttributeCorrelation<EnumerationValue> newEnumerationValue:
                            {
                                header = $"created list value \"{newEnumerationValue.CorrelatedEntity.Name}\" ({kv.Key:N}) for list property \"{(newState.Attributes.TryGetValue(newEnumerationValue.AttributeId, out var newEnumeratedAttribute) ? newEnumeratedAttribute : throw new Exception("Failed to find referenced attribute")).Name}\" ({newEnumerationValue.AttributeId:N})";
                            }
                            break;
                        case ItemPersonCorrelation<Interest> newInterest:
                            {
                                header = $"created assignment of \"{(newState.Items.TryGetValue(newInterest.ItemId, out var newItem) ? newItem : originalState.Items.TryGetValue(newInterest.ItemId, out var originalItem) ? originalItem : throw new Exception("Failed to find referenced item")).Name}\" ({newInterest.ItemId:N}) to \"{(newState.People.TryGetValue(newInterest.PersonId, out var newPerson) ? newPerson : originalState.People.TryGetValue(newInterest.PersonId, out var originalPerson) ? originalPerson : throw new Exception("Failed to find referenced person")).Name}\" ({newInterest.PersonId:N})";
                                if (newInterest.CorrelatedEntity.Estimate != null)
                                    details.Add($"set estimate to \"{newInterest.CorrelatedEntity.Estimate}\"");
                                if (newInterest.CorrelatedEntity.Due != null)
                                    details.Add($"set due date and time to \"{newInterest.CorrelatedEntity.Due:O}\"");
                                if (newInterest.CorrelatedEntity.Complete != null)
                                    details.Add($"set complete date and time to \"{newInterest.CorrelatedEntity.Complete:O}\"");
                            }
                            break;
                        case Item newItem:
                            {
                                header = $"created work item \"{newItem.Name}\" ({kv.Key:N})";
                                if (newItem.Notes != null)
                                    details.Add($"set notes to \"{newItem.Notes}\"");
                            }
                            break;
                        case ItemPersonCorrelation<Period> newPeriod:
                            {
                                header = $"created time slice for \"{(newState.People.TryGetValue(newPeriod.PersonId, out var newPerson) ? newPerson : originalState.People.TryGetValue(newPeriod.PersonId, out var originalPerson) ? originalPerson : throw new Exception("Failed to find referenced person")).Name}\" ({newPeriod.PersonId:N}) logged under \"{(newState.Items.TryGetValue(newPeriod.ItemId, out var newItem) ? newItem : originalState.Items.TryGetValue(newPeriod.ItemId, out var originalItem) ? originalItem : throw new Exception("Failed to find referenced item")).Name}\" ({newPeriod.ItemId:N})";
                                details.Add($"set start date and time to \"{newPeriod.CorrelatedEntity.Start:O}\"");
                                details.Add($"set end date and time to \"{newPeriod.CorrelatedEntity.End:O}\"");
                                if (newPeriod.CorrelatedEntity.Notes != null)
                                    details.Add($"set notes to \"{newPeriod.CorrelatedEntity.Notes}\"");
                            }
                            break;
                        case Person newPerson:
                            {
                                header = $"created person \"{newPerson.Name}\" ({kv.Key:N})";
                                if (newPerson.WentAway != null)
                                    details.Add($"set away date and time to \"{newPerson.WentAway:O}\"");
                            }
                            break;
                        default:
                            throw new NotSupportedException();
                    }
                return $"{header}{(details.Any() ? $" and {string.Join(", ", details)}" : string.Empty)}";
            });

        static IEnumerable<string> DescribeDelete<TKey>(Type entityType, IndexedFrame originalState, IReadOnlyCollection<TKey> delete) =>
            (delete ?? Enumerable.Empty<TKey>()).Select(key =>
            {
                string header;
                var details = new List<string>();
                if (entityType == typeof(Quantum.Entities.Attribute))
                {
                    var attributeId = (Guid)(object)key;
                    var attribute = originalState.Attributes[attributeId];
                    header = $"{(attribute.IsEnumeration ? "list " : string.Empty)}property \"{attribute.Name}\" ({attributeId:N})";
                    if (attribute.Notes != null)
                        details.Add($"notes set to \"{attribute.Notes}\"");
                }
                else if (entityType == typeof(object))
                {
                    var attributeValueKey = (AttributeObjectCompositeKey)(object)key;
                    var attributeValue = originalState.AttributeValues[attributeValueKey];
                    if (attributeValue is Guid enumerationValueId)
                        header = $"list property \"{originalState.Attributes[attributeValueKey.AttributeId].Name}\" ({attributeValueKey.AttributeId:N}) value for \"{originalState.Items[attributeValueKey.ObjectId].Name}\" ({attributeValueKey.ObjectId:N}) which was set to \"{originalState.EnumerationValues[enumerationValueId].CorrelatedEntity.Name}\" ({enumerationValueId:N})";
                    else if (attributeValue is string stringAttributeValue)
                        header = $"property \"{originalState.Attributes[attributeValueKey.AttributeId].Name}\" ({attributeValueKey.AttributeId:N}) value for \"{originalState.Items[attributeValueKey.ObjectId].Name}\" ({attributeValueKey.ObjectId:N}) which was set to \"{stringAttributeValue}\"";
                    else
                        throw new Exception("Unrecognized attribute value type");
                }
                else if (entityType == typeof(AttributeCorrelation<EnumerationValue>))
                {
                    var enumerationValueId = (Guid)(object)key;
                    var enumerationValue = originalState.EnumerationValues[enumerationValueId];
                    header = $"list value \"{enumerationValue.CorrelatedEntity.Name}\" ({enumerationValueId:N}) for list property \"{originalState.Attributes[enumerationValue.AttributeId].Name}\" ({enumerationValue.AttributeId:N})";
                }
                else if (entityType == typeof(ItemPersonCorrelation<Interest>))
                {
                    var interestId = (Guid)(object)key;
                    var interest = originalState.Interests[interestId];
                    header = $"assignment of \"{originalState.Items[interest.ItemId].Name}\" ({interest.ItemId}) to \"{originalState.People[interest.PersonId].Name}\" ({interest.PersonId:N})";
                    if (interest.CorrelatedEntity.Estimate != null)
                        details.Add($"estimate set to \"{interest.CorrelatedEntity.Estimate}\"");
                    if (interest.CorrelatedEntity.Due != null)
                        details.Add($"due date and time set to \"{interest.CorrelatedEntity.Due:O}\"");
                    if (interest.CorrelatedEntity.Complete != null)
                        details.Add($"complete date and time set to \"{interest.CorrelatedEntity.Complete:O}\"");
                }
                else if (entityType == typeof(Item))
                {
                    var itemId = (Guid)(object)key;
                    var item = originalState.Items[itemId];
                    header = $"work item \"{item.Name}\" ({itemId:N})";
                    if (item.Notes != null)
                        details.Add($"notes set to \"{item.Notes}\"");
                }
                else if (entityType == typeof(ItemPersonCorrelation<Period>))
                {
                    var periodId = (Guid)(object)key;
                    var period = originalState.Periods[periodId];
                    header = $"time slice for \"{originalState.People[period.PersonId].Name}\" ({period.PersonId:N}) logged under \"{originalState.Items[period.ItemId].Name}\" ({period.ItemId:N})";
                    details.Add($"start date and time set to \"{period.CorrelatedEntity.Start:O}\"");
                    details.Add($"end date and time set to \"{period.CorrelatedEntity.End:O}\"");
                    if (period.CorrelatedEntity.Notes != null)
                        details.Add($"notes set to \"{period.CorrelatedEntity.Notes}\"");
                }
                else if (entityType == typeof(Person))
                {
                    var personId = (Guid)(object)key;
                    var person = originalState.People[personId];
                    header = $"person \"{person.Name}\" ({personId:N})";
                    if (person.WentAway != null)
                        details.Add($"away date and time set to {person.WentAway:O}");
                }
                else
                    throw new NotSupportedException();
                return $"deleted {header}{(details.Any() ? $" which had {string.Join(", ", details)}" : string.Empty)}";
            });

        static IEnumerable<string> DescribeTiming(bool started, IndexedFrame originalState, IndexedFrame newState, IEnumerable<Guid> periodIds) =>
            (periodIds ?? Enumerable.Empty<Guid>()).Select(periodId =>
            {
                var period = originalState.Periods.TryGetValue(periodId, out var originalPeriod) ? originalPeriod : newState.Periods.TryGetValue(periodId, out var newPeriod) ? newPeriod : throw new Exception("Failed to find referenced period");
                var item = originalState.Items.TryGetValue(period.ItemId, out var originalItem) ? originalItem : newState.Items.TryGetValue(period.ItemId, out var newItem) ? newItem : throw new Exception("Failed to find referenced item");
                var person = originalState.People.TryGetValue(period.PersonId, out var originalPerson) ? originalPerson : newState.People.TryGetValue(period.PersonId, out var newPerson) ? newPerson : throw new Exception("Failed to find reference person");
                return $"{(started ? "started" : "stopped")} timing time slice ({periodId:N}) which is for \"{person.Name}\" ({period.PersonId:N}) logged under \"{item.Name}\" ({period.ItemId:N})";
            });

        #endregion Description Helper Methods

        DirectoryInfo auditDirectory;
        FileInfo auditFile;

        public override async Task<object> OnApiRequestAsync(HttpRequestMessage httpRequestMessage)
        {
            if (httpRequestMessage.Method == HttpMethod.Delete)
            {
                auditDirectory.Refresh();
                if (auditDirectory.Exists)
                {
                    auditFile.Refresh();
                    if (auditFile.Exists)
                        auditFile.Delete();
                }
                return HttpStatusCode.Accepted;
            }
            else if (httpRequestMessage.Method == HttpMethod.Get)
            {
                auditDirectory.Refresh();
                if (!auditDirectory.Exists)
                    return HttpStatusCode.NotFound;
                auditFile.Refresh();
                if (!auditFile.Exists)
                    return HttpStatusCode.NotFound;
                using (var auditFileStreamReader = new StreamReader(auditFile.FullName))
                    return new HttpResponseMessage(HttpStatusCode.OK)
                    {
                        Content = new StringContent(await auditFileStreamReader.ReadToEndAsync().ConfigureAwait(false), Encoding.UTF8, "text/plain")
                    };
            }
            return await base.OnApiRequestAsync(httpRequestMessage).ConfigureAwait(false);
        }

        public override async Task<ChangeSet> OnChangeSetAppliedAsync(IndexedFrame originalState, ChangeSet changesApplied, ChangeSet inScopeChangesApplied)
        {
            auditDirectory.Refresh();
            if (!auditDirectory.Exists)
                auditDirectory.Create();
            string actor;
            switch (changesApplied)
            {
                case ApiInitiatedChangeSet apiChangesApplied:
                    actor = $"Third-party via API (using access grant {apiChangesApplied.ApiAccessGrantId:N})";
                    break;
                case PairedClientInitiatedChangeSet pairedClientChangesApplied:
                    actor = $"{originalState.People[pairedClientChangesApplied.PersonId].Name} via Grindstone";
                    break;
                case PersonInitiatedChangeSet personChangesApplied:
                    actor = originalState.People[personChangesApplied.PersonId].Name;
                    break;
                case PluginInitiatedChangeSet pluginChangesApplied:
                    actor = $"Plugin {pluginChangesApplied.PluginId:N}";
                    break;
                default:
                    actor = "Unknown actor";
                    break;
            }
            var actions = new List<string>();
            var newState = await SnapshotAsync().ConfigureAwait(false);
            actions.AddRange(DescribeCreateOrUpdate(originalState, originalState.Attributes, newState, inScopeChangesApplied.CreateOrUpdateAttributes));
            actions.AddRange(DescribeCreateOrUpdate(originalState, originalState.EnumerationValues, newState, inScopeChangesApplied.CreateOrUpdateEnumerationValues));
            actions.AddRange(DescribeCreateOrUpdate(originalState, originalState.Items, newState, inScopeChangesApplied.CreateOrUpdateItems));
            actions.AddRange(DescribeCreateOrUpdate(originalState, originalState.AttributeValues, newState, inScopeChangesApplied.CreateOrUpdateAttributeValues));
            actions.AddRange(DescribeCreateOrUpdate(originalState, originalState.People, newState, inScopeChangesApplied.CreateOrUpdatePeople));
            actions.AddRange(DescribeCreateOrUpdate(originalState, originalState.Interests, newState, inScopeChangesApplied.CreateOrUpdateInterests));
            actions.AddRange(DescribeCreateOrUpdate(originalState, originalState.Periods, newState, inScopeChangesApplied.CreateOrUpdatePeriods));
            actions.AddRange(DescribeDelete(typeof(ItemPersonCorrelation<Period>), originalState, inScopeChangesApplied.DeletePeriods));
            actions.AddRange(DescribeDelete(typeof(ItemPersonCorrelation<Interest>), originalState, inScopeChangesApplied.DeleteInterests));
            actions.AddRange(DescribeDelete(typeof(Person), originalState, inScopeChangesApplied.DeletePeople));
            actions.AddRange(DescribeDelete(typeof(object), originalState, inScopeChangesApplied.DeleteAttributeValues));
            actions.AddRange(DescribeDelete(typeof(Item), originalState, inScopeChangesApplied.DeleteItems));
            actions.AddRange(DescribeDelete(typeof(AttributeCorrelation<EnumerationValue>), originalState, inScopeChangesApplied.DeleteEnumerationValues));
            actions.AddRange(DescribeDelete(typeof(Quantum.Entities.Attribute), originalState, inScopeChangesApplied.DeleteAttributes));
            actions.AddRange(DescribeTiming(false, originalState, newState, inScopeChangesApplied.PeriodsStopTiming));
            actions.AddRange(DescribeTiming(true, originalState, newState, inScopeChangesApplied.PeriodsStartTiming));
            using (var auditFileStreamWriter = new StreamWriter(auditFile.FullName, true))
                await auditFileStreamWriter.WriteLineAsync($"At {DateTime.UtcNow:O} {actor} performed the following actions: {string.Join("; ", actions)}").ConfigureAwait(false);
            return await base.OnChangeSetAppliedAsync(originalState, changesApplied, inScopeChangesApplied).ConfigureAwait(false);
        }

        public override Task OnLoadedAsync()
        {
            auditDirectory = new DirectoryInfo(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "GSS Example Text File Writer Plug-in"));
            auditFile = new FileInfo(Path.Combine(auditDirectory.FullName, $"{TeamId:N}.log"));
            return base.OnLoadedAsync();
        }
    }
}
