using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Globalization;
using Microsoft.VisualBasic.CompilerServices;
using System.Data;

namespace MESws
{

    /// <summary>
    /// 20210513 13871,Json相關擴充方法
    /// </summary>
    internal static class JsonSerializationExceptionHelper
    {
        public static JsonSerializationException Create(this JsonReader reader, string format, params object[] args)
        {
            IJsonLineInfo lineInfo = reader as IJsonLineInfo;
            string path = reader == null ? null : reader.Path;
            string message = string.Format(CultureInfo.InvariantCulture, format, args);

            if (Convert.ToBoolean(!message.EndsWith(Environment.NewLine, StringComparison.Ordinal)))
            {
                message = message.Trim();
                if (Convert.ToBoolean(!message.EndsWith(".", StringComparison.Ordinal)))
                    message += ".";
                message += " ";
            }

            message += string.Format(CultureInfo.InvariantCulture, "Path '{0}'", path);
            if (lineInfo != null && lineInfo.HasLineInfo()) message += String.Format(CultureInfo.InvariantCulture, ", line {0}, position {1}", lineInfo.LineNumber, lineInfo.LinePosition);
            message += ".";
            return new JsonSerializationException(Convert.ToString(message));
        }
    }

    internal static class StringUtils
    {
        public static string FormatWith(this string format, IFormatProvider provider, object arg0)
        {
            return format.FormatWith(provider, new[] { arg0 });
        }

        private static string FormatWith(string format, IFormatProvider provider, params object[] args)
        {
            return string.Format(provider, format, args);
        }
    }

    internal static class JsonReaderExtensions
    {
        public static void ReadAndAssert(this JsonReader reader)
        {
            if (reader == null)
                throw new ArgumentNullException("reader");

            if (!reader.Read())
            {
                throw reader.Create("Unexpected end when reading JSON.");
            }
        }
    }

    /// <summary>
    /// 20210513 13871,Json DataTableConverter類別,自定義轉型方法,排除浮點數被轉成整數問題
    /// </summary>
    internal class TypeInferringDataTableConverter : Newtonsoft.Json.Converters.DataTableConverter
    {

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            if (reader.TokenType == JsonToken.Null)
            {
                return null;
            }

            DataTable dt = existingValue as DataTable;

            if (dt == null)
            {
                dt = objectType == typeof(DataTable) ? new DataTable() : (DataTable)Activator.CreateInstance(objectType);
            }

            if (reader.TokenType == JsonToken.PropertyName)
            {
                dt.TableName = Convert.ToString(reader.Value);
                reader.ReadAndAssert();

                if (reader.TokenType == JsonToken.Null)
                {
                    return dt;
                }
            }

            if (reader.TokenType != JsonToken.StartArray)
            {
                throw reader.Create("Unexpected JSON token when reading DataTable. Expected StartArray, got {0}.".FormatWith(CultureInfo.InvariantCulture, reader.TokenType));
            }

            reader.ReadAndAssert();
            object ambiguousColumnTypes = new HashSet<string>();

            while (reader.TokenType != JsonToken.EndArray)
            {
                CreateRow(reader, dt, serializer, (HashSet<string>)ambiguousColumnTypes);
                reader.ReadAndAssert();
            }

            return dt;
        }

        private static void CreateRow(JsonReader reader, DataTable dt, JsonSerializer serializer, HashSet<string> ambiguousColumnTypes)
        {
            var dr = dt.NewRow();
            reader.ReadAndAssert();

            while (reader.TokenType == JsonToken.PropertyName)
            {
                string columnName = Convert.ToString(reader.Value);
                reader.ReadAndAssert();
                DataColumn column = dt.Columns[columnName];   // 20211203 13871,DataTable內欄位如果是陣列、物件則不處理

                if (column == null)
                {
                    bool isAmbiguousType = false;
                    Type columnType = GetColumnDataType(reader, ref isAmbiguousType);
                    if (columnType == null)   //20211203 13871,DataTable內欄位如果是陣列、物件則不處理
                    {
                        reader.ReadAndAssert();
                        continue;
                    }
                    column = new DataColumn(columnName, columnType);
                    dt.Columns.Add(column);
                    if (isAmbiguousType) ambiguousColumnTypes.Add(columnName);
                }
                else if (ambiguousColumnTypes.Contains(columnName))
                {
                    bool isAmbiguousType = false;
                    Type newColumnType = GetColumnDataType(reader, ref isAmbiguousType);
                    if (!isAmbiguousType) ambiguousColumnTypes.Remove(columnName);


                    if (newColumnType != column.DataType)
                    {
                        column = ReplaceColumn(dt, column, newColumnType, serializer);
                    }
                }

                if (column.DataType == typeof(DataTable))
                {

                    if (reader.TokenType == JsonToken.StartArray)
                    {
                        reader.ReadAndAssert();
                    }

                    var nestedDt = new DataTable();
                    object nestedUnknownColumnTypes = new HashSet<string>();

                    while (reader.TokenType != JsonToken.EndArray)
                    {
                        CreateRow(reader, nestedDt, serializer, (HashSet<string>)nestedUnknownColumnTypes);
                        reader.ReadAndAssert();
                    }

                    dr[columnName] = nestedDt;
                }
                else if (column.DataType.IsArray && column.DataType != typeof(byte[]))
                {

                    if (reader.TokenType == JsonToken.StartArray)
                    {
                        reader.ReadAndAssert();
                    }

                    var o = new List<object>();

                    while (reader.TokenType != JsonToken.EndArray)
                    {
                        o.Add(reader.Value);
                        reader.ReadAndAssert();
                    }

                    var destinationArray = Array.CreateInstance(column.DataType.GetElementType(), o.Count);
                    Array.Copy(o.ToArray(), destinationArray, o.Count);
                    dr[columnName] = destinationArray;
                }
                else
                {
                    var columnValue = reader.Value != null ? serializer.Deserialize(reader, column.DataType) ?? DBNull.Value : DBNull.Value;
                    dr[columnName] = columnValue;
                }

                reader.ReadAndAssert();
            }

            dr.EndEdit();
            dt.Rows.Add(dr);
        }

        private static object RemapValue(object oldValue, Type newType, JsonSerializer serializer)
        {
            if (oldValue == null)
                return null;
            if (Convert.ToBoolean(Operators.ConditionalCompareObjectEqual(oldValue, DBNull.Value, false)))
                return oldValue;
            return JToken.FromObject(oldValue, serializer).ToObject(newType, serializer);
        }

        private static DataColumn ReplaceColumn(DataTable dt, DataColumn column, Type newColumnType, JsonSerializer serializer)
        {
            List<object> newValues = Enumerable.Range(0, dt.Rows.Count).Select(i => dt.Rows[i]).Select(r => RemapValue(r[column], newColumnType, serializer)).ToList();
            int ordinal = column.Ordinal;
            string name = column.ColumnName;
            string @namespace = column.Namespace;
            DataColumn newColumn = new DataColumn(Convert.ToString(name), newColumnType);
            newColumn.Namespace = @namespace;
            dt.Columns.Remove(column);
            dt.Columns.Add(newColumn);
            newColumn.SetOrdinal(ordinal);

            for (int i = 0, loopTo = dt.Rows.Count - 1; i <= loopTo; i++)
                dt.Rows[i][newColumn] = newValues[i];

            return (DataColumn)newColumn;
        }

        private static Type GetColumnDataType(JsonReader reader, ref bool isAmbiguous)
        {
            var tokenType = reader.TokenType;

            switch (tokenType)
            {
                case JsonToken.Integer:
                    {
                        isAmbiguous = false;
                        return Type.GetType("System.Decimal");
                    }
                case JsonToken.Float:
                    {
                        isAmbiguous = false;
                        return Type.GetType("System.Decimal");
                    }
                case JsonToken.Null:
                case JsonToken.Undefined:
                    {
                        isAmbiguous = false;
                        return typeof(string);
                    }
                case JsonToken.Boolean:
                    {
                        return Type.GetType("System.Boolean");
                    }
                // 20211203 13871,DataTable內欄位如果是陣列、物件則不處理
                case JsonToken.StartArray:
                    {
                        isAmbiguous = false;
                        int iCount = 1;
                        while (iCount > 0)
                        {
                            reader.ReadAndAssert();
                            var tokenType2 = reader.TokenType;
                            if (tokenType2 == JsonToken.StartArray)
                                iCount += 1;
                            if (tokenType2 == JsonToken.EndArray)
                                iCount -= 1;
                        }
                        return null;
                    }
                case JsonToken.StartObject:
                    {
                        isAmbiguous = false;
                        int iCount = 1;
                        while (iCount > 0)
                        {
                            reader.ReadAndAssert();
                            var tokenType2 = reader.TokenType;
                            if (tokenType2 == JsonToken.StartObject)
                                iCount += 1;
                            if (tokenType2 == JsonToken.EndObject)
                                iCount -= 1;
                        }
                        return null;
                    }

                default:
                    {
                        isAmbiguous = false;
                        return reader.ValueType;
                    }
            }
        }
    }


}