/* **********************************************************************************
 *
 * 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.Collections;
using System.Collections.Generic;

using System.Reflection;
using System.Reflection.Emit;

using System.Security.Permissions;

using IronPython.Runtime;
using IronMath;

namespace IronPython.Compiler {
    public class TypeGen {
        private ConstructorBuilder initializer; // The .cctor() of the type
        private CodeGen initGen; // The IL generator for the .cctor()
        private Dictionary<object, Slot> constants = new Dictionary<object,Slot>();
        private Dictionary<SymbolId, Slot> indirectSymbolIds = new Dictionary<SymbolId, Slot>();
        private List<TypeGen> nestedTypeGens = new List<TypeGen>();
        
        public readonly AssemblyGen myAssembly;
        public readonly TypeBuilder myType;
        public Slot moduleSlot;


        internal ConstructorBuilder DefaultConstructor;
        

        public TypeGen(AssemblyGen myAssembly, TypeBuilder myType) {
            this.myAssembly = myAssembly;
            this.myType = myType;
            //!!!AddModuleField(myType);
        }

        public CodeGen GetOrMakeInitializer() {
            if (initializer == null) {
                initializer = myType.DefineTypeInitializer();
                initGen = new CodeGen(this, initializer, initializer.GetILGenerator(), Type.EmptyTypes);
            }
            return initGen;
        }

        public Type FinishType() {
            if (initGen != null) initGen.Emit(OpCodes.Ret);

            Type ret = myType.CreateType();
            foreach (TypeGen ntb in nestedTypeGens) {
                ntb.FinishType();
            }
            //Console.WriteLine("finished: " + ret.FullName);
            return ret;
        }

        public TypeGen DefineNestedType(string name, Type parent) {
            TypeBuilder tb = myType.DefineNestedType(name, TypeAttributes.NestedPublic);
            tb.SetParent(parent);
            TypeGen ret = new TypeGen(myAssembly, tb);
            nestedTypeGens.Add(ret);

            ret.AddModuleField(typeof(PythonModule));
            
            return ret;
        }

        public void AddModuleField(Type moduleType) {
            FieldBuilder moduleField = this.myType.DefineField(PythonModule.ModuleFieldName,
                moduleType, FieldAttributes.Public | FieldAttributes.Static);  //!!!
            this.moduleSlot = new StaticFieldSlot(moduleField);
        }

        public Slot AddField(Type fieldType, string name) {
            FieldBuilder fb = myType.DefineField(name, fieldType, FieldAttributes.Public);
            return new FieldSlot(new ThisSlot(myType), fb);
        }
        public Slot AddStaticField(Type fieldType, string name) {
            FieldBuilder fb = myType.DefineField(name, fieldType, FieldAttributes.Public | FieldAttributes.Static);
            return new StaticFieldSlot(fb);
        }

        public CodeGen DefineExplicitInterfaceImplementation(MethodInfo baseMethod) {
            MethodAttributes attrs = baseMethod.Attributes & ~(MethodAttributes.Abstract | MethodAttributes.Public);
            attrs |= MethodAttributes.NewSlot | MethodAttributes.Final;

            MethodBuilder mb = myType.DefineMethod(
                baseMethod.DeclaringType.Name + "." + baseMethod.Name,
                attrs,
                baseMethod.ReturnType,
                CompilerHelpers.GetTypes(baseMethod.GetParameters()));
            CodeGen ret = new CodeGen(this, mb, mb.GetILGenerator(), baseMethod.GetParameters());
            ret.methodToOverride = baseMethod;
            return ret;
        }

        public CodeGen DefineMethodOverride(MethodInfo baseMethod) {
            MethodAttributes attrs = baseMethod.Attributes &
                ~(MethodAttributes.Abstract | MethodAttributes.ReservedMask);
            MethodBuilder mb = myType.DefineMethod(baseMethod.Name, attrs, baseMethod.ReturnType,
                CompilerHelpers.GetTypes(baseMethod.GetParameters()));
            CodeGen ret = new CodeGen(this, mb, mb.GetILGenerator(), baseMethod.GetParameters());
            ret.methodToOverride = baseMethod;
            return ret;
        }

        public CodeGen DefineMethod(string name, Type retType, Type[] paramTypes, string[] paramNames) {
            MethodAttributes attrs = MethodAttributes.Public;
            if (Options.StaticModules) attrs |= MethodAttributes.Static;
            return DefineMethod(attrs, name, retType, paramTypes, paramNames);
        }

        public CodeGen DefineMethod(string name, Type retType, Type[] paramTypes, string[] paramNames, object[] defaultVals) {
            MethodAttributes attrs = MethodAttributes.Public;
            if (Options.StaticModules) attrs |= MethodAttributes.Static;
            return DefineMethod(attrs, name, retType, paramTypes, paramNames, defaultVals);
        }

        public CodeGen DefineMethod(MethodAttributes attrs, string name, Type retType, Type[] paramTypes, string[] paramNames, object[] defaultVals) {
            return DefineMethod(attrs, name, retType, paramTypes, paramNames, defaultVals, null);
        }

        public CodeGen DefineMethod(string name, Type retType, Type[] paramTypes, string[] paramNames, object[] defaultVals, CustomAttributeBuilder[] cabs) {
            MethodAttributes attrs = MethodAttributes.Public;
            if (Options.StaticModules) attrs |= MethodAttributes.Static;
            return DefineMethod(attrs, name, retType, paramTypes, paramNames, defaultVals, cabs);
        }

        public CodeGen DefineMethod(MethodAttributes attrs, string name, Type retType, Type[] paramTypes, string[] paramNames, object [] defaultVals, CustomAttributeBuilder[]cabs) {
            MethodBuilder mb = myType.DefineMethod(name, attrs, retType, paramTypes);
            CodeGen res = new CodeGen(this, mb, mb.GetILGenerator(), paramTypes);

            for (int i = 0; i < paramTypes.Length; i++) {
                // parameters are index from 1.
                ParameterBuilder pb = res.DefineParameter(i + 1, ParameterAttributes.None, paramNames[i]);
                if (defaultVals != null && i < defaultVals.Length && defaultVals[i] != DBNull.Value) {
                    pb.SetConstant(defaultVals[i]);
                }

                if (cabs != null && i < cabs.Length && cabs[i] != null) {
                    pb.SetCustomAttribute(cabs[i]);
                }                
            }            
            return res;
        }

        public CodeGen DefineMethod(MethodAttributes attrs, string name, Type retType, Type[] paramTypes, string[] paramNames) {
            return DefineMethod(attrs, name, retType, paramTypes, paramNames, null);
        }

        public CodeGen DefineUserHiddenMethod(MethodAttributes attrs, string name, Type retType, Type[] paramTypes) {
            MethodBuilder mb = myType.DefineMethod(name, attrs, retType, paramTypes);
            CodeGen res = new CodeGen(this, mb, mb.GetILGenerator(), paramTypes);
            return res;
        }

        public CodeGen DefineConstructor(Type[] paramTypes) {
            ConstructorBuilder cb = myType.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, paramTypes);
            return new CodeGen(this, cb, cb.GetILGenerator(), paramTypes);
        }

        public CodeGen DefineStaticConstructor() {
            ConstructorBuilder cb = myType.DefineTypeInitializer();
            return new CodeGen(this, cb, cb.GetILGenerator(), new ParameterInfo[0]);
        }

        private static Type[] ExtractTypes(ParameterInfo[] ps) {
            int len = ps.Length;
            Type[] ret = new Type[len];
            for (int i = 0; i < len; i++) {
                ret[i] = ps[i].ParameterType;
            }
            return ret;
        }

        /*
        public ConstructorInfo DefineChainedConstructor(ConstructorInfo parentConstructor) {
            ParameterInfo[] pis = parentConstructor.GetParameters();
            Type[] types = ExtractTypes(pis);
            ConstructorBuilder cb = myType.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, types);
            for (int i = 0; i < pis.Length; i++) {
                ParameterBuilder pb = cb.DefineParameter(i + 1, pis[i].Attributes, pis[i].Name);
                if (pis[i].IsDefined(typeof(ParamArrayAttribute), false)) {
                    pb.SetCustomAttribute(new CustomAttributeBuilder(
                        typeof(ParamArrayAttribute).GetConstructor(Type.EmptyTypes), new object[0]));
                }
            }

            ILGenerator ilg = cb.GetILGenerator();
            ilg.Emit(OpCodes.Ldarg_0);

            for (int i = 0; i < types.Length; i++) {
                ilg.Emit(OpCodes.Ldarg, i + 1);
            }
            ilg.Emit(OpCodes.Call, parentConstructor);
            ilg.Emit(OpCodes.Ret);
            return cb;
        }*/

        public void SetCustomAttribute(Type t, object [] values) {
            
            Type[] types = new Type[values.Length];
            for(int i = 0; i<types.Length;i++){
                if(values[i] != null){
                types[i] = values[i].GetType();
                }else{
                   types[i] = typeof(object);
                }
            }
            CustomAttributeBuilder cab = new CustomAttributeBuilder(t.GetConstructor(types), values);

            myType.SetCustomAttribute(cab);
        }

        /// <summary>
        /// Constants
        /// </summary>

        public Slot GetOrMakeConstant(object value) {
            return GetOrMakeConstant(value, typeof(object));
        }

        public Slot GetOrMakeConstant(object value, Type type) {
            Slot ret;
            if (constants.TryGetValue(value, out ret)) return ret;

            String name = "c$" + constants.Count;

            FieldBuilder fb = myType.DefineField(name, type, FieldAttributes.Static);//??? | FieldAttributes.Literal
            ret = new StaticFieldSlot(fb);

            GetOrMakeInitializer().EmitConstantBoxed(value);
            initGen.EmitFieldSet(fb);

            constants[value] = ret;
            return ret;
        }

        public void EmitIndirectedSymbol(CodeGen cg, SymbolId id) {
            Slot value;
            if (!indirectSymbolIds.TryGetValue(id, out value)) {
                // create field, emit fix-up...

                value = AddStaticField(typeof(int), "symbol_" + SymbolTable.IdToString(id));
                CodeGen init = GetOrMakeInitializer();
                Slot localTmp = init.GetLocalTmp(typeof(SymbolId));
                init.EmitString((string)SymbolTable.IdToString(id));
                init.EmitCall(typeof(SymbolTable), "StringToId");
                localTmp.EmitSet(init);
                localTmp.EmitGetAddr(init);
                init.EmitFieldGet(typeof(SymbolId), "Id");
                value.EmitSet(init);

                cg.FreeLocalTmp(localTmp);
                indirectSymbolIds[id] = value;
            }

            value.EmitGet(cg);
        }
    }
}
