﻿using System;
using System.Globalization;
using System.Reflection;
using Jint.Native;

namespace Jint.Runtime.Descriptors.Specialized
{
    public sealed class IndexDescriptor : PropertyDescriptor
    {
        private readonly Engine _engine;
        private readonly object _key;
        private readonly object _item;
        private readonly PropertyInfo _indexer;
        private readonly MethodInfo _containsKey;

        public IndexDescriptor(Engine engine, Type targetType, string key, object item)
        {
            _engine = engine;
            _item = item;

            // get all instance indexers with exactly 1 argument
            var indexers = targetType.GetProperties();

            // try to find first indexer having either public getter or setter with matching argument type
            foreach (var indexer in indexers)
            {
                if (indexer.GetIndexParameters().Length != 1) continue;
                if (indexer.GetGetMethod() != null || indexer.GetSetMethod() != null)
                {
                    var paramType = indexer.GetIndexParameters()[0].ParameterType;

                    if (_engine.ClrTypeConverter.TryConvert(key, paramType, CultureInfo.InvariantCulture, out _key))
                    {
                        _indexer = indexer;
                        // get contains key method to avoid index exception being thrown in dictionaries
                        _containsKey = targetType.GetMethod("ContainsKey", new Type[] { paramType });
                        break;

                    }
                }
            }

            // throw if no indexer found
            if (_indexer == null)
            {
                throw new InvalidOperationException("No matching indexer found.");
            }

            Writable = true;
        }


        public IndexDescriptor(Engine engine, string key, object item)
            : this(engine, item.GetType(), key, item)
        {
        }

        public override JsValue Value
        {
            get
            {
                var getter = _indexer.GetGetMethod();

                if (getter == null)
                {
                    throw new InvalidOperationException("Indexer has no public getter.");
                }

                object[] parameters = { _key };

                if (_containsKey != null)
                {
                    if ((_containsKey.Invoke(_item, parameters) as bool?) != true)
                    {
                        return JsValue.Undefined;
                    }
                }

                try
                {
                    return JsValue.FromObject(_engine, getter.Invoke(_item, parameters));
                }
                catch
                {
                    return JsValue.Undefined;
                }
            }

            set
            {
                var setter = _indexer.GetSetMethod();
                if (setter == null)
                {
                    throw new InvalidOperationException("Indexer has no public setter.");
                }

                object[] parameters = { _key, value != null ? value.ToObject() : null };
				try{
					setter.Invoke(_item, parameters);
				}catch(Exception e){
					UnityEngine.Debug.Log("Indexer failure: Can't assign " + value.ToObject().GetType() + " to " + _indexer.DeclaringType);
					throw e;
				}
            }
        }
    }
}