/*********
        ** Private methods
        *********/
        /// <summary>Register a config menu field with Generic Mod Config Menu.</summary>
        /// <param name="name">The config field name.</param>
        /// <param name="field">The config field instance.</param>
        /// <param name="resetToken">Remove and re-register the config token.</param>
        private void AddField(string name, ConfigField field, Action resetToken)
        {
            if (!this.ConfigMenu.IsLoaded)
            {
                return;
            }

            if (field.AllowValues.Any())
            {
                if (field.AllowMultiple)
                {
                    // Whitelist + multiple options = fake with multiple checkboxes
                    foreach (string value in field.AllowValues)
                    {
                        this.ConfigMenu.AddCheckbox(
                            label: $"{name}.{value}",
                            description: field.Description,
                            get: config => field.Value.Contains(value),
                            set: (config, selected) =>
                        {
                            // toggle value
                            if (selected)
                            {
                                field.Value.Add(value);
                            }
                            else
                            {
                                field.Value.Remove(value);
                            }

                            // set default if blank
                            if (!field.AllowBlank && !field.Value.Any())
                            {
                                field.Value = new InvariantHashSet(field.DefaultValues);
                            }

                            // update token
                            resetToken();
                        }
                            );
                    }
                }
                else if (field.IsBoolean() && !field.AllowBlank)
                {
                    // true/false only = checkbox
                    this.ConfigMenu.AddCheckbox(
                        label: name,
                        description: field.Description,
                        get: config => field.Value.Contains(true.ToString()),
                        set: (config, selected) =>
                    {
                        field.Value.Clear();
                        field.Value.Add(selected.ToString().ToLower());

                        resetToken();
                    }
                        );
                }
                else
                {
                    // Whitelist + single value = drop down
                    // Need an extra option when blank is allowed
                    List <string> choices = new List <string>(field.AllowValues);
                    if (field.AllowBlank)
                    {
                        choices.Insert(0, "");
                    }

                    this.ConfigMenu.AddDropdown(
                        label: name,
                        description: field.Description,
                        get: config => field.Value.FirstOrDefault() ?? "",
                        set: (config, newValue) =>
                    {
                        field.Value = new InvariantHashSet(newValue);
                        resetToken();
                    },
                        choices.ToArray()
                        );
                }
            }
            else
            {
                // No whitelist = text field
                this.ConfigMenu.AddTextbox(
                    label: name,
                    description: field.Description,
                    get: config => string.Join(", ", field.Value.ToArray()),
                    set: (config, newValue) =>
                {
                    field.Value = this.ParseCommaDelimitedField(newValue);

                    if (!field.AllowMultiple && field.Value.Count > 1)
                    {
                        field.Value = new InvariantHashSet(field.Value.Take(1));
                    }

                    resetToken();
                }
                    );
            }
        }
        /*********
        ** Private methods
        *********/
        /// <summary>Register a config menu field with Generic Mod Config Menu.</summary>
        /// <param name="name">The config field name.</param>
        /// <param name="field">The config field instance.</param>
        private void AddField(string name, ConfigField field)
        {
            if (!this.ConfigMenu.IsLoaded)
            {
                return;
            }

            // textbox if any values allowed
            if (!field.AllowValues.Any())
            {
                this.ConfigMenu.AddTextbox(
                    label: name,
                    description: field.Description,
                    get: _ => string.Join(", ", field.Value.ToArray()),
                    set: (_, newValue) =>
                {
                    field.Value = this.ParseCommaDelimitedField(newValue);

                    if (!field.AllowMultiple && field.Value.Count > 1)
                    {
                        field.Value = new InvariantHashSet(field.Value.Take(1));
                    }
                }
                    );
            }

            // checkboxes if player can choose multiple values
            else if (field.AllowMultiple)
            {
                foreach (string value in field.AllowValues)
                {
                    this.ConfigMenu.AddCheckbox(
                        label: $"{name}.{value}",
                        description: field.Description,
                        get: _ => field.Value.Contains(value),
                        set: (_, selected) =>
                    {
                        // toggle value
                        if (selected)
                        {
                            field.Value.Add(value);
                        }
                        else
                        {
                            field.Value.Remove(value);
                        }

                        // set default if blank
                        if (!field.AllowBlank && !field.Value.Any())
                        {
                            field.Value = new InvariantHashSet(field.DefaultValues);
                        }
                    }
                        );
                }
            }

            // checkbox for single boolean
            else if (!field.AllowBlank && field.IsBoolean())
            {
                this.ConfigMenu.AddCheckbox(
                    label: name,
                    description: field.Description,
                    get: _ => field.Value.Contains(true.ToString()),
                    set: (_, selected) =>
                {
                    field.Value.Clear();
                    field.Value.Add(selected.ToString().ToLower());
                }
                    );
            }

            // slider for single numeric range
            else if (!field.AllowBlank && field.IsNumericRange(out int min, out int max))
            {
                if (!int.TryParse(field.DefaultValues.FirstOrDefault(), out int defaultValue))
                {
                    defaultValue = min;
                }

                // number slider
                this.ConfigMenu.AddNumberField(
                    label: name,
                    description: field.Description,
                    get: _ => int.TryParse(field.Value.FirstOrDefault(), out int val) ? val : defaultValue,
                    set: (_, val) => field.Value = new InvariantHashSet(val.ToString(CultureInfo.InvariantCulture)),
                    min: min,
                    max: max
                    );
            }

            // dropdown for single multiple-choice value
            else
            {
                List <string> choices = new List <string>(field.AllowValues);
                if (field.AllowBlank)
                {
                    choices.Insert(0, "");
                }

                this.ConfigMenu.AddDropdown(
                    label: name,
                    description: field.Description,
                    get: _ => field.Value.FirstOrDefault() ?? "",
                    set: (_, newValue) => field.Value = new InvariantHashSet(newValue),
                    choices.ToArray()
                    );
            }
        }