public static Tuple <Version, string> GetSuitableVersion(Context ctx, RemotePackageInfo remotePack,
                                                                 string dependant, VersionRange range)
        {
            var rangeMap = new Dictionary <VersionRange, HashSet <string> >();          // Value is for blaming packages

            rangeMap.Add(range, new HashSet <string>());
            rangeMap[range].Add(dependant);

            // Collect version requirements of other installed packages
            foreach (LocalPackageInfo localPack in ctx.LocalRegistry.ListPackages(ctx))
            {
                if (localPack.Dependencies.ContainsKey(remotePack.ID))
                {
                    if (!rangeMap.ContainsKey(localPack.Dependencies[remotePack.ID]))
                    {
                        rangeMap.Add(localPack.Dependencies[remotePack.ID], new HashSet <string>());
                    }
                    rangeMap[localPack.Dependencies[remotePack.ID]].Add(localPack.PlainName);
                }
            }
            Tuple <VersionRange, string[]>[] rangeMapArray = rangeMap
                                                             .OrderBy(pair => pair.Value.Count)
                                                             .Select(pair => new Tuple <VersionRange, string[]>(pair.Key, pair.Value.ToArray()))
                                                             .ToArray();

            // Narrower the allowed range, from least common range to most common
            var allowedRange  = new VersionRange(null, null);
            var rangesToBlame = new List <Tuple <VersionRange, string[]> >();

            for (int i = 0; i < rangeMapArray.Length; i++)
            {
                Tuple <VersionRange, string[]> rangeItem = rangeMapArray[i];
                var narrowResult = NarrowerRange(allowedRange, rangeItem.Item1);
                if (narrowResult.Item2)
                {
                    allowedRange = narrowResult.Item1;
                    rangesToBlame.Add(rangeItem);
                    // Minimum greater than Maximum, or there is not a downloadable package sitting between the bounds
                    if (!narrowResult.Item3 || !GetVersionInRange(remotePack.AvailableVersions, allowedRange).Item2)
                    {
                        throw new DependencyException(ctx, remotePack.PlainName, dependant, new RangeNotSatisfiableException(ctx, allowedRange, rangesToBlame));
                    }
                }
            }
            return(GetVersionInRange(remotePack.AvailableVersions, allowedRange).Item1);
        }
        async Task fetchPack(Identifier id)
        {
            var ctx = new Context(localReg, remoteReg, i18n);
            RemotePackageInfo pack = await remoteReg.QueryPackage(ctx, id);

            if (pack == null || pack.AvailableVersions.Count < 1)
            {
                SelectedIdentifier = default(Identifier);
                btnOK.Enabled      = false;
                showProgress("TODO: Not Found");
            }
            else
            {
                SelectedIdentifier = pack.ID;
                btnOK.Enabled      = true;
                showResult();
            }
            inflateUI(pack);
        }
        public async Task <IEnumerable <Tuple <Identifier, Version, string> > > DoFetch(Identifier id, VersionRange range, LogHandler logCallback, ProgressHandler progressCallback)
        {
            // TODO: Throw exception on circular reference

            var resolveQueue       = new Queue <Tuple <Identifier, VersionRange, string> >();
            var revInstallSequence = new List <Tuple <Identifier, Version, string> >();

            resolveQueue.Enqueue(new Tuple <Identifier, VersionRange, string>(id, range, ""));
            while (resolveQueue.Count > 0)
            {
                var elem = resolveQueue.Dequeue();
                RemotePackageInfo remotePack = null;
                if (!elem.Item1.HasGuid || !elem.Item1.HasName)
                {
                    // Get the full identifier first, so that installed package can be queried better
                    logCallback(LogLevel.Info, Translation.Translate("bpmcore_context_downloadmetadata", elem.Item1));
                    remotePack = await CachedQueryRemotePackage(elem.Item1);

                    if (remotePack == null)
                    {
                        if (string.IsNullOrEmpty(elem.Item3))
                        {
                            throw new PackageNotFoundException(this, elem.Item1);
                        }
                        else
                        {
                            throw new DependencyException(this, elem.Item1.ToString(), elem.Item3, new PackageNotFoundException(this, elem.Item1));
                        }
                    }
                    else
                    {
                        var newIdentifier = elem.Item1;
                        if (!newIdentifier.HasGuid && remotePack.ID.HasGuid)
                        {
                            newIdentifier.Guid    = remotePack.ID.Guid;
                            newIdentifier.HasGuid = true;
                        }
                        if (!newIdentifier.HasName && remotePack.ID.HasName)
                        {
                            newIdentifier.Name    = remotePack.ID.Name;
                            newIdentifier.HasName = true;
                        }
                        elem = new Tuple <Identifier, VersionRange, string>(newIdentifier, elem.Item2, elem.Item3);
                    }
                }
                var installedPackInfo = LocalRegistry.QueryInstalledPackage(this, elem.Item1);

                // Skip if the dependency is already installed and the version satisfies the requirement
                if (installedPackInfo != null && elem.Item2.ContainsVersion(installedPackInfo.Version))
                {
                    logCallback(LogLevel.Debug, Translation.Translate("bpmcore_context_alreadyinstalled", elem.Item1.ToString(), installedPackInfo.Version));
                    continue;
                }

                // Get the remote metadata
                if (remotePack == null)
                {
                    logCallback(LogLevel.Info, Translation.Translate("bpmcore_context_downloadmetadata", elem.Item1));
                    remotePack = await CachedQueryRemotePackage(elem.Item1);

                    if (remotePack == null)
                    {
                        if (string.IsNullOrEmpty(elem.Item3))
                        {
                            throw new PackageNotFoundException(this, elem.Item1);
                        }
                        else
                        {
                            throw new DependencyException(this, elem.Item1.ToString(), elem.Item3, new PackageNotFoundException(this, elem.Item1));
                        }
                    }
                }
                // Check other dependncies for a highest suitable version
                var installable = DependencyHelper.GetSuitableVersion(this, remotePack, elem.Item3, elem.Item2);
                logCallback(LogLevel.Info, Translation.Translate("bpmcore_context_willinstall", elem.Item1, installable.Item1));
                if (installable.Item1 < remotePack.AvailableVersions.Keys.Last())
                {
                    logCallback(LogLevel.Warning, Translation.Translate("bpmcore_context_cannotlatest",
                                                                        remotePack.AvailableVersions.Keys.Last(), remotePack.ID));
                }

                // Download the file from internet
                logCallback(LogLevel.Info, Translation.Translate("bpmcore_context_downloading", installable.Item2));
                var tempFile = await DownloadManager.AcquirePackage(this, elem.Item1, installable.Item1, installable.Item2, progressCallback);

                // Push further dependencies into the queue
                var packInfo = LocalRegistry.QueryExternalPackage(this, tempFile);
                logCallback(LogLevel.Debug, Translation.Translate("bpmcore_context_requires", packInfo.ID, string.Join(",", packInfo.Dependencies.Select(t => t.Key.ToString() + t.Value))));
                foreach (var dependency in packInfo.Dependencies)
                {
                    resolveQueue.Enqueue(new Tuple <Identifier, VersionRange, string>(dependency.Key, dependency.Value, packInfo.PlainName));
                }

                if (revInstallSequence.Any(t => t.Item3 == tempFile))
                {
                    revInstallSequence.Remove(revInstallSequence.Single(t => t.Item3 == tempFile));
                }
                revInstallSequence.Add(new Tuple <Identifier, Version, string>(remotePack.ID, installable.Item1, tempFile));
            }
            revInstallSequence.Reverse();
            return(revInstallSequence);
        }
        private void inflateUI(RemotePackageInfo pack)
        {
            var rangeTuple      = DependencyHelper.GetSuitableRange(ctx, pack);
            var suitableRange   = rangeTuple.Item1;
            var packagesToBlame = rangeTuple.Item2;

            Font biggerFont = new Font(this.Font.FontFamily, 14, FontStyle.Regular, GraphicsUnit.Pixel);

            infoPanel.Controls.Clear();
            versionPanel.Controls.Clear();
            if (pack == null)
            {
                return;
            }
            int rownum = 0;

            string[] blacklist = new[] {
                "Files",
                "AvailableVersions",
                "ForcePopup"
            };
            infoPanel.SuspendLayout();
            foreach (var prop in pack.GetType().GetProperties())
            {
                var value = prop.GetValue(pack);
                if (blacklist.Contains(prop.Name))
                {
                    continue;
                }
                if (value == null)
                {
                    continue;
                }
                infoPanel.Controls.Add(new Label()
                {
                    Text      = prop.Name,
                    Font      = biggerFont,
                    ForeColor = accentColor,
                    Margin    = new Padding(3)
                }, 0, rownum);
                infoPanel.Controls.Add(new Label()
                {
                    Text   = value.ToString(),
                    Font   = biggerFont,
                    Margin = new Padding(3)
                }, 1, rownum);
                rownum++;
            }
            infoPanel.ResumeLayout();
            int  vernum = 0;
            bool foundLatestAvailableVersion = false;

            foreach (var ver in pack.AvailableVersions.Reverse())
            {
                var text = ver.Key.ToString();
                if (vernum == 0)
                {
                    text += " (Latest)";
                }
                bool versionValid = suitableRange.ContainsVersion(ver.Key);
                if (versionValid && !foundLatestAvailableVersion && vernum > 0)
                {
                    text += " (Latest Installable)";
                }
                versionPanel.Controls.Add(new RadioButton()
                {
                    Size    = new Size(300, 30),
                    Enabled = versionValid,
                    Checked = versionValid && !foundLatestAvailableVersion,
                    Font    = biggerFont,
                    Tag     = ver.Key,
                    Text    = text
                });
                foundLatestAvailableVersion |= versionValid;
                vernum++;
            }
            btnOK.Enabled = foundLatestAvailableVersion;
            bool hasWarning = !foundLatestAvailableVersion || !suitableRange.ContainsVersion(pack.AvailableVersions.Last().Key);

            if (hasWarning)
            {
                if (!mainTabControl.TabPages.Contains(tabPageWarning))
                {
                    mainTabControl.TabPages.Insert(0, tabPageWarning);
                }
                mainTabControl.SelectedIndex = 0;
                var sb = new System.Text.StringBuilder();
                if (foundLatestAvailableVersion)
                {
                    sb.AppendLine(I._("bpmcore_context_cannotlatest",
                                      pack.AvailableVersions.Keys.Last(), pack.ID));
                }
                else
                {
                    if (!suitableRange.NotEmpty)
                    {
                        sb.AppendLine(I._("bpmcore_exception_badrange1"));
                    }
                    else
                    {
                        sb.AppendLine(I._("bpmcore_exception_badrange2"));
                    }
                }
                sb.AppendLine(I._("bpmcore_exception_blame"));
                foreach (var item in packagesToBlame)
                {
                    sb.AppendLine(I._("bpmcore_exception_constraint", item.Item1.ToString(),
                                      string.IsNullOrEmpty(item.Item2[0]) ? I._("bpmcore_exception_userconstraint") : item.Item2[0],
                                      item.Item2.Length > 1 ? "...(" + (item.Item2.Length - 1).ToString() + "+)" : ""));
                }
                textWarn.Font = biggerFont;
                textWarn.Text = sb.ToString();
            }
            else
            {
                if (mainTabControl.TabPages.Contains(tabPageWarning))
                {
                    mainTabControl.TabPages.Remove(tabPageWarning);
                }
            }
        }