using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Dynamic;
using System.Xml.Linq;
using System.Text.Json;
using System.Collections;

namespace EasyDevCore.Common.Wrapper
{    
    /// <summary>
    /// 
    /// </summary>
    public class DynamicJson : DynamicObject
    {
        private readonly IDictionary<string, object> _dictionary;

        /// <summary>
        /// Initializes a new instance of the <see cref="DynamicJson"/> class.
        /// </summary>
        /// <param name="jsonString">The json string.</param>
        private DynamicJson(string jsonString):this(JsonSerializer.Deserialize<Dictionary<string, object>>(jsonString))
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="DynamicJson"/> class.
        /// </summary>
        /// <param name="dictionary">The dictionary.</param>
        private DynamicJson(IDictionary<string, object> dictionary)
        {
            _dictionary = dictionary;
        }

        /// <summary>
        /// Parses the specified XML string.
        /// </summary>
        /// <param name="jsonString">The json string.</param>
        /// <returns></returns>
        public static DynamicJson Parse(string jsonString)
        {
            return new DynamicJson(jsonString);
        }

        /// <summary>
        /// Parses the specified dictionary.
        /// </summary>
        /// <param name="dictionary">The dictionary.</param>
        /// <returns></returns>
        public static DynamicJson Parse(IDictionary<string, object> dictionary)
        {
            return new DynamicJson(dictionary);
        }

        /// <summary>
        /// Loads the specified filename.
        /// </summary>
        /// <param name="filename">The filename.</param>
        /// <returns></returns>
        public static DynamicJson Load(string filename)
        {
            return new DynamicJson(System.IO.File.ReadAllText(filename));
        }

        /// <summary>
        /// Provides the implementation for operations that get member values. Classes derived from the <see cref="T:System.Dynamic.DynamicObject" /> class can override this method to specify dynamic behavior for operations such as getting a value for a property.
        /// </summary>
        /// <param name="binder">Provides information about the object that called the dynamic operation. The <c>binder.Name</c> property provides the name of the member on which the dynamic operation is performed. For example, for the <c>Console.WriteLine(sampleObject.SampleProperty)</c> statement, where <c>sampleObject</c> is an instance of the class derived from the <see cref="T:System.Dynamic.DynamicObject" /> class, <c>binder.Name</c> returns "SampleProperty". The <c>binder.IgnoreCase</c> property specifies whether the member name is case-sensitive.</param>
        /// <param name="result">The result of the get operation. For example, if the method is called for a property, you can assign the property value to <paramref name="result" />.</param>
        /// <returns>
        ///   <see langword="true" /> if the operation is successful; otherwise, <see langword="false" />. If this method returns <see langword="false" />, the run-time binder of the language determines the behavior. (In most cases, a run-time exception is thrown.)
        /// </returns>
        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            if (!_dictionary.TryGetValue(binder.Name, out result))
            {
                // return null to avoid exception.  caller can check for null this way...
                result = null;
                return true;
            }

            result = WrapResultObject(result);
            return true;
        }

        /// <summary>
        /// Wraps the result object.
        /// </summary>
        /// <param name="result">The result.</param>
        /// <returns></returns>
        private static object WrapResultObject(object result)
        {
            var dictionary = result as IDictionary<string, object>;
            if (dictionary != null)
                return new DynamicJson(dictionary);

            var arrayList = result as ArrayList;
            if (arrayList != null && arrayList.Count > 0)
            {
                return arrayList[0] is IDictionary<string, object>
                    ? new List<object>(arrayList.Cast<IDictionary<string, object>>().Select(x => new DynamicJson(x)))
                    : new List<object>(arrayList.Cast<object>());
            }

            return result;
        }

        /// <summary>
        /// Provides the implementation for operations that get a value by index. Classes derived from the <see cref="T:System.Dynamic.DynamicObject" /> class can override this method to specify dynamic behavior for indexing operations.
        /// </summary>
        /// <param name="binder">Provides information about the operation.</param>
        /// <param name="indexes">The indexes that are used in the operation. For example, for the <c>sampleObject[3]</c> operation in C# (<c>sampleObject(3)</c> in Visual Basic), where <c>sampleObject</c> is derived from the <see langword="DynamicObject" /> class, <c>indexes[0]</c> is equal to 3.</param>
        /// <param name="result">The result of the index operation.</param>
        /// <returns>
        ///   <see langword="true" /> if the operation is successful; otherwise, <see langword="false" />. If this method returns <see langword="false" />, the run-time binder of the language determines the behavior. (In most cases, a run-time exception is thrown.)
        /// </returns>
        public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
        {
            if (indexes.Length == 1 && indexes[0] != null)
            {
                if (!_dictionary.TryGetValue(indexes[0].ToString(), out result))
                {
                    // return null to avoid exception.  caller can check for null this way...
                    result = null;
                    return true;
                }

                result = WrapResultObject(result);
                return true;
            }

            return base.TryGetIndex(binder, indexes, out result);
        }
    }

}
