using FluentValidation.Internal;
using GreenTree.Maschinenbestellungen.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.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace GreenTree.Maschinenbestellungen.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.DisplayName == funcVal)
                    selectBuilder.AppendFormat("<option selected value=\"{0}\">{1}</option>", country.DisplayName, country.DisplayName);
                else
                    selectBuilder.AppendFormat("<option value=\"{0}\">{1}</option>", country.DisplayName, 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);
        }

        /// <summary>
        /// Return HTML markup for the <paramref name="valueExpression"/>, using a display template for a search selection field.
        /// </summary>
        /// <typeparam name="TModel">Model type.</typeparam>
        /// <typeparam name="TValue">Model value type.</typeparam>
        /// <param name="htmlHelper">HtmlHelper context.</param>
        /// <param name="valueExpression">Expression for the requested model value.</param>
        /// <param name="textExpression">Expression for the requested model text.</param>
        /// <param name="searchActionUrl">POST action URL that returns a model enumeration as JSON result.</param>
        /// <param name="htmlAttributesValue">Additional HTML attributes for the value input.</param>
        /// <param name="htmlAttributesText">Additional HTML attributes for the text input.</param>
        /// <returns></returns>
        public static IHtmlContent SearchFor<TModel, TValue>(
            this IHtmlHelper<TModel> htmlHelper, 
            Expression<Func<TModel, TValue>> valueExpression,
            Expression<Func<TModel, string>> textExpression,
            string searchActionUrl,
            object htmlAttributesValue = null,
            object htmlAttributesText = null)
        {
            var attributesText = htmlAttributesText == null
                ? new string[0]
                : htmlAttributesText.GetType().GetProperties()
                    .Where(p => p.Name != "name")
                    .Select(p => String.Format("{0}='{1}'", p.Name.Replace("_", "-"), p.GetValue(htmlAttributesText)))
                    .ToArray();

            var attributesValue = htmlAttributesValue == null
                ? new string[0]
                : htmlAttributesValue.GetType().GetProperties()
                    .Where(p => p.Name != "name")
                    .Select(p => String.Format("{0}='{1}'", p.Name.Replace("_", "-"), p.GetValue(htmlAttributesValue)))
                    .ToArray();

            var id = RandomId();

            var valFunc = valueExpression.Compile();

            var funcVal = htmlHelper.ViewData.Model == null
                ? default
                : valFunc(htmlHelper.ViewData.Model);

            var isDefaultValue = funcVal == null || (funcVal != null && funcVal.ToString() == default(TValue).ToString());

            var textFunc = textExpression.Compile();

            var funcText = htmlHelper.ViewData.Model == null
                ? default
                : textFunc(htmlHelper.ViewData.Model);

            var memberInfoText = textExpression.Body.NodeType == ExpressionType.MemberAccess
                ? ((MemberExpression)textExpression.Body).Member
                : null;

            var nameText = memberInfoText == null
                ? Guid.NewGuid().ToShortString()
                : memberInfoText.Name;

            if (htmlAttributesText != null && htmlAttributesText.GetType().GetProperty("name") != null)
                nameText = htmlAttributesText.GetType().GetProperty("name").GetValue(htmlAttributesText).ToString();

            var memberInfoValue = valueExpression.Body.NodeType == ExpressionType.MemberAccess
                ? ((MemberExpression)valueExpression.Body).Member
                : null;

            var nameValue = memberInfoValue == null
                ? Guid.NewGuid().ToShortString()
                : memberInfoValue.Name;

            if (htmlAttributesValue != null && htmlAttributesValue.GetType().GetProperty("name") != null)
                nameValue = htmlAttributesValue.GetType().GetProperty("name").GetValue(htmlAttributesValue).ToString();

            var scriptBuilder = new StringBuilder();
            scriptBuilder.AppendFormat(
                "<script type='text/javascript'>\n" +
                "$(document).ready(function() {{\n" +
                "   $(\"#text_{0}\").on('keypress', function (e) {{\n" +
                "       if (e.keyCode == 13)\n" +
                "       {{\n" +
                "           search_{0}();\n" +
                "           e.preventDefault();\n" +
                "       }};\n" +
                "   }});\n" +
                "   $(\"#text_{0}\").on('keydown', function (e) {{\n" +
                "       if (e.keyCode == 8 && $(\"#value_{0}\").val() != '') {{\n" +
                "           $(\"#text_{0}\").val('');\n" +
                "           $(\"#value_{0}\").val('');\n" +
                "           $(\"#text_{0}\").next().children('.fa-check-circle').fadeOut('fast', function() {{\n" +
                "               $(\"#text_{0}\").next().children(\".fa-ellipsis-h\").fadeIn('fast');\n" +
                "           }});\n" +
                "           e.preventDefault();\n" +
                "       }};\n" +
                "   }});\n" +
                "}});\n" +
                "\n" +
                "function search_{0}()\n" +
                "{{\n" +
                "   $.ajax({{\n" +
                "       method: 'POST',\n" +
                "       url: '{1}',\n" +
                "       data:\n" +
                "       {{\n" +
                "           SearchTerm: $('#text_{0}').val()\n" +
                "       }},\n" +
                "       success: function(data) {{\n" +
                "           $(\"#searchModal_{0}\").find('.list-group').empty();\n" +
                "           if (data.length > 0)\n" +
                "           {{\n" +
                "               $(data).each(function(index, elem) {{\n" +
                "                   $(\"#searchModal_{0}\").find('.list-group').append(\n" +
                "                       \"<a href='#' class='list-group-item list-group-item-info list-group-item-action'" +
                "                            data-toggle='list' data-val='{{0}}' data-text='{{1}}' ondblclick='select_{0}()'>{{1}}</a>\"\n" +
                "                           .format(elem.searchId, elem.searchText));\n" +
                "               }});\n" +
                "           }}\n" +
                "           else\n" +
                "           {{\n" +
                "           $(\"#searchModal_{0}\").find('.list-group').append(\n" +
                "               \"'<button type='button' class='list-group-item'>Keine Treffer</button>'\");\n" +
                "           }}\n" +
                "           $(\"#searchModal_{0}\").modal('show');\n" +
                "       }},\n" +
                "       error: function(msg) {{\n" +
                "           \n" +
                "       }}\n" +
                "   }});\n" +
                "}}\n" +
                "\n" +
                "function select_{0}()\n" +
                "{{\n" +
                "   $(\"#searchModal_{0}\").modal('hide');\n" +
                "   var val = $(\"#searchModal_{0}\").find('a.active').attr('data-val');\n" +
                "   var text = $(\"#searchModal_{0}\").find('a.active').attr('data-text');\n" +
                "   $(\"#value_{0}\").val(val);\n" +
                "   $(\"#text_{0}\").val(text);\n" +
                "   $(\"#text_{0}\").next().children('.fa-ellipsis-h').fadeOut('fast', function() {{\n" +
                "       $(\"#text_{0}\").next().children('.fa-check-circle').fadeIn('fast');\n" +
                "       $(\"#text_{0}\").focus();\n" +
                "   }});\n" +
                "}}" +
                "</script>",
                id, 
                searchActionUrl
            );

            var contentBuilder = new StringBuilder();
            contentBuilder.AppendFormat(
                "<div class='input-group'>\n" +
                    "<div class='input-group-prepend'>\n" +
                        "<button class='btn btn-info fas fa-search' type='button' onclick='search_{0}()'></button>\n" +
                    "</div>\n" +
                    "<input id='text_{0}' type='text' name='{1}' value='{2}' class='form-control' placeholder='Suchbegriff'\n" +
                    "       aria-label='Suchbegriff' aria-describedby='basic-addon1' {5} />\n" +
                    "<div class='input-group-append'>\n" +
                    "   <span class='input-group-text bg-success text-white rounded-right fas fa-cust-lh fa-check-circle' {7}></span>\n" +
                    "   <span class='input-group-text rounded-right fas fa-cust-lh fa-ellipsis-h' {8}></span>\n" +
                    "</div>\n" +
                "</div>\n" +
                "\n" +
                "<input id='value_{0}' type='hidden' name='{3}' value='{4}' {6} />\n" +
                "\n" +
                "<div id='searchModal_{0}' class='modal fade'>\n" +
                    "<div class='modal-dialog'>\n" +
                        "<div class='modal-content'>\n" +
                            "<div class='modal-header'>\n" +
                                "<h4 class='modal-title'>Suchergebnisse</h4>\n" +
                            "</div>\n" +
                            "<div class='modal-body overflow-auto' style='max-height: 400px'>\n" +
                                "<div class='list-group'>\n" +
                                "</div>\n" +
                            "</div>\n" +
                            "<div class='modal-footer'>\n" +
                                "<div class='btn btn-secondary' data-dismiss='modal'>Schließen</div>\n" +
                                "<div class='btn btn-success' onclick='select_{0}()'>Auswählen</div>\n" +
                            "</div>\n" +
                        "</div>\n" +
                    "</div>\n" +
                "</div>",
                id,
                nameText,
                funcText,
                nameValue,
                funcVal,
                String.Join(" ", attributesText),
                String.Join(" ", attributesValue),
                (htmlHelper.ViewData.Model == null || isDefaultValue ? "style='display: none'" : String.Empty),
                (htmlHelper.ViewData.Model == null || isDefaultValue ? String.Empty : "style='display: none'")
            );

            var htmlString = String.Format("{0}\n{1}", scriptBuilder.ToString(), contentBuilder.ToString());

            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
    }
}
