Code/Dependencies/Pixie/Pixie/Markup/OptionSetSummary.cs
using System;
using System.Collections.Generic;
using WasmBox.Pixie.Options;

namespace WasmBox.Pixie.Markup {
    /// <summary>
    /// Summarizes the forms of a list of options.
    /// </summary>
    public sealed class OptionSetSummary : MarkupNode {
        /// <summary>
        /// Creates an option set summary node.
        /// </summary>
        /// <param name="options">The options to summarize.</param>
        /// <param name="printer">The option printer to use.</param>
        public OptionSetSummary(IReadOnlyList<Option> options, OptionPrinter printer) {
            this.Options = options;
            this.Printer = printer;
        }

        /// <summary>
        /// Gets the list of options to summarize.
        /// </summary>
        /// <returns>A list of options to summarize.</returns>
        public IReadOnlyList<Option> Options { get; private set; }

        /// <summary>
        /// Gets the option printer to use.
        /// </summary>
        /// <returns>The option printer.</returns>
        public OptionPrinter Printer { get; private set; }

        /// <inheritdoc/>
        public override MarkupNode Fallback {
            get {
                var grouped = SortAndGroupByCategory(Options);

                var groupNodes = new List<MarkupNode>();
                foreach (var kvPair in grouped) {
                    int optCount = kvPair.Value.Count;

                    var optionFormNodes = new List<MarkupNode>();
                    for (int i = 0; i < optCount; i++) {
                        var docs = kvPair.Value[i].Documentation;
                        var forms = kvPair.Value[i].Forms;
                        int formCount = forms.Count;

                        for (int j = 0; j < formCount; j++) {
                            if (optionFormNodes.Count > 0) {
                                optionFormNodes.Add(" ");
                            }
                            optionFormNodes.Add(
                                DecorationSpan.MakeBold(
                                    Printer.Print(
                                        forms[j],
                                        docs.GetParameters(forms[j]))));
                        }
                    }

                    groupNodes.Add(
                        new Paragraph(
                            new Sequence(
                                new MarkupNode[] {
                                    DecorationSpan.MakeBold(kvPair.Key),
                                    new IndentBox(optionFormNodes)
                                })));
                }
                return new Sequence(groupNodes);
            }
        }

        /// <inheritdoc/>
        public override MarkupNode Map(Func<MarkupNode, MarkupNode> mapping) {
            return this;
        }

        /// <summary>
        /// Groups a list of options by their category and then sorts
        /// the options in each category by their first forms.
        /// </summary>
        /// <param name="options">The options to sort and group.</param>
        /// <returns>
        /// A sorted dictionary that maps categories to sorted lists of options.
        /// </returns>
        public static SortedDictionary<string, IReadOnlyList<Option>> SortAndGroupByCategory(
            IReadOnlyList<Option> options) {
            var results = new SortedDictionary<string, IReadOnlyList<Option>>();
            int optionCount = options.Count;

            // Add all the options to the dictionary.
            for (int i = 0; i < optionCount; i++) {
                string category = options[i].Documentation.Category;
                IReadOnlyList<Option> list;
                if (!results.TryGetValue(category, out list)) {
                    list = new List<Option>();
                    results[category] = list;
                }
                ((List<Option>)list).Add(options[i]);
            }

            // Sort all the sub-lists.
            foreach (var kvPair in results) {
                ((List<Option>)kvPair.Value).Sort(CompareByFirstForm);
            }

            return results;
        }

        private static int CompareByFirstForm(Option first, Option second) {
            int firstFormCount = first.Forms.Count;
            int secondFormCount = second.Forms.Count;

            if (firstFormCount == 0) {
                if (secondFormCount == 0) {
                    return 0;
                }
                else {
                    return -1;
                }
            }
            else if (secondFormCount == 0) {
                return 1;
            }

            return first.Forms[0].Name.CompareTo(second.Forms[0].Name);
        }
    }
}