/// <summary>
 /// Construct an object based on another modifier.
 /// </summary>
 public PackageManifestModifier(PackageManifestModifier other)
 {
     Logger = other.Logger;
     try {
         manifestDict = Json.Deserialize(other.GetManifestJson()) as Dictionary <string, object>;
     } catch (Exception e) {
         Logger.Log(String.Format("Failed to clone PackageManifestModifier. \nException:{1}",
                                  MANIFEST_FILE_PATH, e.ToString()), LogLevel.Error);
     }
 }
        /// <summary>
        /// Update manifest file based on the settings.
        /// </summary>
        /// <param name="mode">Manifest modification mode being applied.</param>
        /// <param name="promptBeforeAction">Whether to display a window that prompts the user for
        /// confirmation before applying changes.</param>
        /// <param name="showDisableButton">Whether to show a button to disable auto-registry
        /// addition.</param>
        /// <param name="scopePrefixFilter">List of scope prefixes used to filter the set of registries
        /// being operated on.</param>
        internal static void UpdateManifest(ManifestModificationMode mode,
                                            bool promptBeforeAction = true,
                                            bool showDisableButton  = false,
                                            IEnumerable <string> scopePrefixFilter = null)
        {
            if (!ScopedRegistriesSupported)
            {
                logger.Log(String.Format("Scoped registries not supported in this version of Unity."),
                           level: LogLevel.Verbose);
                return;
            }

            PackageManifestModifier modifier = new PackageManifestModifier()
            {
                Logger = logger
            };
            Dictionary <string, List <PackageManagerRegistry> > manifestRegistries =
                modifier.ReadManifest() ? modifier.PackageManagerRegistries : null;

            if (manifestRegistries == null)
            {
                PackageManagerResolver.analytics.Report(
                    "registry_manifest/read/failed",
                    "Update Manifest failed: Read/Parse manifest failed");
                return;
            }

            var xmlRegistries = ReadRegistriesFromXml();

            // Filter registries using the scope prefixes.
            if (scopePrefixFilter != null)
            {
                foreach (var registry in new List <PackageManagerRegistry>(xmlRegistries.Values))
                {
                    bool removeRegistry = true;
                    foreach (var scope in registry.Scopes)
                    {
                        foreach (var scopePrefix in scopePrefixFilter)
                        {
                            if (scope.StartsWith(scopePrefix))
                            {
                                removeRegistry = false;
                            }
                        }
                    }
                    if (removeRegistry)
                    {
                        xmlRegistries.Remove(registry.Url);
                    }
                }
            }

            // Filter the set of considered registries based upon the modification mode.
            HashSet <string> selectedRegistryUrls = null;

            switch (mode)
            {
            case ManifestModificationMode.Add:
                // Remove all items from the XML loaded registries that are present in the manifest.
                foreach (var url in manifestRegistries.Keys)
                {
                    xmlRegistries.Remove(url);
                }
                selectedRegistryUrls = new HashSet <string>(xmlRegistries.Keys);
                break;

            case ManifestModificationMode.Remove:
                // Remove all items from the XML loaded registries that are not present in the
                // manifest.
                foreach (var url in new List <string>(xmlRegistries.Keys))
                {
                    if (!manifestRegistries.ContainsKey(url))
                    {
                        xmlRegistries.Remove(url);
                    }
                }
                selectedRegistryUrls = new HashSet <string>(xmlRegistries.Keys);
                break;

            case ManifestModificationMode.Modify:
                selectedRegistryUrls = new HashSet <string>();
                // Keep all XML loaded registries and select the items in the manifest.
                foreach (var url in xmlRegistries.Keys)
                {
                    if (manifestRegistries.ContainsKey(url))
                    {
                        selectedRegistryUrls.Add(url);
                    }
                }
                break;
            }

            // Applies the manifest modification based upon the modification mode.
            Action <HashSet <string> > syncRegistriesToManifest = (urlSelectionToApply) => {
                var addedRegistries = new List <PackageManagerRegistry>();
                SyncRegistriesToManifest(modifier, xmlRegistries, manifestRegistries,
                                         urlSelectionToApply,
                                         addRegistries: (mode == ManifestModificationMode.Add ||
                                                         mode == ManifestModificationMode.Modify),
                                         removeRegistries: (mode == ManifestModificationMode.Remove ||
                                                            mode == ManifestModificationMode.Modify),
                                         invertSelection: mode == ManifestModificationMode.Remove,
                                         addedRegistries: addedRegistries);
                // If any registries were added try migration if enabled.
                if (addedRegistries.Count > 0 && PromptToMigratePackages)
                {
                    PackageMigrator.MigratePackages();
                }
            };

            // Get the manifest json string based on the current selection and mode.
            Func <HashSet <string>, string> getManifestJsonAfterChange = (urlSelectionToApply) => {
                PackageManifestModifier       clonedModifier = new PackageManifestModifier(modifier);
                List <PackageManagerRegistry> toAdd;
                List <PackageManagerRegistry> toRemove;
                SyncRegistriesToModifier(clonedModifier, out toAdd, out toRemove,
                                         xmlRegistries, manifestRegistries,
                                         urlSelectionToApply,
                                         addRegistries: (mode == ManifestModificationMode.Add ||
                                                         mode == ManifestModificationMode.Modify),
                                         removeRegistries: (mode == ManifestModificationMode.Remove ||
                                                            mode == ManifestModificationMode.Modify),
                                         invertSelection: mode == ManifestModificationMode.Remove);
                return(clonedModifier.GetManifestJson());
            };

            if (xmlRegistries.Count > 0)
            {
                if (promptBeforeAction)
                {
                    // Build a list of items to display.
                    var registryItems = new List <KeyValuePair <string, string> >();
                    foreach (var kv in xmlRegistries)
                    {
                        registryItems.Add(new KeyValuePair <string, string>(kv.Key, kv.Value.Name));
                    }

                    // Optional when prompting is enabled or forced.
                    var window =
                        MultiSelectWindow.CreateMultiSelectWindow <PackageManagerResolverWindow>(
                            PLUGIN_NAME);
                    window.minSize        = new Vector2(1024, 500);
                    window.AvailableItems = registryItems;
                    window.Sort(1);
                    window.SelectedItems = selectedRegistryUrls;
                    switch (mode)
                    {
                    case ManifestModificationMode.Add:
                        window.Caption = String.Format("{0}\n\n{1}\n\n{2}",
                                                       ADD_REGISTRIES_QUESTION,
                                                       ADD_REGISTRIES_DESCRIPTION,
                                                       MODIFY_MENU_ITEM_DESCRIPTION);
                        window.ApplyLabel = "Add Selected Registries";
                        break;

                    case ManifestModificationMode.Remove:
                        window.Caption = String.Format("{0}\n\n{1}{2}",
                                                       REMOVE_REGISTRIES_QUESTION,
                                                       REMOVE_REGISTRIES_DESCRIPTION,
                                                       MODIFY_MENU_ITEM_DESCRIPTION);
                        window.ApplyLabel = "Remove Selected Registries";
                        break;

                    case ManifestModificationMode.Modify:
                        window.Caption = String.Format("{0}\n\n{1} {2}",
                                                       ADD_OR_REMOVE_REGISTRIES_QUESTION,
                                                       ADD_REGISTRIES_DESCRIPTION,
                                                       REMOVE_REGISTRIES_DESCRIPTION);
                        window.ApplyLabel = "Modify Registries";
                        break;
                    }
                    window.RenderItem = (item) => {
                        var registry       = xmlRegistries[item.Key];
                        var termsOfService = registry.TermsOfService;
                        if (!String.IsNullOrEmpty(termsOfService))
                        {
                            if (GUILayout.Button("View Terms of Service"))
                            {
                                Application.OpenURL(termsOfService);
                            }
                        }
                        var privacyPolicy = registry.PrivacyPolicy;
                        if (!String.IsNullOrEmpty(privacyPolicy))
                        {
                            if (GUILayout.Button("View Privacy Policy"))
                            {
                                Application.OpenURL(privacyPolicy);
                            }
                        }
                    };
                    // Set the scroll position to the bottom since "scopedRegistry" section is most
                    // likely at the bottom of the file.
                    scrollManifestViewLeft  = new Vector2(0.0f, float.PositiveInfinity);
                    scrollManifestViewRight = new Vector2(0.0f, float.PositiveInfinity);

                    // Render the change in manifest.json dynamically.
                    window.RenderAfterItems = () => {
                        GUILayout.Label("Changes to Packages/manifest.json");
                        EditorGUILayout.Space();

                        EditorGUILayout.BeginHorizontal();

                        EditorGUILayout.BeginVertical();
                        GUILayout.Label("Before", EditorStyles.boldLabel);
                        EditorGUILayout.Space();
                        scrollManifestViewLeft =
                            EditorGUILayout.BeginScrollView(scrollManifestViewLeft,
                                                            GUILayout.MaxWidth(window.position.width / 2.0f));
                        EditorGUILayout.TextArea(modifier.GetManifestJson());
                        EditorGUILayout.EndScrollView();
                        EditorGUILayout.EndVertical();

                        EditorGUILayout.Space();

                        EditorGUILayout.BeginVertical();
                        GUILayout.Label("After", EditorStyles.boldLabel);
                        EditorGUILayout.Space();
                        scrollManifestViewRight =
                            EditorGUILayout.BeginScrollView(scrollManifestViewRight,
                                                            GUILayout.MaxWidth(window.position.width / 2.0f));
                        EditorGUILayout.TextArea(getManifestJsonAfterChange(window.SelectedItems));
                        EditorGUILayout.EndScrollView();
                        EditorGUILayout.EndVertical();

                        EditorGUILayout.EndHorizontal();
                    };
                    if (showDisableButton)
                    {
                        window.RenderBeforeCancelApply = () => {
                            if (GUILayout.Button("Disable Registry Addition"))
                            {
                                Enable = false;
                                window.Close();
                            }
                        };
                    }
                    window.OnApply = () => { syncRegistriesToManifest(window.SelectedItems); };
                    window.Show();
                }
                else
                {
                    syncRegistriesToManifest(selectedRegistryUrls);
                }
            }
        }