using System;
using System.Collections.Generic;
using System.DirectoryServices.AccountManagement;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Security.AccessControl;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

using C4IT.Logging;
using C4IT.Matrix42.ServerInfo;
using C4IT_IAM;
using C4IT_IAM_Engine;
using C4IT_IAM_SET;
using LiamNtfs;
using static C4IT.Logging.cLogManager;
using static LiamNtfs.cActiveDirectoryBase;

namespace C4IT.LIAM
{
    public static class LiamInitializer
    {
        static public cLiamProviderBase CreateInstance(cLiamConfiguration LiamConfiguration, cLiamProviderData ProviderData)
        {
            return new cLiamProviderNtfs(LiamConfiguration, ProviderData);
        }
    }

    public class cLiamProviderNtfs : cLiamProviderBase
    {
        private enum eNtfsPathKind
        {
            Unknown = 0,
            ServerRoot = 1,
            ClassicShare = 2,
            DfsNamespaceRoot = 3,
            DfsLink = 4,
            Folder = 5
        }

        private sealed class cNtfsPathClassification
        {
            public string NormalizedPath { get; set; } = string.Empty;
            public eNtfsPathKind Kind { get; set; } = eNtfsPathKind.Unknown;
            public string BoundaryPath { get; set; } = string.Empty;
            public string ParentBoundaryPath { get; set; } = string.Empty;
            public string ParentPath { get; set; } = string.Empty;
            public string DisplayName { get; set; } = string.Empty;
            public int Level { get; set; } = -1;
        }

        public static Guid nftsModuleId = new Guid("77e213a1-6517-ea11-4881-000c2980fd94");
        private const string AdditionalConfigurationExcludePathsKey = "NtfsExcludePaths";
        private const string AdditionalConfigurationIncludePathsKey = "NtfsIncludePaths";
        private const string AdditionalConfigurationTraverseBoundaryPathKey = "NtfsTraverseBoundaryPath";
        private const string AdditionalConfigurationGroupNameSanitizeReplacementKey = "NtfsGroupNameSanitizeReplacement";
        private const string AdditionalConfigurationPreserveAdGroupNameCaseKey = "PreserveNtfsAdGroupNameCase";
        private const string AdditionalConfigurationAdDomainControllersKey = "NtfsAdDomainControllers";
        public readonly cNtfsBase ntfsBase = new cNtfsBase();
        public readonly cActiveDirectoryBase activeDirectoryBase = new cActiveDirectoryBase();
        private readonly Dictionary<string, HashSet<string>> publishedShareCache = new Dictionary<string, HashSet<string>>(StringComparer.OrdinalIgnoreCase);
        private readonly Dictionary<string, string> dfsEntryPathCache = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

        //public readonly bool WithoutPrivateFolders = true;

        public cLiamProviderNtfs(cLiamConfiguration LiamConfiguration, cLiamProviderData ProviderData) :
            base(LiamConfiguration, ProviderData)
        {
            this.ReplaceNtfsCustomTags();
        }

        public override async Task<bool> LogonAsync()
        {
            if (!cC4ITLicenseM42ESM.Instance.IsValid || !cC4ITLicenseM42ESM.Instance.Modules.ContainsKey(nftsModuleId))
            {
                LogEntry($"Error: License not valid", LogLevels.Error);
                return false;
            }
            return await LogonAsync(true);
        }
        private void ReplaceNtfsCustomTags()
        {
            foreach (var namingConvention in NamingConventions)
            {
                String key = null;
                String value = null;
                key = "GROUPTYPEPOSTFIX";
                if (namingConvention.AccessRole == eLiamAccessRoles.Owner)
                {
                    value = CustomTags["Filesystem_GroupOwnerTag"];
                }
                else if (namingConvention.AccessRole == eLiamAccessRoles.Write)
                {
                    value = CustomTags["Filesystem_GroupWriteTag"];
                }
                else if (namingConvention.AccessRole == eLiamAccessRoles.Read)
                {
                    value = CustomTags["Filesystem_GroupReadTag"];
                }
                else if (namingConvention.AccessRole == eLiamAccessRoles.Traverse)
                {
                    value = CustomTags["Filesystem_GroupTraverseTag"];
                }
                if (!String.IsNullOrEmpty(key) && !String.IsNullOrEmpty(value))
                {
                    namingConvention.DescriptionTemplate = namingConvention.DescriptionTemplate.Replace($"{{{{{key}}}}}", value);
                    namingConvention.NamingTemplate = namingConvention.NamingTemplate.Replace($"{{{{{key}}}}}", value);
                    namingConvention.Wildcard = namingConvention.Wildcard.Replace($"{{{{{key}}}}}", value);
                }

                value = null;
                key = "SCOPE";
                if (namingConvention.Scope == eLiamAccessRoleScopes.DomainLocal)
                {
                    value = CustomTags["Filesystem_GroupDomainLocalTag"];
                }
                else if (namingConvention.Scope == eLiamAccessRoleScopes.Global)
                {
                    value = CustomTags["Filesystem_GroupGlobalTag"];
                }
                if (!String.IsNullOrEmpty(key) && !String.IsNullOrEmpty(value))
                {
                    namingConvention.DescriptionTemplate = namingConvention.DescriptionTemplate.Replace($"{{{{{key}}}}}", value);
                    namingConvention.NamingTemplate = namingConvention.NamingTemplate.Replace($"{{{{{key}}}}}", value);
                    namingConvention.Wildcard = namingConvention.Wildcard.Replace($"{{{{{key}}}}}", value);
                }
            }
        }
        public async Task<bool> LogonAsync(bool force = false)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                var LI = new cNtfsLogonInfo()
                {
                    Domain = Domain,
                    DomainControllers = GetAdditionalConfigurationValue(AdditionalConfigurationAdDomainControllersKey),
                    User = Credential?.Identification,
                    UserSecret = Credential?.Secret,
                    TargetNetworkName = RootPath,
                    TargetGroupPath = this.GroupPath
                };
                var RetVal = await ntfsBase.LogonAsync(LI) && await activeDirectoryBase.LogonAsync(LI);
                return RetVal;
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                LogMethodEnd(CM);
            }

            return false;
        }

        public override async Task<List<cLiamDataAreaBase>> getDataAreasAsync(int Depth = -1)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                if (!cC4ITLicenseM42ESM.Instance.IsValid || !cC4ITLicenseM42ESM.Instance.Modules.ContainsKey(nftsModuleId))
                {
                    LogEntry($"Error: License not valid", LogLevels.Error);
                    return new List<cLiamDataAreaBase>();
                }
                if (!await LogonAsync())
                    return null;
                if (string.IsNullOrEmpty(this.RootPath))
                    return null;

                var DataAreas = new List<cLiamDataAreaBase>();
                var rootClassification = ClassifyPath(this.RootPath);
                var rootDataArea = await BuildDataAreaAsync(rootClassification);
                if (rootDataArea == null)
                    return null;

                DataAreas.Add(rootDataArea);

                if (Depth == 0)
                    return DataAreas;

                DataAreas.AddRange(await GetChildDataAreasAsync(rootClassification, Depth));
                return DataAreas;
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                LogMethodEnd(CM);
            }

            return null;
        }
        public override async Task<cLiamDataAreaBase> LoadDataArea(string UID)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                await Task.Delay(0);
                if (!cC4ITLicenseM42ESM.Instance.IsValid || !cC4ITLicenseM42ESM.Instance.Modules.ContainsKey(nftsModuleId))
                {
                    LogEntry($"Error: License not valid", LogLevels.Error);
                    return null;
                }
                if (!await LogonAsync())
                    return null;
                var classification = ClassifyPath(UID);
                if (!PathsEqual(classification?.NormalizedPath, this.RootPath) && !ShouldIncludeDataArea(classification))
                    return null;

                return await BuildDataAreaAsync(classification);
            }
            catch (Exception E)
            {
                LogException(E);
                return null;
            }
            finally
            {
                LogMethodEnd(CM);
            }

        }

        private async Task<cLiamDataAreaBase> BuildDataAreaAsync(cNtfsPathClassification classification, cNtfsResultFolder folderResult = null)
        {
            if (classification == null)
                return null;

            switch (classification.Kind)
            {
                case eNtfsPathKind.ServerRoot:
                    {
                        return new cLiamNtfsServerRoot(this, classification.NormalizedPath, classification.Level);
                    }
                case eNtfsPathKind.ClassicShare:
                case eNtfsPathKind.DfsLink:
                    {
                        var share = new cLiamNtfsShare(this, new cNtfsResultShare()
                        {
                            DisplayName = classification.DisplayName,
                            Path = classification.NormalizedPath,
                            Level = classification.Level
                        }, classification.ParentBoundaryPath);
                        await share.ResolvePermissionGroupsAsync(share.TechnicalName);
                        return share;
                    }
                case eNtfsPathKind.DfsNamespaceRoot:
                    {
                        var namespaceRoot = new cLiamNtfsDfsNamespaceRoot(this, new cNtfsResultShare()
                        {
                            DisplayName = classification.DisplayName,
                            Path = classification.NormalizedPath,
                            Level = classification.Level
                        });
                        await namespaceRoot.ResolvePermissionGroupsAsync(namespaceRoot.TechnicalName);
                        return namespaceRoot;
                    }
                case eNtfsPathKind.Folder:
                    {
                        var folderData = folderResult ?? new cNtfsResultFolder()
                        {
                            DisplayName = classification.DisplayName,
                            Path = classification.NormalizedPath,
                            Level = classification.Level,
                            CreatedDate = Directory.Exists(classification.NormalizedPath)
                                ? new DirectoryInfo(classification.NormalizedPath).CreationTimeUtc.ToString("s")
                                : DateTime.MinValue.ToString("s")
                        };
                        folderData.Level = classification.Level;
                        if (folderData.Parent == null && !string.IsNullOrWhiteSpace(classification.ParentPath))
                            folderData.Parent = new cNtfsResultFolder() { Path = classification.ParentPath };
                        var parentPath = !string.IsNullOrWhiteSpace(classification.ParentPath)
                            ? classification.ParentPath
                            : classification.ParentBoundaryPath;
                        var folder = new cLiamNtfsFolder(this, null, null, folderData, parentPath);
                        await folder.ResolvePermissionGroupsAsync(folder.TechnicalName);
                        return folder;
                    }
                default:
                    return null;
            }
        }

        private cNtfsPathClassification ClassifyPath(string path)
        {
            var normalizedPath = NormalizeUncPath(path);
            var segments = GetUncSegments(normalizedPath);
            var classification = new cNtfsPathClassification()
            {
                NormalizedPath = normalizedPath,
                DisplayName = GetDisplayName(normalizedPath),
                Level = getDepth(normalizedPath)
            };

            if (segments.Length == 1)
            {
                classification.Kind = eNtfsPathKind.ServerRoot;
                return classification;
            }

            if (segments.Length < 2)
                return classification;

            classification.ParentPath = segments.Length > 2
                ? BuildUncPath(segments, segments.Length - 1)
                : string.Empty;

            var dfsPrefixes = GetDfsObjectPrefixes(normalizedPath);
            if (dfsPrefixes.Count > 0)
            {
                var namespaceRootPath = dfsPrefixes[0];
                var deepestDfsPath = dfsPrefixes[dfsPrefixes.Count - 1];

                if (PathsEqual(normalizedPath, namespaceRootPath))
                {
                    classification.Kind = eNtfsPathKind.DfsNamespaceRoot;
                    classification.BoundaryPath = normalizedPath;
                    return classification;
                }

                if (PathsEqual(normalizedPath, deepestDfsPath))
                {
                    classification.Kind = eNtfsPathKind.DfsLink;
                    classification.BoundaryPath = deepestDfsPath;
                    classification.ParentBoundaryPath = dfsPrefixes.Count > 1
                        ? dfsPrefixes[dfsPrefixes.Count - 2]
                        : namespaceRootPath;
                    return classification;
                }

                classification.Kind = eNtfsPathKind.Folder;
                classification.BoundaryPath = deepestDfsPath;
                classification.ParentBoundaryPath = classification.ParentPath;
                return classification;
            }

            var shareBoundaryPath = GetPublishedShareBoundaryPath(segments);
            if (!string.IsNullOrWhiteSpace(shareBoundaryPath))
            {
                if (PathsEqual(normalizedPath, shareBoundaryPath))
                {
                    classification.Kind = eNtfsPathKind.ClassicShare;
                    classification.BoundaryPath = shareBoundaryPath;
                    return classification;
                }

                classification.Kind = eNtfsPathKind.Folder;
                classification.BoundaryPath = shareBoundaryPath;
                classification.ParentBoundaryPath = classification.ParentPath;
                return classification;
            }

            if (Directory.Exists(normalizedPath))
            {
                classification.Kind = eNtfsPathKind.Folder;
                classification.ParentBoundaryPath = classification.ParentPath;
            }

            return classification;
        }

        private async Task<List<cLiamDataAreaBase>> GetChildDataAreasAsync(cNtfsPathClassification parentClassification, int depth)
        {
            var children = new List<cLiamDataAreaBase>();
            if (parentClassification == null || depth == 0)
                return children;

            if (parentClassification.Kind == eNtfsPathKind.ServerRoot)
            {
                foreach (var childPath in GetServerRootChildPaths(parentClassification.NormalizedPath))
                {
                    var childClassification = ClassifyPath(childPath);
                    if (!ShouldTraverseDataArea(childClassification))
                        continue;

                    if (ShouldIncludeDataArea(childClassification))
                    {
                        var childDataArea = await BuildDataAreaAsync(childClassification);
                        if (childDataArea != null)
                            children.Add(childDataArea);
                    }

                    if (depth > 1)
                        children.AddRange(await GetChildDataAreasAsync(childClassification, depth - 1));
                }

                return children;
            }

            var folderEntries = await ntfsBase.RequestFoldersListAsync(parentClassification.NormalizedPath, 1);
            if (folderEntries == null)
                return children;

            foreach (var entry in folderEntries.Values.OfType<cNtfsResultFolder>())
            {
                var childClassification = ClassifyPath(entry.Path);
                if (!ShouldTraverseDataArea(childClassification))
                    continue;

                if (ShouldIncludeDataArea(childClassification))
                {
                    var childDataArea = await BuildDataAreaAsync(childClassification, entry);
                    if (childDataArea != null)
                        children.Add(childDataArea);
                }

                if (depth > 1)
                    children.AddRange(await GetChildDataAreasAsync(childClassification, depth - 1));
            }

            return children;
        }

        private IEnumerable<string> GetServerRootChildPaths(string serverRootPath)
        {
            var segments = GetUncSegments(serverRootPath);
            if (segments.Length != 1)
                return Enumerable.Empty<string>();

            var serverName = segments[0];
            return GetPublishedShareNames(serverName)
                .OrderBy(i => i, StringComparer.OrdinalIgnoreCase)
                .Select(shareName => BuildUncPath(new[] { serverName, shareName }, 2));
        }

        private bool ShouldIncludeDataArea(cNtfsPathClassification classification)
        {
            if (classification == null)
                return false;

            if (!MatchesDataAreaRegEx(classification.DisplayName))
                return false;

            string matchingConfigurationKey;
            string matchingRule;
            if (IsPathBlacklisted(classification, out matchingConfigurationKey, out matchingRule))
            {
                LogEntry($"Skip NTFS path '{classification.NormalizedPath}' due to AdditionalConfiguration rule '{matchingConfigurationKey}={matchingRule}'", LogLevels.Debug);
                return false;
            }

            if (!IsPathWhitelisted(classification, true, out matchingConfigurationKey, out matchingRule))
            {
                LogEntry($"Skip NTFS path '{classification.NormalizedPath}' because no AdditionalConfiguration whitelist matched", LogLevels.Debug);
                return false;
            }

            return true;
        }

        private bool ShouldTraverseDataArea(cNtfsPathClassification classification)
        {
            if (classification == null)
                return false;

            string matchingConfigurationKey;
            string matchingRule;
            if (IsPathBlacklisted(classification, out matchingConfigurationKey, out matchingRule))
            {
                LogEntry($"Skip NTFS subtree '{classification.NormalizedPath}' due to AdditionalConfiguration rule '{matchingConfigurationKey}={matchingRule}'", LogLevels.Debug);
                return false;
            }

            if (!HasAdditionalConfigurationValues(AdditionalConfigurationIncludePathsKey))
                return true;

            if (IsPathWhitelisted(classification, true, out matchingConfigurationKey, out matchingRule))
                return true;

            LogEntry($"Skip NTFS subtree '{classification.NormalizedPath}' because it is outside AdditionalConfiguration whitelist '{AdditionalConfigurationIncludePathsKey}'", LogLevels.Debug);
            return false;
        }

        private bool MatchesDataAreaRegEx(string displayName)
        {
            if (string.IsNullOrEmpty(this.DataAreaRegEx))
                return true;

            return Regex.Match(displayName ?? string.Empty, this.DataAreaRegEx).Success;
        }

        private bool IsPathBlacklisted(cNtfsPathClassification classification, out string matchingConfigurationKey, out string matchingRule)
        {
            return TryMatchPathPolicy(classification, AdditionalConfigurationExcludePathsKey, false, out matchingConfigurationKey, out matchingRule);
        }

        private bool IsPathWhitelisted(cNtfsPathClassification classification, bool allowPathAncestorMatches, out string matchingConfigurationKey, out string matchingRule)
        {
            matchingConfigurationKey = null;
            matchingRule = null;

            if (!HasAdditionalConfigurationValues(AdditionalConfigurationIncludePathsKey))
                return true;

            return TryMatchPathPolicy(classification, AdditionalConfigurationIncludePathsKey, allowPathAncestorMatches, out matchingConfigurationKey, out matchingRule);
        }

        private bool TryMatchPathPolicy(cNtfsPathClassification classification, string key, bool allowPathAncestorMatches, out string matchingConfigurationKey, out string matchingRule)
        {
            matchingConfigurationKey = null;
            matchingRule = null;

            if (classification == null || string.IsNullOrWhiteSpace(key))
                return false;

            var patterns = GetAdditionalConfigurationValues(key).ToList();
            if (patterns.Count == 0)
                return false;

            foreach (var pattern in patterns)
            {
                if (!MatchesPathPolicy(classification, pattern))
                    continue;

                matchingConfigurationKey = key;
                matchingRule = pattern;
                return true;
            }

            if (!allowPathAncestorMatches)
                return false;

            foreach (var pattern in patterns)
            {
                if (!CanPathLeadToPattern(classification, pattern))
                    continue;

                matchingConfigurationKey = key;
                matchingRule = pattern;
                return true;
            }

            return false;
        }

        private bool HasAdditionalConfigurationValues(string key)
        {
            return GetAdditionalConfigurationValues(key).Any();
        }

        private IEnumerable<string> GetAdditionalConfigurationValues(string key)
        {
            if (AdditionalConfiguration == null || string.IsNullOrWhiteSpace(key))
                return Enumerable.Empty<string>();

            string rawValue;
            if (!AdditionalConfiguration.TryGetValue(key, out rawValue) || string.IsNullOrWhiteSpace(rawValue))
                return Enumerable.Empty<string>();

            return rawValue
                .Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)
                .Select(i => i.Trim())
                .Where(i => !string.IsNullOrWhiteSpace(i))
                .Distinct(StringComparer.OrdinalIgnoreCase)
                .ToList();
        }

        private string GetRelativePathFromRoot(string path)
        {
            var normalizedRoot = NormalizeUncPath(this.RootPath);
            var normalizedPath = NormalizeUncPath(path);

            if (string.IsNullOrWhiteSpace(normalizedRoot) || string.IsNullOrWhiteSpace(normalizedPath))
                return string.Empty;

            if (PathsEqual(normalizedRoot, normalizedPath))
                return string.Empty;

            var rootWithSeparator = normalizedRoot + "\\";
            if (!normalizedPath.StartsWith(rootWithSeparator, StringComparison.OrdinalIgnoreCase))
                return normalizedPath;

            return normalizedPath.Substring(rootWithSeparator.Length)
                .Trim()
                .TrimStart('\\')
                .Replace('/', '\\');
        }

        private bool MatchesPathPolicy(cNtfsPathClassification classification, string pattern)
        {
            if (classification == null || string.IsNullOrWhiteSpace(pattern))
                return false;

            foreach (var candidate in GetPathPolicyCandidates(classification))
            {
                if (MatchesAdditionalConfigurationPattern(candidate, pattern))
                    return true;
            }

            return false;
        }

        private IEnumerable<string> GetPathPolicyCandidates(cNtfsPathClassification classification)
        {
            if (classification == null)
                return Enumerable.Empty<string>();

            var candidates = new List<string>();
            var relativePath = GetRelativePathFromRoot(classification.NormalizedPath);
            if (!string.IsNullOrWhiteSpace(relativePath))
                candidates.Add(relativePath);

            if (!string.IsNullOrWhiteSpace(classification.NormalizedPath))
                candidates.Add(classification.NormalizedPath);

            return candidates
                .Where(i => !string.IsNullOrWhiteSpace(i))
                .Distinct(StringComparer.OrdinalIgnoreCase)
                .ToList();
        }

        private bool MatchesAdditionalConfigurationPattern(string value, string pattern)
        {
            if (string.IsNullOrWhiteSpace(value) || string.IsNullOrWhiteSpace(pattern))
                return false;

            var normalizedValue = value.Trim().Replace('/', '\\').Trim('\\');
            var normalizedPattern = pattern.Trim().Replace('/', '\\').Trim('\\');
            if (string.IsNullOrWhiteSpace(normalizedPattern))
                return false;

            var regexPattern = "^" + Regex.Escape(normalizedPattern).Replace("\\*", ".*") + "$";
            return Regex.IsMatch(normalizedValue, regexPattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
        }

        private bool CanPathLeadToPattern(cNtfsPathClassification classification, string pattern)
        {
            if (classification == null || string.IsNullOrWhiteSpace(pattern))
                return false;

            foreach (var candidate in GetPathPolicyCandidates(classification))
            {
                if (IsPathAncestorOfPattern(candidate, pattern))
                    return true;
            }

            return false;
        }

        private bool IsPathAncestorOfPattern(string path, string pattern)
        {
            var normalizedPath = (path ?? string.Empty).Trim().Replace('/', '\\').Trim('\\');
            var normalizedPattern = (pattern ?? string.Empty).Trim().Replace('/', '\\').Trim('\\');
            if (string.IsNullOrWhiteSpace(normalizedPath) || string.IsNullOrWhiteSpace(normalizedPattern))
                return false;

            var pathSegments = normalizedPath.Split(new[] { '\\' }, StringSplitOptions.RemoveEmptyEntries);
            var patternSegments = normalizedPattern.Split(new[] { '\\' }, StringSplitOptions.RemoveEmptyEntries);
            if (pathSegments.Length > patternSegments.Length)
                return false;

            for (var segmentIndex = 0; segmentIndex < pathSegments.Length; segmentIndex++)
            {
                if (segmentIndex >= patternSegments.Length)
                    return false;

                if (!MatchesPatternSegment(pathSegments[segmentIndex], patternSegments[segmentIndex]))
                    return false;
            }

            return true;
        }

        private bool MatchesPatternSegment(string valueSegment, string patternSegment)
        {
            if (string.IsNullOrWhiteSpace(valueSegment) || string.IsNullOrWhiteSpace(patternSegment))
                return false;

            var regexPattern = "^" + Regex.Escape(patternSegment).Replace("\\*", ".*") + "$";
            return Regex.IsMatch(valueSegment, regexPattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
        }

        private List<string> GetDfsObjectPrefixes(string path)
        {
            var normalizedPath = NormalizeUncPath(path);
            var segments = GetUncSegments(normalizedPath);
            var prefixes = new List<string>();

            for (var segmentCount = 2; segmentCount <= segments.Length; segmentCount++)
            {
                var prefix = BuildUncPath(segments, segmentCount);
                string entryPath;
                if (!TryGetDfsEntryPath(prefix, out entryPath))
                    continue;

                prefixes.Add(!string.IsNullOrWhiteSpace(entryPath)
                    ? NormalizeUncPath(entryPath)
                    : prefix);
            }

            return prefixes
                .Distinct(StringComparer.OrdinalIgnoreCase)
                .OrderBy(i => GetUncSegments(i).Length)
                .ToList();
        }

        private bool TryGetDfsEntryPath(string path, out string entryPath)
        {
            var normalizedPath = NormalizeUncPath(path);
            if (dfsEntryPathCache.TryGetValue(normalizedPath, out entryPath))
                return !string.IsNullOrWhiteSpace(entryPath);

            string resolvedEntryPath;
            if (cNetworkConnection.TryGetDfsEntryPath(normalizedPath, out resolvedEntryPath))
            {
                entryPath = NormalizeUncPath(string.IsNullOrWhiteSpace(resolvedEntryPath) ? normalizedPath : resolvedEntryPath);
                dfsEntryPathCache[normalizedPath] = entryPath;
                return true;
            }

            dfsEntryPathCache[normalizedPath] = string.Empty;
            entryPath = string.Empty;
            return false;
        }

        private string GetPublishedShareBoundaryPath(string[] segments)
        {
            if (segments == null || segments.Length < 2)
                return string.Empty;

            var serverName = segments[0];
            var publishedShares = GetPublishedShareNames(serverName);
            if (!publishedShares.Contains(segments[1]))
                return string.Empty;

            return BuildUncPath(segments, 2);
        }

        private bool PathsEqual(string left, string right)
        {
            return string.Equals(
                NormalizeUncPath(left),
                NormalizeUncPath(right),
                StringComparison.OrdinalIgnoreCase);
        }

        private string NormalizeUncPath(string path)
        {
            if (string.IsNullOrWhiteSpace(path))
                return string.Empty;

            var segments = path.Trim().Replace('/', '\\').Split(new[] { '\\' }, StringSplitOptions.RemoveEmptyEntries);
            if (segments.Length == 0)
                return string.Empty;

            return @"\\" + string.Join("\\", segments);
        }

        private string[] GetUncSegments(string path)
        {
            var normalized = NormalizeUncPath(path);
            return normalized.Split(new[] { '\\' }, StringSplitOptions.RemoveEmptyEntries);
        }

        private string BuildUncPath(string[] segments, int segmentCount)
        {
            if (segments == null || segmentCount <= 0 || segments.Length < segmentCount)
                return string.Empty;

            return @"\\" + string.Join("\\", segments.Take(segmentCount));
        }

        private string GetDisplayName(string path)
        {
            var segments = GetUncSegments(path);
            if (segments.Length == 0)
                return string.Empty;

            return segments.Last();
        }

        private HashSet<string> GetPublishedShareNames(string serverName)
        {
            HashSet<string> shares;
            if (publishedShareCache.TryGetValue(serverName, out shares))
                return shares;

            shares = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
            try
            {
                using (var connection = new cNetworkConnection(this.RootPath, this.Credential?.Identification, this.Credential?.Secret))
                {
                    foreach (var share in connection.EnumNetShares(serverName))
                    {
                        if (!IsVisibleDiskShare(share))
                            continue;

                        shares.Add(share.shi1_netname);
                    }
                }
            }
            catch (Exception ex)
            {
                LogException(ex);
            }

            publishedShareCache[serverName] = shares;
            return shares;
        }

        private bool IsVisibleDiskShare(C4IT_IAM.SHARE_INFO_1 share)
        {
            if (string.IsNullOrWhiteSpace(share.shi1_netname))
                return false;

            if (share.shi1_netname.StartsWith("ERROR=", StringComparison.OrdinalIgnoreCase))
                return false;

            if (share.shi1_netname.EndsWith("$", StringComparison.OrdinalIgnoreCase))
                return false;

            var shareType = share.shi1_type & 0xFF;
            return shareType == (uint)C4IT_IAM.SHARE_TYPE.STYPE_DISKTREE;
        }

        public override async Task<List<cLiamDataAreaBase>> getSecurityGroupsAsync(string groupFilter)
        {

            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                if (!cC4ITLicenseM42ESM.Instance.IsValid || !cC4ITLicenseM42ESM.Instance.Modules.ContainsKey(nftsModuleId))
                {
                    LogEntry($"Error: License not valid", LogLevels.Error);
                    return new List<cLiamDataAreaBase>();
                }
                if (!await LogonAsync())
                    return null;
                if (string.IsNullOrEmpty(this.GroupPath))
                    return null;

                var SecurityGroups = new List<cLiamDataAreaBase>();

                var SGL = await activeDirectoryBase.RequestSecurityGroupsListAsync(groupFilter);
                if (SGL == null)
                    return null;

                foreach (var Entry in SGL)
                {
                    if (!string.IsNullOrEmpty(this.GroupRegEx) && !Regex.Match(Entry.Value.DisplayName, this.GroupRegEx).Success)
                        continue;


                    var SecurityGroup = new cLiamAdGroup(this, (cSecurityGroupResult)Entry.Value);
                    SecurityGroups.Add(SecurityGroup);
                }
                return SecurityGroups;
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                LogMethodEnd(CM);
            }

            return null;
        }

        public Task<ResultToken> CreateDataAreaAsync(
    string newFolderPath,
    string newFolderParent,
    IDictionary<string, string> customTags,
    IEnumerable<string> ownerSids,
    IEnumerable<string> readerSids,
    IEnumerable<string> writerSids,
    bool whatIf = false
)
        {
            var engine = CreateFilesystemEngine(
                newFolderPath,
                newFolderParent,
                customTags,
                ownerSids,
                readerSids,
                writerSids);
            engine.WhatIf = whatIf;
            var result = engine.createDataArea();
            return Task.FromResult(result);
        }

        public Task<ResultToken> EnsureMissingPermissionGroupsAsync(
            string folderPath,
            IDictionary<string, string> customTags,
            IEnumerable<string> ownerSids,
            IEnumerable<string> readerSids,
            IEnumerable<string> writerSids,
            bool allowSharePathEnsure = false,
            bool ensureTraverseGroups = false,
            bool whatIf = false)
        {
            var classification = ClassifyPath(folderPath);
            var allowShareKinds = allowSharePathEnsure;
            if (!IsSupportedPermissionManagedPathKind(
                classification,
                allowShareKinds
                    ? new[] { eNtfsPathKind.Folder, eNtfsPathKind.ClassicShare, eNtfsPathKind.DfsLink }
                    : new[] { eNtfsPathKind.Folder }))
            {
                return Task.FromResult(new ResultToken(System.Reflection.MethodBase.GetCurrentMethod().ToString())
                {
                    resultErrorId = 30008,
                    resultMessage = allowShareKinds
                        ? $"NTFS permission ensure is only supported for folder and share paths. DFS namespaces and server roots are skipped: {folderPath}"
                        : $"NTFS permission ensure is only supported for folder paths unless share support is explicitly enabled. Shares, DFS namespaces and server roots are skipped: {folderPath}"
                });
            }

            string matchingConfigurationKey;
            string matchingRule;
            if (IsPathBlacklisted(classification, out matchingConfigurationKey, out matchingRule))
            {
                return Task.FromResult(new ResultToken(System.Reflection.MethodBase.GetCurrentMethod().ToString())
                {
                    resultErrorId = 30008,
                    resultMessage = $"NTFS permission ensure skipped for '{folderPath}' due to AdditionalConfiguration rule '{matchingConfigurationKey}={matchingRule}'."
                });
            }

            if (!IsPathWhitelisted(classification, false, out matchingConfigurationKey, out matchingRule))
            {
                return Task.FromResult(new ResultToken(System.Reflection.MethodBase.GetCurrentMethod().ToString())
                {
                    resultErrorId = 30008,
                    resultMessage = $"NTFS permission ensure skipped for '{folderPath}' because no AdditionalConfiguration whitelist matched."
                });
            }

            var parentPath = Directory.GetParent(folderPath)?.FullName;
            var engine = CreateFilesystemEngine(
                folderPath,
                parentPath,
                customTags,
                ownerSids,
                readerSids,
                writerSids);
            engine.WhatIf = whatIf;

            var allowTraverseGroups = ensureTraverseGroups
                && IsSupportedPermissionManagedPathKind(
                    classification,
                    eNtfsPathKind.Folder,
                    eNtfsPathKind.ClassicShare,
                    eNtfsPathKind.DfsLink);
            var resultToken = engine.ensureDataAreaPermissions(allowTraverseGroups);
            if (!allowTraverseGroups && ensureTraverseGroups)
                resultToken.warnings.Add($"Traverse groups are currently only ensured for folder and share paths. Traverse processing was skipped for '{folderPath}'.");

            return Task.FromResult(resultToken);
        }

        private DataArea_FileSystem CreateFilesystemEngine(
            string folderPath,
            string parentFolderPath,
            IDictionary<string, string> customTags,
            IEnumerable<string> ownerSids,
            IEnumerable<string> readerSids,
            IEnumerable<string> writerSids)
        {
            var requiresDomainLocalTag = this.GroupStrategy == eLiamGroupStrategies.Ntfs_AGDLP
                || (NamingConventions ?? Enumerable.Empty<cLiamNamingConvention>())
                    .Any(i => i.Scope == eLiamAccessRoleScopes.DomainLocal);

            var mergedCustomTags = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
            foreach (var tag in CustomTags)
                mergedCustomTags[tag.Key] = tag.Value;
            if (customTags != null)
            {
                foreach (var tag in customTags)
                    mergedCustomTags[tag.Key] = tag.Value;
            }

            var engine = new DataArea_FileSystem
            {
                ConfigID = "manual",
                domainName = this.Domain,
                effectiveDomainController = activeDirectoryBase.EffectiveDomainController,
                username = this.Credential.Identification,
                password = new NetworkCredential("", this.Credential.Secret).SecurePassword,
                baseFolder = this.RootPath,
                newFolderPath = folderPath,
                newFolderParent = parentFolderPath,
                groupPrefix = GetRequiredCustomTag("Filesystem_GroupPrefixTag"),
                groupOUPath = this.GroupPath,
                groupPermissionStrategy = (C4IT_IAM_GET.PermissionGroupStrategy)this.GroupStrategy,
                groupCustomTags = mergedCustomTags,
                ownerUserSids = ownerSids?.Where(i => !string.IsNullOrWhiteSpace(i)).Distinct(StringComparer.OrdinalIgnoreCase).ToList() ?? new List<string>(),
                readerUserSids = readerSids?.Where(i => !string.IsNullOrWhiteSpace(i)).Distinct(StringComparer.OrdinalIgnoreCase).ToList() ?? new List<string>(),
                writerUserSids = writerSids?.Where(i => !string.IsNullOrWhiteSpace(i)).Distinct(StringComparer.OrdinalIgnoreCase).ToList() ?? new List<string>(),
                groupOwnerTag = GetRequiredCustomTag("Filesystem_GroupOwnerTag"),
                groupWriteTag = GetRequiredCustomTag("Filesystem_GroupWriteTag"),
                groupReadTag = GetRequiredCustomTag("Filesystem_GroupReadTag"),
                groupTraverseTag = GetRequiredCustomTag("Filesystem_GroupTraverseTag"),
                groupDLTag = requiresDomainLocalTag ? GetRequiredCustomTag("Filesystem_GroupDomainLocalTag") : string.Empty,
                groupGTag = GetRequiredCustomTag("Filesystem_GroupGlobalTag"),
                CanManagePermissionsForPath = IsPermissionManagedFolderPath,
                CanManageTraversePermissionsForPath = IsTraversePermissionManagedPath,
                forceStrictAdGroupNames = IsAdditionalConfigurationEnabled("ForceStrictAdGroupNames"),
                groupNameSanitizeReplacement = GetAdditionalConfigurationValueOrDefault(
                    AdditionalConfigurationGroupNameSanitizeReplacementKey,
                    Helper.DefaultGroupNameSanitizeReplacement),
                preserveAdGroupNameCase = IsAdditionalConfigurationEnabled(AdditionalConfigurationPreserveAdGroupNameCaseKey)
            };
            engine.traverseBoundaryPath = GetAdditionalConfigurationValue(AdditionalConfigurationTraverseBoundaryPathKey);

            foreach (var template in BuildSecurityGroupTemplates())
                engine.templates.Add(template);

            return engine;
        }

        private bool IsAdditionalConfigurationEnabled(string key)
        {
            if (AdditionalConfiguration == null || string.IsNullOrWhiteSpace(key))
                return false;

            if (!AdditionalConfiguration.TryGetValue(key, out var rawValue) || string.IsNullOrWhiteSpace(rawValue))
                return false;

            return rawValue.Equals("true", StringComparison.OrdinalIgnoreCase)
                || rawValue.Equals("1", StringComparison.OrdinalIgnoreCase)
                || rawValue.Equals("yes", StringComparison.OrdinalIgnoreCase);
        }

        private string GetAdditionalConfigurationValue(string key)
        {
            if (AdditionalConfiguration == null || string.IsNullOrWhiteSpace(key))
                return string.Empty;

            if (!AdditionalConfiguration.TryGetValue(key, out var rawValue) || string.IsNullOrWhiteSpace(rawValue))
                return string.Empty;

            return rawValue.Trim();
        }

        private string GetAdditionalConfigurationValueOrDefault(string key, string defaultValue)
        {
            if (AdditionalConfiguration == null || string.IsNullOrWhiteSpace(key))
                return defaultValue;

            if (!AdditionalConfiguration.TryGetValue(key, out var rawValue))
                return defaultValue;

            return rawValue == null ? string.Empty : rawValue.Trim();
        }

        public bool IsPermissionManagedFolderPath(string path)
        {
            return IsPermissionManagedPath(path, eNtfsPathKind.Folder);
        }

        public bool IsPermissionManagedSharePath(string path)
        {
            return IsPermissionManagedPath(path, eNtfsPathKind.ClassicShare, eNtfsPathKind.DfsLink);
        }

        private bool IsPermissionManagedPath(string path, params eNtfsPathKind[] supportedKinds)
        {
            var classification = ClassifyPath(path);
            if (!IsSupportedPermissionManagedPathKind(classification, supportedKinds))
                return false;

            string matchingConfigurationKey;
            string matchingRule;
            if (IsPathBlacklisted(classification, out matchingConfigurationKey, out matchingRule))
                return false;

            return IsPathWhitelisted(classification, false, out matchingConfigurationKey, out matchingRule);
        }

        private bool IsTraversePermissionManagedPath(string path)
        {
            if (string.IsNullOrWhiteSpace(GetAdditionalConfigurationValue(AdditionalConfigurationTraverseBoundaryPathKey)))
                return IsPermissionManagedFolderPath(path);

            var classification = ClassifyPath(path);
            if (classification == null || classification.Kind == eNtfsPathKind.ServerRoot || classification.Kind == eNtfsPathKind.Unknown)
                return false;

            string matchingConfigurationKey;
            string matchingRule;
            if (IsPathBlacklisted(classification, out matchingConfigurationKey, out matchingRule))
                return false;

            return Directory.Exists(path);
        }

        private static bool IsSupportedPermissionManagedPathKind(cNtfsPathClassification classification, params eNtfsPathKind[] supportedKinds)
        {
            if (classification == null || supportedKinds == null || supportedKinds.Length == 0)
                return false;

            return supportedKinds.Contains(classification.Kind);
        }

        private IEnumerable<IAM_SecurityGroupTemplate> BuildSecurityGroupTemplates()
        {
            var templates = new List<IAM_SecurityGroupTemplate>();
            var namingConventions = (NamingConventions ?? Enumerable.Empty<cLiamNamingConvention>()).ToList();
            var hasStrategyMatchingTraverseConvention = namingConventions.Any(i =>
                TryMapSecurityGroupType(i.AccessRole, out var securityGroupType)
                && securityGroupType == SecurityGroupType.Traverse
                && IsStrategyMatchingTraverseScope(i.Scope));

            foreach (var namingConvention in namingConventions)
            {
                if (!TryMapSecurityGroupType(namingConvention.AccessRole, out var securityGroupType))
                    continue;

                if (securityGroupType == SecurityGroupType.Traverse
                    && hasStrategyMatchingTraverseConvention
                    && !IsStrategyMatchingTraverseScope(namingConvention.Scope))
                {
                    continue;
                }

                if (!TryMapGroupScope(namingConvention.Scope, securityGroupType, out var groupScope))
                    continue;

                templates.Add(new IAM_SecurityGroupTemplate(
                    namingConvention.NamingTemplate,
                    namingConvention.DescriptionTemplate,
                    namingConvention.Wildcard,
                    securityGroupType,
                    groupScope));
            }

            return templates;
        }

        private bool TryMapSecurityGroupType(eLiamAccessRoles accessRole, out SecurityGroupType securityGroupType)
        {
            securityGroupType = SecurityGroupType.Read;
            switch (accessRole)
            {
                case eLiamAccessRoles.Owner:
                    securityGroupType = SecurityGroupType.Owner;
                    return true;
                case eLiamAccessRoles.Write:
                    securityGroupType = SecurityGroupType.Write;
                    return true;
                case eLiamAccessRoles.Read:
                    securityGroupType = SecurityGroupType.Read;
                    return true;
                case eLiamAccessRoles.Traverse:
                    securityGroupType = SecurityGroupType.Traverse;
                    return true;
                default:
                    return false;
            }
        }

        private bool TryMapGroupScope(eLiamAccessRoleScopes scope, SecurityGroupType type, out GroupScope groupScope)
        {
            groupScope = GroupScope.Global;
            if (type == SecurityGroupType.Traverse)
            {
                groupScope = GetStrategyTraverseGroupScope();
                return true;
            }

            switch (scope)
            {
                case eLiamAccessRoleScopes.Global:
                    groupScope = GroupScope.Global;
                    return true;
                case eLiamAccessRoleScopes.DomainLocal:
                    groupScope = GroupScope.Local;
                    return true;
                case eLiamAccessRoleScopes.Unknown:
                    return false;
                default:
                    return false;
            }
        }

        private GroupScope GetStrategyTraverseGroupScope()
        {
            return this.GroupStrategy == eLiamGroupStrategies.Ntfs_AGDLP
                ? GroupScope.Local
                : GroupScope.Global;
        }

        private bool IsStrategyMatchingTraverseScope(eLiamAccessRoleScopes scope)
        {
            if (scope == eLiamAccessRoleScopes.Unknown)
                return true;

            var strategyScope = GetStrategyTraverseGroupScope();
            return strategyScope == GroupScope.Local
                ? scope == eLiamAccessRoleScopes.DomainLocal
                : scope == eLiamAccessRoleScopes.Global;
        }

        private string GetRequiredCustomTag(string key)
        {
            if (CustomTags.TryGetValue(key, out var value) && !string.IsNullOrWhiteSpace(value))
                return value;

            if (string.Equals(key, "Filesystem_GroupPrefixTag", StringComparison.OrdinalIgnoreCase)
                && CustomTags.TryGetValue("ADGroupPrefix", out value)
                && !string.IsNullOrWhiteSpace(value))
            {
                return value;
            }

            throw new InvalidOperationException($"Missing NTFS custom tag '{key}'.");

        }


        public int getDepth(string path)
        {
            return getDepth(this.RootPath, path);
        }
        public static int getDepth(DirectoryInfo root, DirectoryInfo folder)
        {
            var rootDepth = root.FullName.TrimEnd(Path.DirectorySeparatorChar).Split(Path.DirectorySeparatorChar).Length;
            var folderDepth = folder.FullName.TrimEnd(Path.DirectorySeparatorChar).Split(Path.DirectorySeparatorChar).Length;
            return folderDepth - rootDepth;

        }
        public static int getDepth(string root, string folder)
        {
            if (string.IsNullOrWhiteSpace(root) || string.IsNullOrWhiteSpace(folder))
                return -1;

            var rootSegments = root.Trim().Replace('/', '\\').Split(new[] { '\\' }, StringSplitOptions.RemoveEmptyEntries);
            var folderSegments = folder.Trim().Replace('/', '\\').Split(new[] { '\\' }, StringSplitOptions.RemoveEmptyEntries);
            return folderSegments.Length - rootSegments.Length;

        }

        public override string GetLastErrorMessage()
        {
            var messages = new List<string>();
            if (!string.IsNullOrEmpty(ntfsBase?.LastErrorMessage))
                messages.Add(ntfsBase.LastErrorMessage);
            if (!string.IsNullOrEmpty(activeDirectoryBase?.LastErrorMessage))
                messages.Add(activeDirectoryBase.LastErrorMessage);

            return messages.Count > 0 ? string.Join(" | ", messages) : null;
        }
    }


    public abstract class cLiamNtfsPermissionDataAreaBase : cLiamDataAreaBase
    {
        public new readonly cLiamProviderNtfs Provider = null;
        public string OwnerGroupIdentifier = "S-1-0-0";
        public string WriteGroupIdentifier = "S-1-0-0";
        public string ReadGroupIdentifier = "S-1-0-0";
        public string TraverseGroupIdentifier = "S-1-0-0";

        protected cLiamNtfsPermissionDataAreaBase(cLiamProviderNtfs Provider) :
            base(Provider)
        {
            this.Provider = Provider;
            this.SupportsOwners = true;
            this.SupportsPermissions = true;
        }

        public override async Task<List<cLiamUserInfo>> GetOwnersAsync()
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                return await GetMembersAsync(true);
            }
            catch (Exception E)
            {
                LogException(E);
                return null;
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        protected async Task<List<cLiamUserInfo>> GetMembersAsync(bool owners)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                var AD = this.Provider?.activeDirectoryBase;
                if (AD == null)
                {
                    LogEntry($"Could not get ad class from Provider for data area '{this.TechnicalName}'", LogLevels.Warning);
                    return null;
                }

                cADCollectionBase lstMembers;
                this.OwnerGroupIdentifier = this.OwnerRef ?? this.OwnerGroupIdentifier;
                if (owners && !String.IsNullOrEmpty(this.OwnerGroupIdentifier))
                    lstMembers = await AD.GetMembersAsync(this.OwnerGroupIdentifier);
                else
                    lstMembers = null;
                if (lstMembers == null)
                {
                    LogEntry($"Could not get owner list for data area '{this.TechnicalName}'", LogLevels.Warning);
                    return null;
                }

                var RetVal = new List<cLiamUserInfo>(lstMembers.Count);
                LogEntry($"Owners for data area found: {lstMembers.Count}", LogLevels.Debug);
                foreach (var MemberEntry in lstMembers.Values)
                {
                    var User = new cLiamUserInfo()
                    {
                        DisplayName = MemberEntry.DisplayName,
                        UserPrincipalName = (MemberEntry as cADUserResult).UserPrincipalName,
                        SID = MemberEntry.ID
                    };
                    RetVal.Add(User);
                }

                return RetVal;
            }
            catch (Exception E)
            {
                LogException(E);
                return null;
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        public async Task ResolvePermissionGroupsAsync(string path)
        {
            var ACLs = Provider.activeDirectoryBase.GetAccessControlList(path);
            if (ACLs == null)
                return;

            var ownerNamingConvention = Provider.NamingConventions.First(i => i.AccessRole == eLiamAccessRoles.Owner && (Provider.GroupStrategy == eLiamGroupStrategies.Ntfs_AGP && i.Scope == eLiamAccessRoleScopes.Global || Provider.GroupStrategy == eLiamGroupStrategies.Ntfs_AGDLP && i.Scope == eLiamAccessRoleScopes.DomainLocal));
            var writeNamingConvention = Provider.NamingConventions.First(i => i.AccessRole == eLiamAccessRoles.Write && (Provider.GroupStrategy == eLiamGroupStrategies.Ntfs_AGP && i.Scope == eLiamAccessRoleScopes.Global || Provider.GroupStrategy == eLiamGroupStrategies.Ntfs_AGDLP && i.Scope == eLiamAccessRoleScopes.DomainLocal));
            var readNamingConvention = Provider.NamingConventions.First(i => i.AccessRole == eLiamAccessRoles.Read && (Provider.GroupStrategy == eLiamGroupStrategies.Ntfs_AGP && i.Scope == eLiamAccessRoleScopes.Global || Provider.GroupStrategy == eLiamGroupStrategies.Ntfs_AGDLP && i.Scope == eLiamAccessRoleScopes.DomainLocal));
            var traverseNamingConvention = Provider.NamingConventions.First(i => i.AccessRole == eLiamAccessRoles.Traverse);
            foreach (FileSystemAccessRule rule in ACLs)
            {
                var aclSid = rule.IdentityReference.Value;
                if (aclSid == "S-1-1-0")
                    continue;

                GroupPrincipal grp = GroupPrincipal.FindByIdentity(Provider.activeDirectoryBase.adContext, IdentityType.Sid, aclSid);
                if (grp == null)
                {
                    DefaultLogger.LogEntry(LogLevels.Debug, $"ACL SID '{aclSid}' on '{path}' could not be resolved to an AD group. Naming convention matching skipped.");
                    continue;
                }

                var samAccountName = grp.SamAccountName ?? string.Empty;
                if (string.IsNullOrWhiteSpace(samAccountName))
                {
                    DefaultLogger.LogEntry(LogLevels.Debug, $"ACL SID '{aclSid}' on '{path}' resolved to '{grp.Name}', but no sAMAccountName is available. Naming convention matching skipped.");
                    continue;
                }

                if (Regex.IsMatch(samAccountName, ownerNamingConvention.Wildcard, RegexOptions.IgnoreCase))
                {
                    this.OwnerGroupIdentifier = aclSid;
                    DefaultLogger.LogEntry(LogLevels.Debug, $"ACL SID '{aclSid}' on '{path}' resolved to '{samAccountName}' and matched Owner naming convention '{ownerNamingConvention.Wildcard}'.");
                    if (Provider.GroupStrategy == eLiamGroupStrategies.Ntfs_AGDLP)
                    {
                        var ldapFilter = String.Format("memberOf={0}", grp.DistinguishedName);
                        var res = await Provider.activeDirectoryBase.RequestSecurityGroupsListAsync(ldapFilter);
                        var ownerNamingConventionGlobal = Provider.NamingConventions.First(i => i.AccessRole == eLiamAccessRoles.Owner && i.Scope == eLiamAccessRoleScopes.Global);
                        var matchedGlobalGroup = false;

                        foreach (var memberItem in res ?? new cADCollectionBase())
                        {
                            var SecurityGroup = new cLiamAdGroup(this.Provider, (cSecurityGroupResult)memberItem.Value);
                            if (Regex.IsMatch(SecurityGroup.TechnicalName, ownerNamingConventionGlobal.Wildcard, RegexOptions.IgnoreCase))
                            {
                                this.OwnerGroupIdentifier = SecurityGroup.UID;
                                matchedGlobalGroup = true;
                                DefaultLogger.LogEntry(LogLevels.Debug, $"AGDLP Owner ACL group '{samAccountName}' resolved to global group '{SecurityGroup.TechnicalName}' with SID '{SecurityGroup.UID}'.");
                            }
                        }

                        if (!matchedGlobalGroup)
                            DefaultLogger.LogEntry(LogLevels.Debug, $"AGDLP Owner ACL group '{samAccountName}' matched, but no nested global group matched naming convention '{ownerNamingConventionGlobal.Wildcard}'. Keeping ACL SID '{aclSid}'.");
                    }
                }
                else if (Regex.IsMatch(samAccountName, writeNamingConvention.Wildcard, RegexOptions.IgnoreCase))
                {
                    this.WriteGroupIdentifier = aclSid;
                    DefaultLogger.LogEntry(LogLevels.Debug, $"ACL SID '{aclSid}' on '{path}' resolved to '{samAccountName}' and matched Write naming convention '{writeNamingConvention.Wildcard}'.");
                    if (Provider.GroupStrategy == eLiamGroupStrategies.Ntfs_AGDLP)
                    {
                        var ldapFilter = String.Format("memberOf={0}", grp.DistinguishedName);
                        var res = await Provider.activeDirectoryBase.RequestSecurityGroupsListAsync(ldapFilter);
                        var writeNamingConventionGlobal = Provider.NamingConventions.First(i => i.AccessRole == eLiamAccessRoles.Write && i.Scope == eLiamAccessRoleScopes.Global);
                        var matchedGlobalGroup = false;

                        foreach (var memberItem in res ?? new cADCollectionBase())
                        {
                            var SecurityGroup = new cLiamAdGroup(this.Provider, (cSecurityGroupResult)memberItem.Value);
                            if (Regex.IsMatch(SecurityGroup.TechnicalName, writeNamingConventionGlobal.Wildcard, RegexOptions.IgnoreCase))
                            {
                                this.WriteGroupIdentifier = SecurityGroup.UID;
                                matchedGlobalGroup = true;
                                DefaultLogger.LogEntry(LogLevels.Debug, $"AGDLP Write ACL group '{samAccountName}' resolved to global group '{SecurityGroup.TechnicalName}' with SID '{SecurityGroup.UID}'.");
                            }
                        }

                        if (!matchedGlobalGroup)
                            DefaultLogger.LogEntry(LogLevels.Debug, $"AGDLP Write ACL group '{samAccountName}' matched, but no nested global group matched naming convention '{writeNamingConventionGlobal.Wildcard}'. Keeping ACL SID '{aclSid}'.");
                    }
                }
                else if (Regex.IsMatch(samAccountName, readNamingConvention.Wildcard, RegexOptions.IgnoreCase))
                {
                    this.ReadGroupIdentifier = aclSid;
                    DefaultLogger.LogEntry(LogLevels.Debug, $"ACL SID '{aclSid}' on '{path}' resolved to '{samAccountName}' and matched Read naming convention '{readNamingConvention.Wildcard}'.");
                    if (Provider.GroupStrategy == eLiamGroupStrategies.Ntfs_AGDLP)
                    {
                        var ldapFilter = String.Format("memberOf={0}", grp.DistinguishedName);
                        var res = await Provider.activeDirectoryBase.RequestSecurityGroupsListAsync(ldapFilter);
                        var readNamingConventionGlobal = Provider.NamingConventions.First(i => i.AccessRole == eLiamAccessRoles.Read && i.Scope == eLiamAccessRoleScopes.Global);
                        var matchedGlobalGroup = false;

                        foreach (var memberItem in res ?? new cADCollectionBase())
                        {
                            var SecurityGroup = new cLiamAdGroup(this.Provider, (cSecurityGroupResult)memberItem.Value);
                            if (Regex.IsMatch(SecurityGroup.TechnicalName, readNamingConventionGlobal.Wildcard, RegexOptions.IgnoreCase))
                            {
                                this.ReadGroupIdentifier = SecurityGroup.UID;
                                matchedGlobalGroup = true;
                                DefaultLogger.LogEntry(LogLevels.Debug, $"AGDLP Read ACL group '{samAccountName}' resolved to global group '{SecurityGroup.TechnicalName}' with SID '{SecurityGroup.UID}'.");
                            }
                        }

                        if (!matchedGlobalGroup)
                            DefaultLogger.LogEntry(LogLevels.Debug, $"AGDLP Read ACL group '{samAccountName}' matched, but no nested global group matched naming convention '{readNamingConventionGlobal.Wildcard}'. Keeping ACL SID '{aclSid}'.");
                    }
                }
                else if (Regex.IsMatch(samAccountName, traverseNamingConvention.Wildcard, RegexOptions.IgnoreCase))
                {
                    this.TraverseGroupIdentifier = aclSid;
                    DefaultLogger.LogEntry(LogLevels.Debug, $"ACL SID '{aclSid}' on '{path}' resolved to '{samAccountName}' and matched Traverse naming convention '{traverseNamingConvention.Wildcard}'.");
                }
                else
                {
                    DefaultLogger.LogEntry(LogLevels.Debug, $"ACL SID '{aclSid}' on '{path}' resolved to '{samAccountName}', but did not match Owner/Write/Read/Traverse naming conventions.");
                }
            }
        }
    }

    public class cLiamNtfsShare : cLiamNtfsPermissionDataAreaBase
    {
        private readonly cNtfsResultBase Share = null;

        public cLiamNtfsShare(cLiamProviderNtfs Provider, cNtfsResultBase Share, string parentPath = null) :
            base(Provider)
        {
            this.Share = Share;

            this.DisplayName = Share.Path.Split('\\').Last();
            this.TechnicalName = Share.Path;
            this.UID = cLiamNtfsFolder.GetUniqueDataAreaID(Share.Path);
            this.Level = Share.Level;
            this.DataType = eLiamDataAreaTypes.NtfsShare;
            if (Directory.Exists(Share.Path))
                this.CreatedDate = new DirectoryInfo(Share.Path).CreationTimeUtc.ToString("s");
            if (!string.IsNullOrWhiteSpace(parentPath))
                this.ParentUID = cLiamNtfsFolder.GetUniqueDataAreaID(parentPath);
        }

        internal async Task<List<cLiamDataAreaBase>> getFolders()
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                await Task.Delay(0);
                var RetVal = new List<cLiamDataAreaBase>(0);
                return RetVal;
            }
            catch (Exception E)
            {
                LogException(E);
                return null;
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        public override async Task<List<cLiamDataAreaBase>> getChildrenAsync(int Depth = -1)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                await Task.Delay(0);
                var RetVal = new List<cLiamDataAreaBase>();
                return RetVal;
            }
            catch (Exception E)
            {
                LogException(E);
                return null;
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

    }
    public class cLiamNtfsDfsNamespaceRoot : cLiamNtfsPermissionDataAreaBase
    {
        private readonly cNtfsResultBase NamespaceRoot = null;

        public cLiamNtfsDfsNamespaceRoot(cLiamProviderNtfs Provider, cNtfsResultBase NamespaceRoot) :
            base(Provider)
        {
            this.NamespaceRoot = NamespaceRoot;

            this.DisplayName = NamespaceRoot.Path.Split('\\').Last();
            this.TechnicalName = NamespaceRoot.Path;
            this.UID = cLiamNtfsFolder.GetUniqueDataAreaID(NamespaceRoot.Path);
            this.Level = NamespaceRoot.Level;
            this.DataType = eLiamDataAreaTypes.DfsNamespaceRoot;
            if (Directory.Exists(NamespaceRoot.Path))
                this.CreatedDate = new DirectoryInfo(NamespaceRoot.Path).CreationTimeUtc.ToString("s");
        }

        public override async Task<List<cLiamDataAreaBase>> getChildrenAsync(int Depth = -1)
        {
            await Task.Delay(0);
            return new List<cLiamDataAreaBase>();
        }
    }
    public class cLiamNtfsServerRoot : cLiamDataAreaBase
    {
        public new readonly cLiamProviderNtfs Provider = null;

        public cLiamNtfsServerRoot(cLiamProviderNtfs Provider, string path, int level) : base(Provider)
        {
            this.Provider = Provider;
            this.DisplayName = path.Split('\\').Last();
            this.TechnicalName = path;
            this.UID = cLiamNtfsFolder.GetUniqueDataAreaID(path);
            this.Level = level;
            this.DataType = eLiamDataAreaTypes.NtfsServerRoot;
        }

        public override async Task<List<cLiamDataAreaBase>> getChildrenAsync(int Depth = -1)
        {
            await Task.Delay(0);
            return new List<cLiamDataAreaBase>();
        }
    }
    public class cLiamAdGroup : cLiamDataAreaBase
    {
        public new readonly cLiamProviderNtfs Provider = null;
        public readonly string dn = null;
        public readonly string scope = null;
        public override Task<List<cLiamDataAreaBase>> getChildrenAsync(int Depth = -1)
        {
            throw new NotImplementedException();
        }
        public cLiamAdGroup(cLiamProviderNtfs Provider, cSecurityGroupResult secGroup) : base(Provider)
        {
            this.UID = secGroup.ID;
            this.TechnicalName = secGroup.DisplayName;
            this.Provider = Provider;
            this.dn = secGroup.Path;
            this.scope = secGroup.Scope.ToString();
        }
    }
    public class cLiamNtfsFolder : cLiamNtfsPermissionDataAreaBase
    {
        public readonly cLiamNtfsShare Share = null;
        public readonly cLiamNtfsFolder NtfsRootFolder = null;
        public cLiamNtfsFolder(cLiamProviderNtfs Provider, cLiamNtfsShare share, cLiamNtfsFolder ntfsRootFolder, cNtfsResultFolder NtfsFolder, string parentPathOverride = null) : base(Provider)
        {
            var ntfsParent = NtfsFolder.Parent;
            this.NtfsRootFolder = ntfsRootFolder;
            this.Share = share;
            this.TechnicalName =  NtfsFolder.Path;
            this.UID =GetUniqueDataAreaID(NtfsFolder.Path);
            this.DisplayName = new DirectoryInfo(NtfsFolder.Path).Name;
            this.Level = NtfsFolder.Level;
            this.DataType = eLiamDataAreaTypes.NtfsFolder;
            this.CreatedDate = NtfsFolder.CreatedDate;
            if (!string.IsNullOrWhiteSpace(parentPathOverride))
            {
                this.ParentUID = GetUniqueDataAreaID(parentPathOverride);
            }
            else if (ntfsParent != null)
            {
                this.ParentUID = GetUniqueDataAreaID(ntfsParent.Path);
            }
            else if (this.Level == 1)
            {
                this.ParentUID = GetUniqueDataAreaID(this.Provider.RootPath);
            }
        }

        public static string GetUniqueDataAreaID(string fullPath)
        {
            LogMethodBegin(MethodBase.GetCurrentMethod());
            try
            {
                var md5 = new System.Security.Cryptography.MD5CryptoServiceProvider();
                var utf8 = new System.Text.UTF8Encoding();
                var hash = BitConverter.ToString(md5.ComputeHash(utf8.GetBytes(fullPath)));
                hash = hash.ToLower().Replace("-", "");
                return hash;
            }
            catch (Exception E)
            {
                cLogManager.DefaultLogger.LogException(E);
                throw;
            }
            finally
            {
                LogMethodEnd(MethodBase.GetCurrentMethod());
            }
        }
        public string GetUniqueDataAreaID()
        {
            return GetUniqueDataAreaID(this.TechnicalName);
        }
        public override async Task<List<cLiamDataAreaBase>> getChildrenAsync(int Depth = 1)
        {
            //TODO implement getChildrenAsync
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                await Task.Delay(0);
                var DataAreas = new List<cLiamDataAreaBase>();

                return DataAreas;
            }
            catch (Exception E)
            {
                LogException(E);
                return null;
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

    }
}
