// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using Microsoft.Extensions.Primitives;

namespace Microsoft.AspNetCore.Mvc.ModelBinding
{
    /// <summary>
    /// Result of an <see cref="IValueProvider.GetValue(string)"/> operation.
    /// </summary>
    /// <remarks>
    /// <para>
    /// <see cref="ValueProviderResult"/> can represent a single submitted value or multiple submitted values.
    /// </para>
    /// <para>
    /// Use <see cref="FirstValue"/> to consume only a single value, regardless of whether a single value or
    /// multiple values were submitted.
    /// </para>
    /// <para>
    /// Treat <see cref="ValueProviderResult"/> as an <see cref="IEnumerable{String}"/> to consume all values,
    /// regardless of whether a single value or multiple values were submitted.
    /// </para>
    /// </remarks>
    public readonly struct ValueProviderResult : IEquatable<ValueProviderResult>, IEnumerable<string>
    {
        private static readonly CultureInfo _invariantCulture = CultureInfo.InvariantCulture;

        /// <summary>
        /// A <see cref="ValueProviderResult"/> that represents a lack of data.
        /// </summary>
        public static ValueProviderResult None = new ValueProviderResult(new string[0]);

        /// <summary>
        /// Creates a new <see cref="ValueProviderResult"/> using <see cref="CultureInfo.InvariantCulture"/>.
        /// </summary>
        /// <param name="values">The submitted values.</param>
        public ValueProviderResult(StringValues values)
            : this(values, _invariantCulture)
        {
        }

        /// <summary>
        /// Creates a new <see cref="ValueProviderResult"/>.
        /// </summary>
        /// <param name="values">The submitted values.</param>
        /// <param name="culture">The <see cref="CultureInfo"/> associated with this value.</param>
        public ValueProviderResult(StringValues values, CultureInfo culture)
        {
            Values = values;
            Culture = culture ?? _invariantCulture;
        }

        /// <summary>
        /// Gets or sets the <see cref="CultureInfo"/> associated with the values.
        /// </summary>
        public CultureInfo Culture { get; }

        /// <summary>
        /// Gets or sets the values.
        /// </summary>
        public StringValues Values { get; }

        /// <summary>
        /// Gets the first value based on the order values were provided in the request. Use <see cref="FirstValue"/>
        /// to get a single value for processing regardless of whether a single or multiple values were provided
        /// in the request.
        /// </summary>
        public string FirstValue
        {
            get
            {
                if (Values.Count == 0)
                {
                    return null;
                }
                return Values[0];
            }
        }

        /// <summary>
        /// Gets the number of submitted values.
        /// </summary>
        public int Length => Values.Count;

        /// <inheritdoc />
        public override bool Equals(object obj)
        {
            var other = obj as ValueProviderResult?;
            return other.HasValue && Equals(other.Value);
        }

        /// <inheritdoc />
        public bool Equals(ValueProviderResult other)
        {
            if (Length != other.Length)
            {
                return false;
            }
            else
            {
                var x = Values;
                var y = other.Values;
                for (var i = 0; i < x.Count; i++)
                {
                    if (!string.Equals(x[i], y[i], StringComparison.Ordinal))
                    {
                        return false;
                    }
                }
                return true;
            }
        }

        /// <inheritdoc />
        public override int GetHashCode()
        {
            return ToString().GetHashCode();
        }

        /// <inheritdoc />
        public override string ToString()
        {
            return Values.ToString();
        }

        /// <summary>
        /// Gets an <see cref="IEnumerator{String}"/> for this <see cref="ValueProviderResult"/>.
        /// </summary>
        /// <returns>An <see cref="IEnumerator{String}"/>.</returns>
        public IEnumerator<string> GetEnumerator()
        {
            return ((IEnumerable<string>)Values).GetEnumerator();
        }

        /// <inheritdoc />
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        /// <summary>
        /// Converts the provided <see cref="ValueProviderResult"/> into a comma-separated string containing all
        /// submitted values.
        /// </summary>
        /// <param name="result">The <see cref="ValueProviderResult"/>.</param>
        public static explicit operator string(ValueProviderResult result)
        {
            return result.Values;
        }

        /// <summary>
        /// Converts the provided <see cref="ValueProviderResult"/> into a an array of <see cref="string"/> containing
        /// all submitted values.
        /// </summary>
        /// <param name="result">The <see cref="ValueProviderResult"/>.</param>
        public static explicit operator string[](ValueProviderResult result)
        {
            return result.Values;
        }

        /// <summary>
        /// Compares two <see cref="ValueProviderResult"/> objects for equality.
        /// </summary>
        /// <param name="x">A <see cref="ValueProviderResult"/>.</param>
        /// <param name="y">A <see cref="ValueProviderResult"/>.</param>
        /// <returns><c>true</c> if the values are equal, otherwise <c>false</c>.</returns>
        public static bool operator ==(ValueProviderResult x, ValueProviderResult y)
        {
            return x.Equals(y);
        }

        /// <summary>
        /// Compares two <see cref="ValueProviderResult"/> objects for inequality.
        /// </summary>
        /// <param name="x">A <see cref="ValueProviderResult"/>.</param>
        /// <param name="y">A <see cref="ValueProviderResult"/>.</param>
        /// <returns><c>false</c> if the values are equal, otherwise <c>true</c>.</returns>
        public static bool operator !=(ValueProviderResult x, ValueProviderResult y)
        {
            return !x.Equals(y);
        }
    }
}
