using FluentValidation.Internal;
using GreenTree.Strohrmann.ERP.Core.Extension;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;

namespace GreenTree.Strohrmann.ERP.Web.Extension
{
    public static class HtmlContentExtension
    {
        #region Inputs

        /// <summary>
        /// Return HTML markup for the <paramref name="expression"/>, using a display template for a bootstrap extension token field.
        /// See <see href="https://sliptree.github.io/bootstrap-tokenfield/">bootstrap-tokenfield</see> for more information of the 
        /// input type.
        /// </summary>
        /// <typeparam name="TModel">Model type.</typeparam>
        /// <typeparam name="TValue">Model value type.</typeparam>
        /// <param name="htmlHelper">HtmlHelper context.</param>
        /// <param name="expression">Expression for the requested model enumeration value.</param>
        /// <param name="delimiter">The delimiter string used to seperate token values.</param>
        /// <param name="htmlAttributes">Additional HTML attributes.</param>
        /// <param name="availableValues">The available values for the tokenbox.</param>
        /// <param name="allowOnlyOnce"><c>True</c> when every available value can only be selected once.</param>
        public static IHtmlContent TokenBoxFor<TModel, TValue>(
            this IHtmlHelper<TModel> htmlHelper,
            Expression<Func<TModel, TValue>> expression,
            string delimiter = "|",
            object htmlAttributes = null,
            string[] availableValues = null,
            bool allowOnlyOnce = true) where TValue : IEnumerable<string>
        {
            var attributes = htmlAttributes == null
                ? new string[0]
                : htmlAttributes.GetType().GetProperties()
                    .Select(p => String.Format("{0}='{1}'", p.Name.Replace("_", "-"), p.GetValue(htmlAttributes)))
                    .ToArray();

            var func = expression.Compile();

            var id = Guid.NewGuid().ToShortString();

            var scriptBuilder = new StringBuilder();

            var memberInfo = expression.Body.NodeType == ExpressionType.MemberAccess
                ? ((MemberExpression)expression.Body).Member
                : null;

            var type = expression.Body.GetType();

            if (availableValues != null)
            {
                var autoComplete = JsonConvertWithoutQuotes(new
                {
                    autocomplete = new
                    {
                        source = availableValues,
                        delay = 200
                    },
                    showAutocompleteOnFocus = true
                });

                scriptBuilder.AppendFormat(
                    "<script>" +
                    "   $('#{0}').tokenfield({1})", 
                    id, autoComplete);

                if (allowOnlyOnce)
                {
                    scriptBuilder.Append(
                        ".on('tokenfield:createtoken', function (e) { " +
                        "   if ($(this).tokenfield('getTokens').some(t => t.value === e.attrs.value)) return false;" +
                        "})");
                }

                if (memberInfo != null)
                {
                    var inputCreationScriptPart = String.Format(
                        "function (e) {{ " +
                        "   $(\"[name = '{1}'\").remove();" +
                        "   $(this).tokenfield('getTokens').forEach(" +
                        "       function (obj) {{ $('#{0}').after('<input type=\"hidden\" name=\"{1}\" value=\"' + obj.value + '\" />'); }});" +
                        "}}", id, memberInfo.Name);

                    scriptBuilder.AppendFormat(
                        ".on('tokenfield:createdtoken', {0})", inputCreationScriptPart);

                    scriptBuilder.AppendFormat(
                        ".on('tokenfield:removedtoken', {0})", inputCreationScriptPart);
                }

                scriptBuilder.Append(";</script>");
            }
            else
            {
                scriptBuilder.AppendFormat("<script>$('#{0}').tokenfield();</script>", id);
            }

            if (htmlHelper.ViewData.Model == null)
            {
                return new HtmlString(
                    String.Format(
                        "<input id='{0}' class='tokenBox' value='{1}' data-delimiter='{2}' {3} /> {4}",
                        id,
                        String.Empty,
                        delimiter,
                        String.Join(" ", attributes),
                        scriptBuilder));
            }
            else
            {
                var values = func(htmlHelper.ViewData.Model);

                var valueInputs = values
                    .Select(v => String.Format(
                        "<input type=\"hidden\" name=\"{0}\" value=\"{1}\" />",
                        memberInfo == null
                            ? id
                            : memberInfo.Name,
                        v));

                return new HtmlString(
                    String.Format(
                        "<input id='{0}' class='tokenBox' value='{1}' data-delimiter='{2}' {3} /> {4} {5}",
                        id,
                        values == null
                            ? String.Empty
                            : String.Join(delimiter, values),
                        delimiter,
                        String.Join(" ", attributes),
                        scriptBuilder,
                        String.Join(" ", valueInputs)));
            }
        }

        /// <summary>
        /// Return HTML markup for the <paramref name="expression"/>, using a bootstrap badge containing 'Yes' or 'No'.
        /// See <see href="https://getbootstrap.com/docs/4.1/components/badge/">bootstrap badge</see> for more information of the badge.
        /// </summary>
        /// <typeparam name="TModel">Model type.</typeparam>
        /// <param name="htmlHelper">HtmlHelper context.</param>
        /// <param name="expression">Expression for the requested model bool value.</param>
        public static IHtmlContent YesNoBadgeFor<TModel>(
            this IHtmlHelper<TModel> htmlHelper,
            Expression<Func<TModel, bool>> expression)
        {
            var func = expression.Compile();

            if (htmlHelper.ViewData.Model == null)
            {
                return new HtmlString("<span class='badge badge-secondary'>N/A</span>");
            }
            else
            {
                return
                    func(htmlHelper.ViewData.Model)
                        ? new HtmlString("<span class='badge badge-success'>Ja</span>")
                        : new HtmlString("<span class='badge badge-danger'>Nein</span>");
            }
        }

        /// <summary>
        /// Return HTML markup for the <paramref name="expression"/>, using a display template for a country selection field.
        /// </summary>
        /// <typeparam name="TModel">Model type.</typeparam>
        /// <typeparam name="TValue">Model value type.</typeparam>
        /// <param name="htmlHelper">HtmlHelper context.</param>
        /// <param name="expression">Expression for the requested model enumeration value.</param>
        /// <param name="htmlAttributes">Additional HTML attributes.</param>
        public static IHtmlContent CountrySelectionFor<TModel, TValue>(
            this IHtmlHelper<TModel> htmlHelper,
            Expression<Func<TModel, TValue>> expression,
            object htmlAttributes = null)
        {
            var attributes = htmlAttributes == null
                ? new string[0]
                : htmlAttributes.GetType().GetProperties()
                    .Select(p => String.Format("{0}='{1}'", p.Name.Replace("_", "-"), p.GetValue(htmlAttributes)))
                    .ToArray();

            var func = expression.Compile();

            var funcVal = htmlHelper.ViewData.Model == null
                ? String.Empty
                : func(htmlHelper.ViewData.Model).ToString();

            var memberInfo = expression.Body.NodeType == ExpressionType.MemberAccess
                ? ((MemberExpression)expression.Body).Member
                : null;

            var name = memberInfo == null
                ? Guid.NewGuid().ToShortString()
                : memberInfo.Name;

            var selectBuilder = new StringBuilder();
            selectBuilder.AppendFormat("<select id=\"{0}\" name =\"{0}\" {1}>", name, String.Join(" ", htmlAttributes));

            var countries = CultureInfo.GetCultures(CultureTypes.AllCultures)
                .Where(c => c.LCID != CultureInfo.InvariantCulture.LCID && !c.IsNeutralCulture)
                .Select(c => new RegionInfo(c.LCID))
                .Distinct()
                .OrderBy(r => r.DisplayName);

            foreach (var country in countries)
            {
                if (country.EnglishName == funcVal)
                    selectBuilder.AppendFormat("<option selected value=\"{0}\">{1}</option>", country.EnglishName, country.DisplayName);
                else
                    selectBuilder.AppendFormat("<option value=\"{0}\">{1}</option>", country.EnglishName, country.DisplayName);
            }

            selectBuilder.Append("</select>");

            return new HtmlString(selectBuilder.ToString());
        }

        /// <summary>
        /// Generates a Span HTML element
        /// </summary>
        /// <param name="htmlHelper">HtmlHelper context.</param>
        /// <param name="content">The span content text.</param>
        /// <param name="htmlAttributes">Additional HTML attributes.</param>
        /// <returns></returns>
        public static IHtmlContent Span(this IHtmlHelper htmlHelper, string content, object htmlAttributes = null)
        {
            var attributes = htmlAttributes == null
                ? new string[0]
                : htmlAttributes.GetType().GetProperties()
                    .Select(p => String.Format("{0}='{1}'", p.Name.Replace("_", "-"), p.GetValue(htmlAttributes)))
                    .ToArray();

            var htmlString = String.Format("<span {0}>{1}</span>", String.Join(" ", attributes), content);

            return new HtmlString(htmlString);
        }

        #endregion

        #region Helper

        /// <summary>
        /// Generates a random short ID string
        /// </summary>
        /// <param name="htmlHelper">HtmlHelper context.</param>
        public static string RandomId(this IHtmlHelper htmlHelper)
        {
            return Guid.NewGuid().ToShortString();
        }

        /// <summary>
        /// Generates a random short ID string
        /// </summary>
        public static string RandomId()
        {
            return Guid.NewGuid().ToShortString();
        }

        /// <summary>
        /// Converts an object in a JS literal object format
        /// </summary>
        /// <param name="value">The object to be converted.</param>
        private static string JsonConvertWithoutQuotes(object value)
        {
            var serializer = new JsonSerializer();
            var stringWriter = new StringWriter();

            using (var writer = new JsonTextWriter(stringWriter))
            {
                writer.QuoteName = false;

                serializer.Serialize(writer, value);
            }

            return stringWriter.ToString();
        }

        #endregion
    }
}
