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

using System;
using System.Reflection;
using System.Collections;
using System.Collections.Specialized;
using System.Collections.Generic;
using System.Diagnostics;

using IronPython.Compiler;
using IronPython.Modules;
using IronMath;

namespace IronPython.Runtime {
    /// <summary>
    /// OpsReflectedType is the Python representation of primitive CLI valuetypes,
    /// and some Python valuetypes. This allows the engine to present a uniform
    /// view of types, including features not supported by the CLI like
    /// inheriting from a valuetype.
    /// </summary>
    public class OpsReflectedType : ReflectedType {
        // Maps the OpsType back to the corresponding ReflectedType.
        internal static Dictionary<Type, ReflectedType> OpsTypeToType = new Dictionary<Type, ReflectedType>(10);

        // This is the type to extend for inheriting from the current ReflectedType.
        protected Type extensibleType;

        // This is the type that implements various functionality on behalf of the
        // current ReflectedType
        protected Type opsType;
        private CallTarget1 optCtor;

        public OpsReflectedType(string name, Type baseType, Type opsType, Type extensibleType, CallTarget1 optimizedCtor)
            :
            this(name, baseType, opsType, extensibleType) {
            optCtor = optimizedCtor;
        }

        public OpsReflectedType(string name, Type baseType, Type opsType, Type extensibleType)
            : base(baseType) {
            this.extensibleType = extensibleType;
            this.__name__ = name;
            this.opsType = opsType;

            OpsTypeToType[opsType] = this;
        }

        public override Type GetTypeToExtend() {
            return extensibleType;
        }

        public override bool IsPythonType {
            get {
                return true;
            }
        }

        public override object Call(ICallerContext context, object[] args) {
            if (optCtor != null && args.Length == 1) return optCtor(args[0]);

            PerfTrack.NoteEvent(PerfTrack.Categories.Methods, "TypeInvoke " + this.__name__ + args.Length);
            return base.Call(context, args);
        }

        public override object Call(params object[] args) {
            if (optCtor != null && args.Length == 1) return optCtor(args[0]);

            PerfTrack.NoteEvent(PerfTrack.Categories.Methods, "TypeInvoke " + this.__name__ + args.Length);
            return base.Call(args);
        }

        /// <summary>
        /// Find the methods implemented by opsType, and expose them from 
        /// the current ReflectedType
        /// </summary>
        protected override void AddOps() {
            foreach (MethodInfo mi in opsType.GetMethods()) {
                AddReflectedUnboundMethod(mi);
            }
        }

        private void AddReflectedUnboundMethod(MethodInfo mi) {
            if (!mi.IsStatic) return;

            string name;
            NameType nt = NameConverter.TryGetName(this, mi, out name);
            if (nt == NameType.None) return;

            FunctionType funcType = FunctionType.Method;
            if (mi.DeclaringType == typeof(ArrayOps)) funcType |= FunctionType.SkipThisCheck;
            if (nt == NameType.PythonMethod) funcType |= FunctionType.PythonVisible;

            RemoveNonOps(name);

            // store Python version
            StoreMethod<ReflectedUnboundMethod>(name, mi, funcType | FunctionType.OpsFunction);

            // store CLR version, if different.
            if (name != mi.Name) {
                RemoveNonOps(mi.Name);
                StoreMethod<ReflectedUnboundMethod>(mi.Name, mi, FunctionType.Method | FunctionType.OpsFunction);
            }
        }

        /// <summary>
        /// Ops functions overwrite non-ops functions.  Therefore we remove the
        /// non-ops version if both exist before inserting the new Ops method.
        /// </summary>
        private void RemoveNonOps(string strName) {
            SymbolId name = SymbolTable.StringToId(strName);
            object value;
            if (dict.TryGetValue(name, out value)) {
                ReflectedUnboundMethod rum = value as ReflectedUnboundMethod;
                BuiltinMethodDescriptor bimd;
                if (rum != null) {
                    if ((rum.FunctionType & FunctionType.OpsFunction) != 0) return;
                } else if ((bimd = value as BuiltinMethodDescriptor) != null) {
                    if ((bimd.template.FunctionType & FunctionType.OpsFunction) != 0) return;
                }

                dict.Remove(name);
            }
        }

        public override bool IsSubclassOf(object other) {
            //!!! This is an ugly special case to handle the fact the Python bool extends Python int
            if (type == typeof(bool)) {
                ReflectedType rt = other as ReflectedType;
                if (rt == null) return false;
                return rt.type == typeof(bool) || rt.type == typeof(int) || rt.type == typeof(object);
            }
            return base.IsSubclassOf(other);
        }
    }

    public class ReflectedArrayType : OpsReflectedType, IMapping {
        public ReflectedArrayType(string name, Type arrayType)
            : base(name, arrayType, typeof(ArrayOps), arrayType) {
        }

        public override object this[object index] {
            get {
                Type[] types = GetTypesFromTuple(index);
                if (types.Length != 1) throw Ops.TypeError("expected single type");

                return Ops.GetDynamicTypeFromType(types[0].MakeArrayType());
            }
            set {
                base[index] = value;
            }
        }

        public override object Call(ICallerContext context, object[] args) {
            if (args.Length != 1) throw Ops.TypeError("array expects one and only 1 argument");
            if (this.type == typeof(Array)) throw Ops.TypeError("general array type is not callable");

            return ArrayOps.CreateArray(this.type.GetElementType(), args[0]);
        }

        public override object Call(params object[] args) {
            if (args.Length != 1) throw Ops.TypeError("array expects one and only 1 argument");
            if (this.type == typeof(Array)) throw Ops.TypeError("general array type is not callable");

            return ArrayOps.CreateArray(this.type.GetElementType(), args[0]);
        }
    }

}
