using System;
using System.Linq;
using System.Reflection;

namespace Aurora.Utils
{
    public static class ReflectionUtils
    {
        private const char IndexBeginOp = '[';
        private const char IndexEndOp = ']';
        private const char PropertyOfOp = '.';

        public static object GetValueByPath(object obj, string path)
        {
            object result = obj;
            var tokens = path?.Split(IndexBeginOp, PropertyOfOp).ToList();
            foreach (var token in tokens)
            {
                if (result == null)
                    break;

                //  Property
                if (!token.Contains(IndexEndOp.ToString()))
                {
                    result = GetPropertyValue(result, token);
                }
                // Index
                else
                {
                    result = GetIndexValue(result, token.Replace(IndexEndOp.ToString(), ""));
                }
            }
            return result;
        }

        private static object GetPropertyValue(object obj, string propertyName)
        {
            try
            {
                return obj?.GetType().GetRuntimeProperty(propertyName)?.GetValue(obj);
            }
            catch
            {
                return null;
            }
        }

        private static object GetIndexValue(object obj, string index)
        {
            object result = null;
            var indexOperator = obj?.GetType().GetRuntimeProperty("Item");
            if (indexOperator != null)
            {
                var indexParameters = indexOperator.GetIndexParameters();
                // Looking up suitable index operator
                foreach (var parameter in indexParameters)
                {
                    bool isIndexOpWorked = true;
                    try
                    {
                        var indexVal = Convert.ChangeType(index, parameter.ParameterType);
                        result = indexOperator.GetValue(obj, new object[] { indexVal });
                    }
                    catch
                    {
                        isIndexOpWorked = false;
                    }

                    // If the index operator worked, skip looking up others
                    if (isIndexOpWorked)
                        break;
                }
            }
            return result;
        }
    }
}