/* ****************************************************************************
 *
 * Copyright (c) Microsoft Corporation. 
 *
 * This source code is subject to terms and conditions of the Microsoft Public License. A 
 * copy of the license can be found in the License.html file at the root of this distribution. If 
 * you cannot locate the  Microsoft Public License, please send an email to 
 * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 
 * by the terms of the Microsoft Public License.
 *
 * You must not remove this notice, or any other, from this software.
 *
 *
 * ***************************************************************************/

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Dynamic;
using System.Reflection;
using System.Threading;

using Microsoft.Contracts;
using Microsoft.Scripting.Actions.Calls;

namespace Microsoft.Scripting.Actions {
    /// <summary>
    /// MethodGroup's represent a unique collection of method's.  Typically this
    /// unique set is all the methods which are overloaded by the same name including
    /// methods with different arity.  These methods represent a single logically
    /// overloaded element of a .NET type.
    /// 
    /// The base DLR binders will produce MethodGroup's when provided with a MemberGroup
    /// which contains only methods.  The MethodGroup's will be unique instances per
    /// each unique group of methods.
    /// </summary>
    public class MethodGroup : MemberTracker {
        private MethodTracker[] _methods;
        private Dictionary<TypeList, MethodGroup> _boundGenerics;

        internal MethodGroup(params MethodTracker[] methods) {
            _methods = methods;
        }

        public override TrackerTypes MemberType {
            get { return TrackerTypes.MethodGroup; }
        }

        public override Type DeclaringType {
            get { return _methods[0].DeclaringType; }
        }

        public override string Name {
            get { return _methods[0].Name; }
        }

        public bool ContainsInstance {
            get {
                foreach (MethodTracker mt in _methods) {
                    if (!mt.IsStatic) return true;
                }
                return false;
            }
        }

        public bool ContainsStatic {
            get {
                foreach (MethodTracker mt in _methods) {
                    if (mt.IsStatic) return true;
                }
                return false;
            }
        }

        public IList<MethodTracker> Methods {
            get {
                return _methods;
            }
        }

        public MethodBase[] GetMethodBases() {
            MethodBase[] methods = new MethodBase[Methods.Count];
            for (int i = 0; i < Methods.Count; i++) {
                methods[i] = Methods[i].Method;
            }
            return methods;
        }

        public override DynamicMetaObject GetValue(OverloadResolverFactory resolverFactory, ActionBinder binder, Type type) {
            return base.GetValue(resolverFactory, binder, type);
        }

        public override MemberTracker BindToInstance(DynamicMetaObject instance) {
            if (ContainsInstance) {
                return new BoundMemberTracker(this, instance);
            }

            return this;
        }

        protected internal override DynamicMetaObject GetBoundValue(OverloadResolverFactory resolverFactory, ActionBinder binder, Type type, DynamicMetaObject instance) {
            return binder.ReturnMemberTracker(type, BindToInstance(instance));
        }

        /// <summary>
        /// Returns a BuiltinFunction bound to the provided type arguments.  Returns null if the binding
        /// cannot be performed.
        /// </summary>
        public MethodGroup MakeGenericMethod(Type[] types) {
            TypeList tl = new TypeList(types);

            // check for cached method first...
            MethodGroup mg;
            if (_boundGenerics != null) {
                lock (_boundGenerics) {
                    if (_boundGenerics.TryGetValue(tl, out mg)) {
                        return mg;
                    }
                }
            }

            // Search for generic targets with the correct arity (number of type parameters).
            // Compatible targets must be MethodInfos by definition (constructors never take
            // type arguments).
            List<MethodTracker> targets = new List<MethodTracker>(Methods.Count);
            foreach (MethodTracker mt in Methods) {
                MethodInfo mi = mt.Method;
                if (mi.ContainsGenericParameters && mi.GetGenericArguments().Length == types.Length)
                    targets.Add((MethodTracker)MemberTracker.FromMemberInfo(mi.MakeGenericMethod(types)));
            }

            if (targets.Count == 0) {
                return null;
            }

            // Build a new MethodGroup that will contain targets with bound type arguments & cache it.
            mg = new MethodGroup(targets.ToArray());

            EnsureBoundGenericDict();

            lock (_boundGenerics) {
                _boundGenerics[tl] = mg;
            }

            return mg;
        }

        private void EnsureBoundGenericDict() {
            if (_boundGenerics == null) {
                Interlocked.CompareExchange<Dictionary<TypeList, MethodGroup>>(
                    ref _boundGenerics,
                    new Dictionary<TypeList, MethodGroup>(1),
                    null);
            }
        }

        private class TypeList {
            private Type[] _types;

            public TypeList(Type[] types) {
                Debug.Assert(types != null);
                _types = types;
            }

            [Confined]
            public override bool Equals(object obj) {
                TypeList tl = obj as TypeList;
                if (tl == null || _types.Length != tl._types.Length) return false;

                for (int i = 0; i < _types.Length; i++) {
                    if (_types[i] != tl._types[i]) return false;
                }
                return true;
            }

            [Confined]
            public override int GetHashCode() {
                int hc = 6551;
                foreach (Type t in _types) {
                    hc = (hc << 5) ^ t.GetHashCode();
                }
                return hc;
            }
        }
    }
}
