private static void CheckDuplicate(IEnumerable <SCIMRepresentationAttribute> existingAttributes, ICollection <SCIMRepresentationAttribute> newFlatAttributes)
        {
            var rootAttributes = SCIMRepresentation.BuildHierarchicalAttributes(newFlatAttributes);

            foreach (var newAttribute in rootAttributes)
            {
                if (existingAttributes.Any(a => a.IsSimilar(newAttribute, true)))
                {
                    throw new SCIMDuplicateAttributeException(string.Format(Global.AttributeMustBeUnique, newAttribute.FullPath));
                }
            }
        }
        public static List <SCIMPatchResult> ApplyPatches(this SCIMRepresentation representation, ICollection <PatchOperationParameter> patches, bool ignoreUnsupportedCanonicalValues)
        {
            var result = new List <SCIMPatchResult>();

            foreach (var patch in patches)
            {
                var scimFilter       = SCIMFilterParser.Parse(patch.Path, representation.Schemas);
                var schemaAttributes = representation.Schemas.SelectMany(_ => _.Attributes);
                List <SCIMRepresentationAttribute> attributes = null;
                string fullPath = null;
                if (scimFilter != null)
                {
                    var scimAttributeExpression = scimFilter as SCIMAttributeExpression;
                    if (scimAttributeExpression == null)
                    {
                        throw new SCIMAttributeException(Global.InvalidAttributeExpression);
                    }

                    fullPath         = scimAttributeExpression.GetFullPath();
                    schemaAttributes = representation.Schemas
                                       .Select(s => s.GetAttribute(fullPath))
                                       .Where(s => s != null);

                    attributes = scimAttributeExpression.EvaluateAttributes(representation.HierarchicalAttributes.AsQueryable(), true).ToList();
                }
                else
                {
                    attributes = representation.FlatAttributes.Where(h => h.IsLeaf()).ToList();
                }

                var removeCallback = new Action <ICollection <SCIMRepresentationAttribute> >((attrs) =>
                {
                    foreach (var a in attrs)
                    {
                        var removedAttrs = representation.RemoveAttributeById(a);
                        foreach (var removedAttr in removedAttrs)
                        {
                            result.Add(new SCIMPatchResult {
                                Attr = removedAttr, Operation = SCIMPatchOperations.REMOVE, Path = removedAttr.FullPath
                            });
                        }
                    }
                });
                switch (patch.Operation)
                {
                case SCIMPatchOperations.ADD:
                    try
                    {
                        if (schemaAttributes == null || !schemaAttributes.Any())
                        {
                            throw new SCIMNoTargetException(string.Format(Global.AttributeIsNotRecognirzed, patch.Path));
                        }

                        var newAttributes = ExtractRepresentationAttributesFromJSON(representation.Schemas, schemaAttributes.ToList(), patch.Value, ignoreUnsupportedCanonicalValues);
                        CheckDuplicate(attributes, newAttributes);
                        removeCallback(attributes.Where(a => !a.SchemaAttribute.MultiValued && a.FullPath == fullPath).ToList());
                        var isAttributeExits = !string.IsNullOrWhiteSpace(fullPath) && attributes.Any(a => a.FullPath == fullPath);
                        foreach (var newAttribute in newAttributes.OrderBy(a => a.GetLevel()))
                        {
                            var path       = newAttribute.FullPath;
                            var schemaAttr = newAttribute.SchemaAttribute;
                            IEnumerable <SCIMRepresentationAttribute> parentAttributes = null;
                            if (fullPath == path)
                            {
                                var attr = attributes.FirstOrDefault(a => a.FullPath == fullPath);
                                if (attr != null)
                                {
                                    var parent = representation.GetParentAttribute(attr);
                                    if (parent != null)
                                    {
                                        parentAttributes = new[] { parent };
                                    }
                                }
                                else
                                {
                                    parentAttributes = representation.GetParentAttributesByPath(fullPath).ToList();
                                }
                            }

                            if (schemaAttr.MultiValued && schemaAttr.Type != SCIMSchemaAttributeTypes.COMPLEX)
                            {
                                var filteredAttribute = attributes.FirstOrDefault(_ => _.FullPath == path);
                                if (filteredAttribute != null)
                                {
                                    newAttribute.AttributeId = filteredAttribute.AttributeId;
                                }

                                representation.AddAttribute(newAttribute);
                            }
                            else if (parentAttributes != null && parentAttributes.Any())
                            {
                                foreach (var parentAttribute in parentAttributes)
                                {
                                    representation.AddAttribute(parentAttribute, newAttribute);
                                }
                            }
                            else
                            {
                                representation.FlatAttributes.Add(newAttribute);
                            }

                            attributes.Add(newAttribute);
                            result.Add(new SCIMPatchResult {
                                Attr = newAttribute, Operation = SCIMPatchOperations.ADD, Path = fullPath
                            });
                        }

                        if (TryGetExternalId(patch, out string externalId))
                        {
                            representation.ExternalId = externalId;
                        }
                    }
                    catch (SCIMSchemaViolatedException)
                    {
                        continue;
                    }
                    break;

                case SCIMPatchOperations.REMOVE:
                {
                    if (scimFilter == null)
                    {
                        throw new SCIMNoTargetException(string.Format(Global.InvalidPath, patch.Path));
                    }

                    if (SCIMFilterParser.DontContainsFilter(patch.Path) && patch.Value != null)
                    {
                        var excludedAttributes = ExtractRepresentationAttributesFromJSON(representation.Schemas, schemaAttributes.ToList(), patch.Value, ignoreUnsupportedCanonicalValues);
                        excludedAttributes = SCIMRepresentation.BuildHierarchicalAttributes(excludedAttributes);
                        attributes         = attributes.Where(a => excludedAttributes.Any(ea => ea.IsSimilar(a, true))).ToList();
                    }

                    removeCallback(attributes);
                }
                break;

                case SCIMPatchOperations.REPLACE:
                {
                    if (TryGetExternalId(patch, out string externalId))
                    {
                        representation.ExternalId = externalId;
                        continue;
                    }

                    if (schemaAttributes == null || !schemaAttributes.Any())
                    {
                        throw new SCIMNoTargetException(string.Format(Global.AttributeIsNotRecognirzed, patch.Path));
                    }

                    var complexAttr = scimFilter as SCIMComplexAttributeExpression;
                    if (complexAttr != null && !attributes.Any() && complexAttr.GroupingFilter != null)
                    {
                        throw new SCIMNoTargetException(Global.PatchMissingAttribute);
                    }

                    try
                    {
                        List <SCIMRepresentationAttribute> parents = new List <SCIMRepresentationAttribute>();
                        if (complexAttr != null)
                        {
                            var attr   = attributes.First(a => a.FullPath == fullPath);
                            var parent = string.IsNullOrWhiteSpace(attr.ParentAttributeId) ? attr : representation.GetParentAttribute(attributes.First(a => a.FullPath == fullPath));
                            if (parent != null)
                            {
                                parents = new List <SCIMRepresentationAttribute> {
                                    parent
                                };
                            }
                        }
                        else
                        {
                            parents = representation.GetParentAttributesByPath(fullPath).ToList();
                        }

                        if (scimFilter != null && parents.Any())
                        {
                            foreach (var parent in parents)
                            {
                                var flatHiearchy            = representation.GetFlatHierarchicalChildren(parent).ToList();
                                var scimAttributeExpression = scimFilter as SCIMAttributeExpression;
                                var newAttributes           = ExtractRepresentationAttributesFromJSON(representation.Schemas, schemaAttributes.ToList(), patch.Value, ignoreUnsupportedCanonicalValues);
                                foreach (var newAttribute in newAttributes.OrderBy(l => l.GetLevel()))
                                {
                                    if (!flatHiearchy.Any(a => a.FullPath == newAttribute.FullPath))
                                    {
                                        var parentPath = SCIMRepresentation.GetParentPath(newAttribute.FullPath);
                                        var p          = flatHiearchy.FirstOrDefault(a => a.FullPath == parentPath);
                                        if (p != null)
                                        {
                                            representation.AddAttribute(p, newAttribute);
                                        }
                                        else
                                        {
                                            representation.AddAttribute(newAttribute);
                                        }

                                        result.Add(new SCIMPatchResult {
                                                Attr = newAttribute, Operation = SCIMPatchOperations.ADD, Path = fullPath
                                            });
                                    }
                                }

                                result.AddRange(Merge(flatHiearchy, newAttributes, fullPath));
                            }
                        }
                        else
                        {
                            var newAttributes     = ExtractRepresentationAttributesFromJSON(representation.Schemas, schemaAttributes.ToList(), patch.Value, ignoreUnsupportedCanonicalValues);
                            var flatHiearchy      = representation.FlatAttributes.ToList();
                            var missingAttributes = newAttributes.Where(na => string.IsNullOrEmpty(na.ParentAttributeId) && !flatHiearchy.Any(fh => string.IsNullOrWhiteSpace(fh.ParentAttributeId) && fh.SchemaAttributeId == na.SchemaAttributeId)).ToList();
                            missingAttributes.ForEach((ma) =>
                                {
                                    representation.AddAttribute(ma);
                                    result.Add(new SCIMPatchResult {
                                        Attr = ma, Operation = SCIMPatchOperations.ADD, Path = ma.FullPath
                                    });
                                });
                            result.AddRange(Merge(flatHiearchy, newAttributes, fullPath));
                        }
                    }
                    catch (SCIMSchemaViolatedException)
                    {
                        continue;
                    }
                }
                break;
                }
            }

            var displayNameAttr = representation.FlatAttributes.FirstOrDefault(a => a.FullPath == "displayName");

            if (displayNameAttr != null)
            {
                representation.SetDisplayName(displayNameAttr.ValueString);
            }

            return(result);
        }