public override Entity Resolve(Entity owner, string key, bool fallback = false)
        {
            VirtualFile file = owner.Value <VirtualFile>();

            if (file != null)
            {
                if (key == EntityKeys.PathKey)
                {
                    return(owner.Create(key, file.Parent.FullName));
                }

                if (file.HasPropertyValueEntity(MapFileAccessKey(key)))
                {
                    return(owner.PropertyValueEntity(MapFileAccessKey(key), file, key));
                }
            }

            VirtualDirectory directory = owner.Value <VirtualDirectory>();

            if (directory != null)
            {
                if (key == EntityKeys.PathKey)
                {
                    return(owner.Create(key, directory.FullName));
                }

                if (directory.HasPropertyValueEntity(MapFileAccessKey(key)))
                {
                    return(owner.PropertyValueEntity(MapFileAccessKey(key), directory, key));
                }
            }

            switch (key)
            {
            case EntityKeys.NewlineKey:
                return(owner.Create(key, Environment.NewLine));

            case EntityKeys.ActiveDirectoryKey:
                return(owner.Create(key, fileSystem.CurrentDirectory.FullName));

            case EntityKeys.IsRootedKey:
                bool isRooted = fileSystem.IsRooted(owner.Value <string>());
                return(owner.Create(key, isRooted.ToString(CultureInfo.InvariantCulture), isRooted));

            case EntityKeys.InternalDirectoryKey:
                string path = owner.HasValue <string>() &&
                              fileSystem.DirectoryExists(owner.Value <string>())
                                      ? owner.Value <string>()
                                      : owner.Path;
                return(owner.Create(key, fileSystem.GetDirectory(path, createNew: false)));

            case EntityKeys.InternalTempDirectoryKey:
                return(owner.Create(key, TempDirectory));

            case EntityKeys.ChunkStartKey:
                return(owner.Create(key, owner.Value <DataChunk>().Start));

            case EntityKeys.ChunkEndKey:
                return(owner.Create(key, owner.Value <DataChunk>().End));

            case EntityKeys.CountKey:
                return(owner.Create(key, owner.Count.ToString(CultureInfo.InvariantCulture), owner.Count));

            default:
                throw new ContentProviderException(key, owner);
            }
        }
        public async Task <IEnumerable <VirtualFile> > InitalizeTemplate(Entity dataModel, ChangeObservable observable)
        {
            bool forced = dataModel.Value <CommandDefinition>()
                          .Argument <BoolArgument>(TemplateCommandBuilder.ForcedArgumentName)
                          .Value;
            TemplateDescription template = dataModel.Template();

            List <VirtualFile> generatedFiles = new List <VirtualFile>(await InitializeFiles().ConfigureAwait(false));

            generatedFiles.AddRange(await InitializeSubTemplates().ConfigureAwait(false));

            Exception e = dataModel.GetCodeExceptions();

            if (e != null)
            {
                e.CompleteCodeExceptions(fileSystem.GetDirectory(dataModel.Root.Path));
                throw e;
            }

            return(generatedFiles);

            async Task <IEnumerable <VirtualFile> > InitializeFiles()
            {
                string basePath             = dataModel.Root.Path;
                HashSet <VirtualFile> files = new HashSet <VirtualFile>();

                foreach (templateFile file in template.File)
                {
                    (string content, Encoding encoding) = await GetResolvedTemplateContent(dataModel, file, template).ConfigureAwait(false);

                    VirtualFile destination = await GetFile(dataModel, file, forced, basePath, template).ConfigureAwait(false);

                    observable.OnNext(new Change(() => destination.Restore(),
                                                 $"Create file {Path.GetFileName(destination.Name)} for template " +
                                                 $"{template.name} in {destination.Parent.FullName}."));

                    using (Stream fileStream = destination.OpenWrite())
                        using (StreamWriter writer = new StreamWriter(fileStream, encoding))
                        {
                            fileStream.SetLength(0);
                            await writer.WriteAsync(content).ConfigureAwait(false);
                        }

                    files.Add(destination);
                }

                return(files);
            }

            async Task <IEnumerable <VirtualFile> > InitializeSubTemplates()
            {
                List <VirtualFile> files = new List <VirtualFile>();

                foreach (templateReference reference in SortByRelationship(template.AddTemplate ?? Enumerable.Empty <templateReference>()))
                {
                    if (repository.Template(reference.template) == null)
                    {
                        throw new TemplateReferenceNotDefinedException(reference.template);
                    }

                    CommandDefinitionBuilder pseudoDefinition = CommandDefinitionBuilder.Create()
                                                                .SetName(reference.template);
                    foreach (templateArgumentInstance argumentInstance in reference.Arguments)
                    {
                        AddArgument(argumentInstance, pseudoDefinition);
                    }

                    IEnumerable <IGrouping <string, templateRelationshipInstance> > grouped = (reference.Relationship ?? Enumerable.Empty <templateRelationshipInstance>()).GroupBy(r => r.name);
                    foreach (IGrouping <string, templateRelationshipInstance> relationshipInstances in grouped)
                    {
                        AddRelationships(relationshipInstances, pseudoDefinition, reference);
                    }

                    pseudoDefinition.CreateArgument()
                    .SetName(TemplateCommandBuilder.ForcedArgumentName)
                    .SetValue(forced)
                    .Build();
                    Entity referencedTemplateEntity = dataModel.Create(reference.template, pseudoDefinition.Build());
                    dataModel.AddEntity(referencedTemplateEntity);
                    files.AddRange(await InitalizeTemplate(referencedTemplateEntity, observable).ConfigureAwait(false));
                }

                return(files);

                void AddArgument(templateArgumentInstance templateArgumentInstance, CommandDefinitionBuilder commandDefinitionBuilder)
                {
                    string templateArgumentValue = resolver.Resolve(templateArgumentInstance.value, dataModel);
                    bool   argumentHasNoValue    = bool.TryParse(templateArgumentValue, out bool boolValue);

                    string[] argumentSplit   = templateArgumentValue.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
                    bool     isMultiArgument = argumentSplit.Length > 1;

                    if (argumentHasNoValue)
                    {
                        commandDefinitionBuilder.CreateArgument()
                        .SetName(templateArgumentInstance.name)
                        .SetValue(boolValue)
                        .Build();
                    }
                    else if (isMultiArgument)
                    {
                        commandDefinitionBuilder.CreateArgument()
                        .SetName(templateArgumentInstance.name)
                        .SetValue(argumentSplit)
                        .Build();
                    }
                    else
                    {
                        commandDefinitionBuilder.CreateArgument()
                        .SetName(templateArgumentInstance.name)
                        .SetValue(templateArgumentValue)
                        .Build();
                    }
                }

                void AddRelationships(IGrouping <string, templateRelationshipInstance> relationshipInstances,
                                      CommandDefinitionBuilder commandDefinitionBuilder, templateReference reference)
                {
                    templateRelationship relationshipDefinition = repository.Template(reference.template).Relationship
                                                                  ?.FirstOrDefault(r => r.name ==
                                                                                   relationshipInstances.Key);

                    if (relationshipDefinition == null)
                    {
                        throw new TemplateRelationshipNotFoundException(reference.template, relationshipInstances.Key);
                    }

                    bool multipleValues = relationshipDefinition.multiplicity == multiplicity.OneOrMore;

                    if (multipleValues)
                    {
                        string[] relationships = relationshipInstances.Select(r => resolver.Resolve(r.value, dataModel))
                                                 .ToArray();
                        commandDefinitionBuilder.CreateArgument()
                        .SetName(relationshipInstances.Key)
                        .SetValue(relationships)
                        .Build();
                    }
                    else
                    {
                        if (relationshipInstances.Count() != 1)
                        {
                            throw new RelationshipMultiplicityMismatchException(relationshipInstances.Key, reference.template);
                        }
                        commandDefinitionBuilder.CreateArgument()
                        .SetName(relationshipInstances.Key)
                        .SetValue(resolver.Resolve(relationshipInstances.Single().value, dataModel))
                        .Build();
                    }
                }

                IEnumerable <templateReference> SortByRelationship(IEnumerable <templateReference> references)
                {
                    List <templateReference> unsorted = references.ToList();
                    List <templateReference> sorted   = new List <templateReference>();

                    while (unsorted.Any())
                    {
                        Insert(unsorted[0]);
                    }

                    return(sorted);

                    void Insert(templateReference current, CycleChecker <templateReference> cycleChecker = null)
                    {
                        using (cycleChecker = cycleChecker?.SpawnChild() ?? new CycleChecker <templateReference>(
                                   ExceptionTexts.TemplateRelationshipCycle,
                                   () => cycleChecker = null))
                        {
                            cycleChecker.AddItem(current);
                            List <templateReference> dependent = new List <templateReference>();
                            foreach (templateRelationshipInstance relationshipInstance in current.Relationship ??
                                     Enumerable
                                     .Empty <
                                         templateRelationshipInstance
                                         >())
                            {
                                templateReference reference =
                                    unsorted.FirstOrDefault(r => HasRelationship(r, relationshipInstance));
                                if (reference != null)
                                {
                                    Insert(reference, cycleChecker);
                                }
                                else
                                {
                                    reference = sorted.FirstOrDefault(r => HasRelationship(r, relationshipInstance));
                                }

                                if (reference != null)
                                {
                                    dependent.Add(reference);
                                }
                            }

                            int skipping = dependent.Any() ? dependent.Select(d => sorted.IndexOf(d)).Max() : -1;
                            int index    = skipping + 1;
                            sorted.Insert(index, current);
                            unsorted.Remove(current);

                            bool HasRelationship(templateReference currentReference,
                                                 templateRelationshipInstance relationshipInstance)
                            {
                                templateArgumentInstance instance = currentReference.Arguments
                                                                    .FirstOrDefault(a => a.name
                                                                                    .Equals(
                                                                                        EntityKeys
                                                                                        .NameKey,
                                                                                        StringComparison
                                                                                        .OrdinalIgnoreCase));

                                return(instance?.value?.Equals(relationshipInstance.value,
                                                               StringComparison.OrdinalIgnoreCase) == true);
                            }
                        }
                    }
                }
            }
        }
        public override Entity Resolve(Entity owner, string key, bool fallback = false)
        {
            VirtualFile file = owner.Value <VirtualFile>();

            if (file != null)
            {
                if (key == EntityKeys.PathKey)
                {
                    return(owner.Create(key, file.Parent.FullName));
                }

                if (file.HasPropertyValueEntity(MapFileAccessKey(key)))
                {
                    return(owner.PropertyValueEntity(MapFileAccessKey(key), file, key));
                }
            }

            VirtualDirectory directory = owner.Value <VirtualDirectory>();

            if (directory != null)
            {
                if (key == EntityKeys.PathKey)
                {
                    return(owner.Create(key, directory.FullName));
                }

                if (directory.HasPropertyValueEntity(MapFileAccessKey(key)))
                {
                    return(owner.PropertyValueEntity(MapFileAccessKey(key), directory, key));
                }
            }

            switch (key)
            {
            case EntityKeys.NewlineKey:
                return(owner.Create(key, Environment.NewLine));

            case EntityKeys.ActiveDirectoryKey:
                return(owner.Create(key, fileSystem.CurrentDirectory.FullName));

            case EntityKeys.IsRootedKey:
                bool isRooted = fileSystem.IsRooted(owner.Value <string>());
                return(owner.Create(key, isRooted.ToString(CultureInfo.InvariantCulture), isRooted));

            case EntityKeys.InternalDirectoryKey:
                string path = owner.HasValue <string>() &&
                              fileSystem.DirectoryExists(owner.Value <string>())
                                      ? owner.Value <string>()
                                      : owner.Path;
                return(owner.Create(key, fileSystem.GetDirectory(path, createNew: false)));

            case EntityKeys.InternalTempDirectoryKey:
                return(owner.Create(key, TempDirectory));

            case EntityKeys.ChunkStartKey:
                return(owner.Create(key, owner.Value <DataChunk>().Start));

            case EntityKeys.ChunkEndKey:
                return(owner.Create(key, owner.Value <DataChunk>().End));

            case EntityKeys.CountKey:
                return(owner.Create(key, owner.Count.ToString(CultureInfo.InvariantCulture), owner.Count));

            case EntityKeys.IncrementKey:
                return(IncrementValue());

            case EntityKeys.DecrementKey:
                return(DecrementValue());

            case EntityKeys.NegateKey:
                if (bool.TryParse(owner.Value <string>(), out bool value))
                {
                    return(owner.Create(key, (!value).ToString(CultureInfo.InvariantCulture), !value));
                }
                throw new ContentProviderException(key, owner);

            case EntityKeys.OriginKey:
                return(owner.Origin);

            case EntityKeys.ThrowIfMultidimensionalKey:
                if (owner.Value <string>()?.Contains(",") == true)
                {
                    throw new MultidimensionalArrayNotSupportedException();
                }
                return(owner);

            default:
                throw new ContentProviderException(key, owner);

                Entity IncrementValue()
                {
                    string number = owner.Value <string>();

                    if (number != null)
                    {
                        if (int.TryParse(number, out int result))
                        {
                            result++;
                            return(owner.Create(key, result.ToString(CultureInfo.InvariantCulture), result));
                        }
                    }

                    return(owner.Create(key, number));
                }

                Entity DecrementValue()
                {
                    string number = owner.Value <string>();

                    if (number != null)
                    {
                        if (int.TryParse(number, out int result))
                        {
                            result--;
                            return(owner.Create(key, result.ToString(CultureInfo.InvariantCulture), result));
                        }
                    }

                    return(owner.Create(key, number));
                }
            }
        }