using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
namespace Godot
{
    /// 
    /// Represents an  whose members can be dynamically accessed at runtime through the Variant API.
    /// 
    /// 
    /// 
    /// The  class enables access to the Variant
    /// members of a  instance at runtime.
    /// 
    /// 
    /// This allows accessing the class members using their original names in the engine as well as the members from the
    /// script attached to the , regardless of the scripting language it was written in.
    /// 
    /// 
    /// 
    /// This sample shows how to use  to dynamically access the engine members of a .
    /// 
    /// dynamic sprite = GetNode("Sprite").DynamicGodotObject;
    /// sprite.add_child(this);
    ///
    /// if ((sprite.hframes * sprite.vframes) > 0)
    ///     sprite.frame = 0;
    /// 
    /// 
    /// 
    /// This sample shows how to use  to dynamically access the members of the script attached to a .
    /// 
    /// dynamic childNode = GetNode("ChildNode").DynamicGodotObject;
    ///
    /// if (childNode.print_allowed)
    /// {
    ///     childNode.message = "Hello from C#";
    ///     childNode.print_message(3);
    /// }
    /// 
    /// The ChildNode node has the following GDScript script attached:
    /// 
    /// // # ChildNode.gd
    /// // var print_allowed = true
    /// // var message = ""
    /// //
    /// // func print_message(times):
    /// //     for i in times:
    /// //         print(message)
    /// 
    /// 
    public class DynamicGodotObject : DynamicObject
    {
        /// 
        /// Gets the  associated with this .
        /// 
        public Object Value { get; }
        /// 
        /// Initializes a new instance of the  class.
        /// 
        /// 
        /// The  that will be associated with this .
        /// 
        /// 
        /// Thrown when the  parameter is null.
        /// 
        public DynamicGodotObject(Object godotObject)
        {
            if (godotObject == null)
                throw new ArgumentNullException(nameof(godotObject));
            this.Value = godotObject;
        }
        public override IEnumerable GetDynamicMemberNames()
        {
            return godot_icall_DynamicGodotObject_SetMemberList(Object.GetPtr(Value));
        }
        public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg, out object result)
        {
            switch (binder.Operation)
            {
                case ExpressionType.Equal:
                case ExpressionType.NotEqual:
                    if (binder.ReturnType == typeof(bool) || binder.ReturnType.IsAssignableFrom(typeof(bool)))
                    {
                        if (arg == null)
                        {
                            bool boolResult = Object.IsInstanceValid(Value);
                            if (binder.Operation == ExpressionType.Equal)
                                boolResult = !boolResult;
                            result = boolResult;
                            return true;
                        }
                        if (arg is Object other)
                        {
                            bool boolResult = (Value == other);
                            if (binder.Operation == ExpressionType.NotEqual)
                                boolResult = !boolResult;
                            result = boolResult;
                            return true;
                        }
                    }
                    break;
                default:
                    // We're not implementing operators <, <=, >, and >= (LessThan, LessThanOrEqual, GreaterThan, GreaterThanOrEqual).
                    // These are used on the actual pointers in variant_op.cpp. It's better to let the user do that explicitly.
                    break;
            }
            return base.TryBinaryOperation(binder, arg, out result);
        }
        public override bool TryConvert(ConvertBinder binder, out object result)
        {
            if (binder.Type == typeof(Object))
            {
                result = Value;
                return true;
            }
            if (typeof(Object).IsAssignableFrom(binder.Type))
            {
                // Throws InvalidCastException when the cast fails
                result = Convert.ChangeType(Value, binder.Type);
                return true;
            }
            return base.TryConvert(binder, out result);
        }
        public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
        {
            if (indexes.Length == 1)
            {
                if (indexes[0] is string name)
                {
                    return godot_icall_DynamicGodotObject_GetMember(Object.GetPtr(Value), name, out result);
                }
            }
            return base.TryGetIndex(binder, indexes, out result);
        }
        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            return godot_icall_DynamicGodotObject_GetMember(Object.GetPtr(Value), binder.Name, out result);
        }
        public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
        {
            return godot_icall_DynamicGodotObject_InvokeMember(Object.GetPtr(Value), binder.Name, args, out result);
        }
        public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value)
        {
            if (indexes.Length == 1)
            {
                if (indexes[0] is string name)
                {
                    return godot_icall_DynamicGodotObject_SetMember(Object.GetPtr(Value), name, value);
                }
            }
            return base.TrySetIndex(binder, indexes, value);
        }
        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            return godot_icall_DynamicGodotObject_SetMember(Object.GetPtr(Value), binder.Name, value);
        }
        [MethodImpl(MethodImplOptions.InternalCall)]
        internal extern static string[] godot_icall_DynamicGodotObject_SetMemberList(IntPtr godotObject);
        [MethodImpl(MethodImplOptions.InternalCall)]
        internal extern static bool godot_icall_DynamicGodotObject_InvokeMember(IntPtr godotObject, string name, object[] args, out object result);
        [MethodImpl(MethodImplOptions.InternalCall)]
        internal extern static bool godot_icall_DynamicGodotObject_GetMember(IntPtr godotObject, string name, out object result);
        [MethodImpl(MethodImplOptions.InternalCall)]
        internal extern static bool godot_icall_DynamicGodotObject_SetMember(IntPtr godotObject, string name, object value);
        #region We don't override these methods
        // Looks like this is not usable from C#
        //public override bool TryCreateInstance(CreateInstanceBinder binder, object[] args, out object result);
        // Object members cannot be deleted
        //public override bool TryDeleteIndex(DeleteIndexBinder binder, object[] indexes);
        //public override bool TryDeleteMember(DeleteMemberBinder binder);
        // Invocation on the object itself, e.g.: obj(param)
        //public override bool TryInvoke(InvokeBinder binder, object[] args, out object result);
        // No unnary operations to handle
        //public override bool TryUnaryOperation(UnaryOperationBinder binder, out object result);
        #endregion
    }
}