#region License
/*
 * Copyright © 2002-2006 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#endregion

#region Imports

using System;
using System.Diagnostics;
using System.Reflection;
using System.Reflection.Emit;
using System.Security;
using System.Security.Permissions;
using System.Web.UI;
using Spring.Reflection.Dynamic;
using Spring.Util;

#endregion

namespace Spring.Web.Support
{
    /// <summary>
    /// Helper class for easier access to reflected Control members.
    /// </summary>
    /// <author>Erich Eichinger</author>
    internal class ControlAccessor
    {
        private delegate ControlCollection CreateControlCollectionDelegate(Control target);
        private delegate void AddedControlDelegate(Control target, Control control, int index);
        private delegate void RemovedControlDelegate(Control target, Control control);
        private delegate void VoidMethodDelegate(Control target);

        private static readonly MethodInfo s_miClear;
        private static readonly SafeField ControlsArrayField;

#if NET_2_0
        private static readonly CreateControlCollectionDelegate BaseCreateControlCollection;
        private static readonly AddedControlDelegate BaseAddedControl;
        private static readonly RemovedControlDelegate BaseRemovedControl;
        private static readonly VoidMethodDelegate BaseClearNamingContainer;

#else
        private class DynamicMethodWrapper
        {
            private IDynamicMethod _method;

            public DynamicMethodWrapper(IDynamicMethod method)
            {
                _method = method;
            }

            public static CreateControlCollectionDelegate CreateControlCollection(MethodInfo methodInfo)
            {
                IDynamicMethod method = new SafeMethod(methodInfo);
                return new CreateControlCollectionDelegate(new DynamicMethodWrapper(method).CreateControlCollectionInternal);
            }

            public static AddedControlDelegate AddedControl(MethodInfo methodInfo)
            {
                IDynamicMethod method = new SafeMethod(methodInfo);
                return new AddedControlDelegate(new DynamicMethodWrapper(method).AddedControlInternal);
            }

            public static RemovedControlDelegate RemovedControl(MethodInfo methodInfo)
            {
                IDynamicMethod method = new SafeMethod(methodInfo);
                return new RemovedControlDelegate(new DynamicMethodWrapper(method).RemovedControlInternal);
            }

            public static VoidMethodDelegate ClearNamingContainer(MethodInfo methodInfo)
            {
                IDynamicMethod method = new SafeMethod(methodInfo);
                return new VoidMethodDelegate(new DynamicMethodWrapper(method).ClearNamingContainerInternal);
            }

            private ControlCollection CreateControlCollectionInternal(Control target)
            {
                return (ControlCollection) _method.Invoke(target, null);
            }

            private void AddedControlInternal(Control target, Control control, int index)
            {
                _method.Invoke(target, new object[] { control, index });
            }

            private void RemovedControlInternal(Control target, Control control)
            {
                _method.Invoke(target, new object[] { control });
            }

            private void ClearNamingContainerInternal(Control target)
            {
                _method.Invoke(target, null);
            }
        }

        private static readonly CreateControlCollectionDelegate BaseCreateControlCollection = DynamicMethodWrapper.CreateControlCollection(GetMethod("CreateControlCollection"));
        private static readonly AddedControlDelegate BaseAddedControl = DynamicMethodWrapper.AddedControl(GetMethod("AddedControl"));
        private static readonly RemovedControlDelegate BaseRemovedControl = DynamicMethodWrapper.RemovedControl(GetMethod("RemovedControl"));
        private static readonly VoidMethodDelegate BaseClearNamingContainer = DynamicMethodWrapper.ClearNamingContainer(GetMethod("ClearNamingContainer"));
#endif

        static ControlAccessor()
        {
            SafeField fldControls = null;
            MethodInfo fnClear = null;
#if NET_2_0
            SecurityCritical.ExecutePrivileged(new PermissionSet(PermissionState.Unrestricted), delegate
            {
#endif
                fnClear = GetMethod("Clear");
                fldControls = new SafeField(typeof(ControlCollection).GetField("_controls", BindingFlags.Instance | BindingFlags.NonPublic));
#if NET_2_0
            });
#endif
            s_miClear = fnClear;
            ControlsArrayField = fldControls;

#if NET_2_0
            CreateControlCollectionDelegate fnBaseCreateControlCollection = null;
            AddedControlDelegate fnBaseAddedControl = null;
            RemovedControlDelegate fnBaseRemovedControl = null;
            VoidMethodDelegate fnBaseClearNamingContainer = null;
#if NET_2_0
            SecurityCritical.ExecutePrivileged(new PermissionSet(PermissionState.Unrestricted), delegate
            {
#endif
                fnBaseCreateControlCollection = (CreateControlCollectionDelegate)Delegate.CreateDelegate(typeof(CreateControlCollectionDelegate), GetMethod("CreateControlCollection"));
                fnBaseAddedControl = (AddedControlDelegate)Delegate.CreateDelegate(typeof(AddedControlDelegate), GetMethod("AddedControl"));
                fnBaseRemovedControl = (RemovedControlDelegate)Delegate.CreateDelegate(typeof(RemovedControlDelegate), GetMethod("RemovedControl"));
                fnBaseClearNamingContainer = (VoidMethodDelegate)Delegate.CreateDelegate(typeof(VoidMethodDelegate), GetMethod("ClearNamingContainer"));
#if NET_2_0
            });
#endif
            BaseCreateControlCollection = fnBaseCreateControlCollection;
            BaseAddedControl = fnBaseAddedControl;
            BaseRemovedControl = fnBaseRemovedControl;
            BaseClearNamingContainer = fnBaseClearNamingContainer;
#endif

        }

        private static MethodInfo GetMethod(string name)
        {
            return typeof(Control).GetMethod(name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
        }
        private static FieldInfo GetField(string name)
        {
            return typeof(Control).GetField(name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
        }


        private readonly Control _targetControl;

        /// <summary>
        /// Instantiates a new Accessor.
        /// </summary>
        /// <param name="control"></param>
        public ControlAccessor(Control control)
        {
            this._targetControl = control;
        }

        /// <summary>
        /// Returns the underlying ControlCollection instance.
        /// </summary>
        public Control GetTarget()
        {
            return _targetControl;
        }

        /// <summary>
        /// Gets or sets the ControlCollection of the target without accessing the target's <see cref="Control.Controls"/> property.
        /// </summary>
        /// <remarks>
        /// If the underlying collection is null, it is automatically created.
        /// </remarks>
        public ControlCollection Controls
        {
            get
            {
                ControlCollection controls = GetChildControlCollection();
                if (controls == null)
                {
                    controls = InterceptControlCollectionStrategy.TryCreateCollection(_targetControl);
                    if (controls == null)
                    {
                        controls = BaseCreateControlCollection(_targetControl);                        
                    }
                    SetChildControlCollection(controls);
                }
                return controls;
            }
            set { SetChildControlCollection(value); }
        }

        public void AddedControl(Control control, int index)
        {
            BaseAddedControl(_targetControl, control, index);
        }

        public void RemovedControl(Control control)
        {
            BaseRemovedControl(_targetControl, control);

            if (!_targetControl.HasControls())
            {
                // clear naming table etc. if collection has been cleared
                // this is because we can't intercept Control.ClearNamingTable(),
                // which is called by ControlCollection.Clear() after removing all controls
                StackFrame frame = new StackFrame(3, false);
                if (frame.GetMethod() == s_miClear)
                {
                    if (_targetControl is INamingContainer)
                    {
                        //s_miClearNamingContainer.Invoke(_targetControl, null);
                        BaseClearNamingContainer(_targetControl);
                    }
                }
            }
        }

        public void SetControlAt(Control control, int index)
        {
            Control[] controls = (Control[]) ControlsArrayField.GetValue(this.Controls);
            controls[index] = control;
        }

#if NET_2_0
        private delegate ControlCollection GetControlsDelegate(Control target);
        private delegate void SetControlsDelegate(Control target, ControlCollection controls);

        private static readonly GetControlsDelegate GetChildControlCollectionInternal = GetGetControlsDelegate();
        private static readonly SetControlsDelegate SetChildControlCollectionInternal = GetSetControlsDelegate();

        private ControlCollection GetChildControlCollection()
        {
            return GetChildControlCollectionInternal(_targetControl);
        }

        private void SetChildControlCollection(ControlCollection controls)
        {
            SetChildControlCollectionInternal(_targetControl, controls);
        }

        private static GetControlsDelegate GetGetControlsDelegate()
        {
            FieldInfo occasionalFields = GetField("_occasionalFields");
            MethodInfo ensureOccasionalFields = GetMethod("EnsureOccasionalFields");
            FieldInfo controls = occasionalFields.FieldType.GetField("Controls");

            GetControlsDelegate handler = null;

            SecurityCritical.ExecutePrivileged(new PermissionSet(PermissionState.Unrestricted), delegate
            {
                System.Reflection.Emit.DynamicMethod dm = new System.Reflection.Emit.DynamicMethod("get_Controls", typeof(ControlCollection), new Type[] { typeof(Control) }, typeof(Control).Module, true);
                ILGenerator il = dm.GetILGenerator();
                Label occFieldsNull = il.DefineLabel();
                Label retControls = il.DefineLabel();
                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Ldfld, occasionalFields);
                il.Emit(OpCodes.Brfalse_S, occFieldsNull);
                il.MarkLabel(retControls);
                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Ldfld, occasionalFields);
                il.Emit(OpCodes.Ldfld, controls);
                il.Emit(OpCodes.Ret);
                il.MarkLabel(occFieldsNull);
                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Call, ensureOccasionalFields);
                il.Emit(OpCodes.Br, retControls);
                handler = (GetControlsDelegate) dm.CreateDelegate(typeof(GetControlsDelegate));
            });
            return handler;
        }

        private static SetControlsDelegate GetSetControlsDelegate()
        {
            FieldInfo occasionalFields = GetField("_occasionalFields");
            MethodInfo ensureOccasionalFields = GetMethod("EnsureOccasionalFields");
            FieldInfo controls = occasionalFields.FieldType.GetField("Controls");

            SetControlsDelegate handler = null;
            SecurityCritical.ExecutePrivileged(new PermissionSet(PermissionState.Unrestricted), delegate 
            {
                System.Reflection.Emit.DynamicMethod dm = new System.Reflection.Emit.DynamicMethod("set_Controls", null, new Type[] { typeof(Control), typeof(ControlCollection) }, typeof(Control).Module, true);
                ILGenerator il = dm.GetILGenerator();
                Label occFieldsNull = il.DefineLabel();
                Label setControls = il.DefineLabel();
                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Ldfld, occasionalFields);
                il.Emit(OpCodes.Brfalse_S, occFieldsNull);
                il.MarkLabel(setControls);
                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Ldfld, occasionalFields);
                il.Emit(OpCodes.Ldarg_1);
                il.Emit(OpCodes.Stfld, controls);
                il.Emit(OpCodes.Ret);
                il.MarkLabel(occFieldsNull);
                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Call, ensureOccasionalFields);
                il.Emit(OpCodes.Br, setControls);
                handler = (SetControlsDelegate) dm.CreateDelegate(typeof(SetControlsDelegate));
            });
            return handler;
        }
#else
	    private static readonly IDynamicField fControls = new SafeField(GetField("_controls"));

		private ControlCollection GetChildControlCollection()
		{
		    return (ControlCollection)fControls.GetValue(_targetControl);
		}

		private void SetChildControlCollection(ControlCollection controls)
		{
            fControls.SetValue(_targetControl, controls);
		}
#endif
    }
}