/* **********************************************************************************
 *
 * 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.IO;
using System.Collections.Generic;
using System.Reflection;
using System.Diagnostics;
using System.Reflection.Emit;
using System.Threading;
using IronPython.Runtime;

namespace IronPython.Compiler {

    // IronPython has two units of compilation:
    // 1. Snippets - These are small pieces of code compiled for these cases:
    //    a. Interactive console expressions and statements
    //    b. exec statement, eval() built-in function
    //    c. Types generated by NewTypeMaker for instances of Python types
    //    d. others ...
    //    All snippets are created in the snippetAssembly. These are created using GenerateSnippet.
    // 2. Modules - Modules are compiled in one shot when they are imported.
    //    Every Python module is generated into its own assembly. These are created using GenerateModule.
    //
    // OutputGenerator manages both units of compilation.

    public static class OutputGenerator {
        private static int count = 0;
        private static AssemblyGen CreateNewSnippetAssembly() {
            string name = "snippets";
            int thisCnt = Interlocked.Increment(ref count);
            if (thisCnt > 0) name += thisCnt;
            return new AssemblyGen(name, Environment.CurrentDirectory, name + ".dll", Options.ILDebug);
        }

        // All snippets are created in this shared assembly
        private static AssemblyGen snippetAssembly = CreateNewSnippetAssembly();
        public static AssemblyGen Snippets {
            get {
                return snippetAssembly;
            }
        }

        private static int index = 0;
        public static void DumpSnippets() {
            if (Options.SaveAndReloadBinaries) {
                snippetAssembly.Dump();
            }
            snippetAssembly = CreateNewSnippetAssembly();
        }

        private static TypeGen MakeModuleSlotHolder() {
            TypeGen tg = snippetAssembly.DefinePublicType("moduleHolder_" + Interlocked.Increment(ref index), typeof(object));
            tg.AddModuleField(typeof(PythonModule));
            tg.myType.DefineDefaultConstructor(MethodAttributes.Public);
            return tg;
        }


        public static FrameCode GenerateSnippet(CompilerContext context, Stmt body, string name, bool printExprStmts) {
            GlobalSuite gs = IronPython.Compiler.Binder.Bind(body, context, name);

            if (name.Length == 0) name = "<empty>"; // The empty string isn't a legal method name
            CodeGen cg;
            List<object> staticData = null;
            TypeGen tg = null;
            cg = snippetAssembly.DefineDynamicMethod(name, typeof(object), new Type[] { typeof(Frame) });
            staticData = new List<object>();
            cg.staticData = staticData;
            cg.doNotCacheConstants = true;
            cg.ModuleSlot = cg.GetLocalTmp(typeof(PythonModule));
            cg.ContextSlot = cg.GetArgumentSlot(0);
            cg.Names = CodeGen.CreateFrameNamespace(cg.ContextSlot);
            cg.Context = context;
            cg.printExprStmts = printExprStmts;
            if (printExprStmts) {
                cg.Names.EnsureLocalSlot(Name.Make("_"));
            }

            cg.ContextSlot.EmitGet(cg);
            cg.EmitFieldGet(typeof(Frame), "__module__");
            cg.ModuleSlot.EmitSet(cg);

            if (context.TrueDivision) {
                cg.ContextSlot.EmitGet(cg);
                cg.EmitInt(1);
                cg.EmitCall(typeof(ICallerContext), "set_TrueDivision");
            }

            gs.Emit(cg);

            if (!(body is ReturnStmt)) {
                cg.EmitPosition(Location.None, Location.None);
                cg.EmitReturn(null);
            }

            if (tg != null) tg.FinishType();

            FrameCode frameCode = new FrameCode(name,
                (FrameCodeDelegate)cg.CreateDelegate(typeof(FrameCodeDelegate)),
                staticData);
            return frameCode;
        }

        public static PythonModule GenerateModule(CompilerContext context, Stmt body, string moduleName, string sourceFileName) {
            if (Options.GenerateModulesAsSnippets) {
                return GenerateModuleAsSnippets(context, body, moduleName);
            }

            GlobalSuite gs = IronPython.Compiler.Binder.Bind(body, context, sourceFileName);
            string suffix = "";
            int counter = 0;

            for (; ; ) {
                try {
                    return DoGenerateModule(context, gs, moduleName, sourceFileName, suffix);
                } catch (System.IO.IOException) {
                    suffix = "_" + (++counter).ToString();
                }
            }
        }

        public static PythonModule GenerateModule(CompilerContext context, Stmt body, string moduleName, string sourceFileName, string outSuffix) {
            if (Options.GenerateModulesAsSnippets) {
                return GenerateModuleAsSnippets(context, body, moduleName);
            }

            GlobalSuite gs = IronPython.Compiler.Binder.Bind(body, context, sourceFileName);
            return DoGenerateModule(context, gs, moduleName, sourceFileName, outSuffix);
        }

        internal class SnippetModuleRunner {
            PythonModule module;
            Frame frame;
            Dict dict = new Dict();
            List<FrameCode> snippets = new List<FrameCode>();

            public PythonModule Module {
                get {
                    return module;
                }
            }

            public SnippetModuleRunner(string name) {
                module = new PythonModule(name, dict, this.Initialize);
                frame = new Frame(module);
            }

            public void AddSnippet(FrameCode fc) {
                snippets.Add(fc);
            }

            public void Initialize() {
                foreach (FrameCode fc in snippets) {
                    fc.Run(frame);
                }
            }
        }

        private static PythonModule GenerateModuleAsSnippets(CompilerContext context, Stmt body, string moduleName) {
            SuiteStmt suite = body as SuiteStmt;
            SnippetModuleRunner smr = new SnippetModuleRunner(moduleName);

            Debug.Assert(suite != null, "invalid statement");

            //  Convert document string into assignment
            if (suite.stmts.Length > 0) {
                ExprStmt es = suite.stmts[0] as ExprStmt;
                if (es != null) {
                    ConstantExpr ce = es.expr as ConstantExpr;
                    if (ce != null && ce.value is string) {
                        suite.stmts[0] = new AssignStmt(new Expr[] { new NameExpr(Name.Make("__doc__")) }, ce);
                    }
                }
            }

            foreach (Stmt stmt in suite.stmts) {
                // GenerateSnippet will do the binding
                smr.AddSnippet(GenerateSnippet(context, stmt, moduleName, true));
            }
            return smr.Module;
        }

        private static PythonModule DoGenerateModule(CompilerContext context, GlobalSuite gs, string moduleName, string sourceFileName, string outSuffix) {
            string fullPath;
            string outDir;
            string fileName;

            if (sourceFileName == "<stdin>") {
                fullPath = Environment.CurrentDirectory;
                outDir = Environment.CurrentDirectory;
                fileName = "__stdin__";
            } else {
                fullPath = Path.GetFullPath(sourceFileName);
                outDir = Path.GetDirectoryName(fullPath);
                fileName = Path.GetFileNameWithoutExtension(sourceFileName);
            }

            AssemblyGen ag = new AssemblyGen(moduleName + outSuffix, outDir, fileName + outSuffix + ".exe", true);
            ag.SetPythonSourceFile(fullPath);


            TypeGen tg = GenerateModuleType(moduleName, ag);
            CodeGen cg = GenerateModuleInitialize(context, gs, tg);

            CodeGen main = GenerateModuleEntryPoint(tg, cg, moduleName, null);
            ag.SetEntryPoint(main.MethodInfo, PEFileKinds.ConsoleApplication);

            Type ret = tg.FinishType();
            Assembly assm = ag.DumpAndLoad();
            ret = assm.GetType(moduleName);

            CustomFieldIdDict dict = (CustomFieldIdDict)ret.GetConstructor(Type.EmptyTypes).Invoke(new object[0]);
            InitializeModule init = (InitializeModule)Delegate.CreateDelegate(
                typeof(InitializeModule), dict, "Initialize");

            PythonModule pmod = new PythonModule(moduleName, dict, init);
            pmod.InitializeBuiltins();
            return pmod;
        }

        internal delegate void CustomModuleInit(CodeGen cg);

        internal static CodeGen GenerateModuleInitialize(CompilerContext context, GlobalSuite gs, TypeGen tg) {
            return GenerateModuleInitialize(context, gs, tg, null);
        }

        internal static CodeGen GenerateModuleInitialize(CompilerContext context, GlobalSuite gs, TypeGen tg, CustomModuleInit customInit) {
            CodeGen ncg = tg.DefineUserHiddenMethod(MethodAttributes.Public, "Initialize", typeof(void), Type.EmptyTypes);
            ncg.Context = context;

            if (Options.StaticModules) {
                ncg.Names = CodeGen.CreateStaticFieldNamespace(tg);
            } else {
                throw new NotImplementedException(); //.names = new FieldNamespace(null, tg, new ModuleSlot(tg.myType));
            }

            if (context.TrueDivision) {
                ncg.ModuleSlot.EmitGet(ncg);
                ncg.EmitInt(1);
                ncg.EmitCall(typeof(ICallerContext), "set_TrueDivision");
            }

            // Add __doc__ and __name__
            ncg.Names.CreateGlobalSlot(Name.Make("__doc__"));
            ncg.Names.CreateGlobalSlot(Name.Make("__name__"));

            string doc = gs.GetDocString();
            ncg.EmitStringOrNull(doc);
            ncg.EmitSet(Name.Make("__doc__"));

            if (customInit != null) customInit(ncg);

            gs.Emit(ncg);
            ncg.EmitPosition(Location.None, Location.None);
            ncg.EmitReturn();

            FinishCustomDict(tg, ncg.Names);

            return ncg;
        }

        /// <summary>
        /// Generates a static entry point for stand-alone EXEs.  We just new up our module dstructure
        /// and then call into Ops to get running.
        /// </summary>
        internal static CodeGen GenerateModuleEntryPoint(TypeGen tg, CodeGen init, string moduleName, IList<string> referencedAssemblies) {
            CodeGen main = tg.DefineMethod(MethodAttributes.Static | MethodAttributes.Public, "Main", typeof(int), Type.EmptyTypes, new string[] { });
            main.SetCustomAttribute(new CustomAttributeBuilder(typeof(STAThreadAttribute).GetConstructor(Type.EmptyTypes), new object[0]));

            if (referencedAssemblies != null) {
                foreach (string referencedAssembly in referencedAssemblies) {
                    AddReference(main, referencedAssembly);
                }
            }

            //  Create instance of the module
            Slot instance = main.GetLocalTmp(tg.myType);
            main.EmitNew(tg.DefaultConstructor);
            instance.EmitSet(main);

            // Emit instance for the ExecuteCompiled call
            instance.EmitGet(main);

            // Emit the delegate to the init method
            main.EmitDelegate(init, typeof(InitializeModule), instance);
            main.EmitString(moduleName);

            // Call ExecuteCompiled
            main.EmitCall(typeof(IronPython.Hosting.PythonEngine), "ExecuteCompiled");
            main.EmitReturn();
            return main;
        }

        private static void AddReference(CodeGen main, string referencedAssembly) {
            if (referencedAssembly.IndexOf(Path.DirectorySeparatorChar) != -1) {
                main.Emit(OpCodes.Ldstr, Path.GetFileNameWithoutExtension(referencedAssembly));
            } else {
                main.Emit(OpCodes.Ldstr, referencedAssembly);
            }
            main.EmitCall(typeof(IronPython.Modules.ClrModule).GetMethod("AddAssembly"));
        }

        internal static TypeGen GenerateModuleType(string moduleName, AssemblyGen ag) {
            TypeGen tg = ag.DefinePublicType(moduleName, typeof(CustomFieldIdDict));
            tg.AddModuleField(typeof(PythonModule));
            tg.DefaultConstructor = tg.myType.DefineDefaultConstructor(MethodAttributes.Public);

            return tg;
        }

        private static void FinishCustomDict(TypeGen tg, Namespace ns) {
            DictBuilder db = new DictBuilder(tg, ns);
            db.AddCustomDictMethods();
        }
    }

    class DictBuilder {
        private TypeGen tg;
        private Namespace names;

        public DictBuilder(TypeGen tg, Namespace names) {
            this.tg = tg;
            this.names = names;
        }

        public void AddCustomDictMethods() {
            MakeGetMethod();
            MakeSetMethod();
            MakeRawKeysMethod();
        }

        //
        // This generates a method like the following:
        //
        //  TryGetExtraValue(int name, object out value) {
        //      if (name1 == name) {
        //          value = type.name1Slot;
        //          return true;
        //      }
        //      if (name2 == name) {
        //          value = type.name2Slot;
        //          return true;
        //      }
        //      ...
        //      return false
        //  }

        private void MakeGetMethod() {
            CodeGen cg = tg.DefineMethodOverride(typeof(CustomFieldIdDict).GetMethod("TryGetExtraValue"));
            foreach (KeyValuePair<Name, Slot> entry in names.Slots) {
                cg.EmitSymbolIdInt(entry.Key.GetString());
                cg.EmitArgAddr(0);
                cg.EmitFieldGet(typeof(SymbolId), "Id");

                Label next = cg.DefineLabel();
                cg.Emit(OpCodes.Bne_Un, next);

                cg.EmitArgGet(1);

                // ugly special casing builtins this way, but they're special...
                // Builtins can be in one of two states:
                //      Not defined, but available internally to the module
                //      Defined, and exposed externally from the module.
                // In both cases we have static fields for these, and we're in the custom dict.
                // 
                // Therefore we keep track of which state they're in w/ a wrapper object,
                // and here we need to emit the real value, not the value that's availble
                // internally to the module.

                StaticFieldBuiltinSlot builtin = entry.Value as StaticFieldBuiltinSlot;
                if (builtin == null) {
                    entry.Value.EmitGet(cg);
                } else {
                    builtin.EmitGetRaw(cg);
                }

                cg.Emit(OpCodes.Stind_Ref);
                cg.EmitInt(1);
                cg.EmitReturn();
                cg.MarkLabel(next);
            }
            cg.EmitInt(0);
            cg.EmitReturn();
            cg.Finish();
        }

        // This generates a method like the following:
        //
        //  TrySetExtraValue(object name, object value) {
        //      if (name1 == name) {
        //          type.name1Slot = value;
        //          return 1;
        //      }
        //      if (name2 == name) {
        //          type.name2Slot = value;
        //          return 1;
        //      }
        //      ...
        //      return 0
        //  }

        private void MakeSetMethod() {
            CodeGen cg = tg.DefineMethodOverride(typeof(CustomFieldIdDict).GetMethod("TrySetExtraValue"));
            Slot valueSlot = cg.GetArgumentSlot(1);
            foreach (KeyValuePair<Name, Slot> entry in names.Slots) {
                cg.EmitSymbolIdInt(entry.Key.GetString());
                cg.EmitArgAddr(0);
                cg.EmitFieldGet(typeof(SymbolId), "Id");

                Label next = cg.DefineLabel();
                cg.Emit(OpCodes.Bne_Un, next);

                entry.Value.EmitSet(cg, valueSlot);
                cg.EmitInt(1);
                cg.EmitReturn();
                cg.MarkLabel(next);
            }
            cg.EmitInt(0);
            cg.EmitReturn();
            cg.Finish();
        }

        private void MakeRawKeysMethod() {
            Slot rawKeysCache = tg.AddStaticField(typeof(SymbolId[]), "ExtraKeysCache");
            CodeGen init = tg.GetOrMakeInitializer();
            SymbolId[] nameArray = new SymbolId[names.Slots.Count];

            init.EmitSymbolIdArray(new List<Name>(names.Slots.Keys));

            rawKeysCache.EmitSet(init);

            CodeGen cg = tg.DefineMethodOverride(typeof(CustomFieldIdDict).GetMethod("GetExtraKeys"));
            rawKeysCache.EmitGet(cg);
            cg.EmitReturn();
            cg.Finish();
        }
    }
}
