/* **********************************************************************************
 *
 * 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.Text;
using System.IO;

using IronPython.Compiler;
using IronPython.Hosting;
using IronPython.Runtime;
using IronPython.Runtime.Exceptions;
using IronPython.Runtime.Operations;

namespace IronPythonTest {
    class Common {
        public static string RuntimeDirectory;
        public static string ScriptTestDirectory;
        public static string InputTestDirectory;

        static Common() {
            RuntimeDirectory = Path.GetDirectoryName(typeof(PythonEngine).Assembly.Location);
            ScriptTestDirectory = Path.Combine(RuntimeDirectory, @"Src\Tests");
            InputTestDirectory = Path.Combine(ScriptTestDirectory, "Inputs");
        }
    }

    public delegate int IntIntDelegate(int arg);
    public delegate string RefStrDelegate(ref string arg);
    public delegate int RefIntDelegate(ref int arg);
    public delegate T GenericDelegate<T, U, V>(U arg1, V arg2);

    public class ClsPart {
        public int Field;
        int m_property;
        public int Property { get { return m_property; } set { m_property = value; } }
        public event IntIntDelegate Event;
        public int Method(int arg) {
            if (Event != null)
                return Event(arg);
            else
                return -1;
        }

        // Private members
#pragma warning disable 169
        // This field is accessed from the test
        private int privateField;
#pragma warning restore 169
        private int privateProperty { get { return m_property; } set { m_property = value; } }
        private event IntIntDelegate privateEvent;
        private int privateMethod(int arg) {
            if (privateEvent != null)
                return privateEvent(arg);
            else
                return -1;
        }
        private int privateStaticMethod() {
            return 100;
        }
    }

    internal class InternalClsPart {
#pragma warning disable 649
        // This field is accessed from the test
        internal int Field;
#pragma warning restore 649
        int m_property;
        internal int Property { get { return m_property; } set { m_property = value; } }
        internal event IntIntDelegate Event;
        internal int Method(int arg) {
            if (Event != null)
                return Event(arg);
            else
                return -1;
        }
    }

    public class EngineTest : MarshalByRefObject {

        PythonEngine standardEngine = new PythonEngine();

        public EngineTest() {
            // Load a script with all the utility functions that are required
            // standardEngine.ExecuteFile(InputTestDirectory + "\\EngineTests.py");
        }

        // Used to test exception thrown in another domain can be shown correctly.
        public void Run(string script) {
            standardEngine.Execute(script);
        }

        static readonly string clspartName = "clsPart";

        // Execute
        public void ScenarioExecute() {
            PythonEngine pe = new PythonEngine();

            ClsPart clsPart = new ClsPart();
            pe.Globals[clspartName] = clsPart;

            // field: assign and get back
            pe.Execute("clsPart.Field = 100");
            pe.Execute("if 100 != clsPart.Field: raise AssertionError('test failed')");
            AreEqual(100, clsPart.Field);

            // property: assign and get back
            pe.Execute("clsPart.Property = clsPart.Field");
            pe.Execute("if 100 != clsPart.Property: raise AssertionError('test failed')");
            AreEqual(100, clsPart.Property);

            // method: Event not set yet
            pe.Execute("a = clsPart.Method(2)");
            pe.Execute("if -1 != a: raise AssertionError('test failed')");

            // method: add python func as event handler
            pe.Execute("def f(x) : return x * x");
            pe.Execute("clsPart.Event += f");
            pe.Execute("a = clsPart.Method(2)");
            pe.Execute("if 4 != a: raise AssertionError('test failed')");

            // ===============================================

            // reset the same variable with instance of the same type
            pe.Globals[clspartName] = new ClsPart();
            pe.Execute("if 0 != clsPart.Field: raise AssertionError('test failed')");

            // add cls method as event handler
            pe.Globals["clsMethod"] = new IntIntDelegate(Negate);
            pe.Execute("clsPart.Event += clsMethod");
            pe.Execute("a = clsPart.Method(2)");
            pe.Execute("if -2 != a: raise AssertionError('test failed')");

            // ===============================================

            // reset the same variable with integer
            pe.Globals[clspartName] = 1;
            pe.Execute("if 1 != clsPart: raise AssertionError('test failed')");
            AreEqual((int)pe.Globals[clspartName], 1);
        }

        // No intereference between two engines
        public void ScenarioNoInterferenceBetweenTwoEngines() {
            PythonEngine pe1 = new PythonEngine();
            PythonEngine pe2 = new PythonEngine();

            pe1.Execute("a = 1");

            try {
                pe2.Execute("print a");
                throw new Exception("Scenario3");
            } catch (IronPython.Runtime.Exceptions.PythonNameErrorException) { }
        }

        public void ScenarioEvaluateInAnonymousEngineModule() {
            EngineModule anonymousScope = standardEngine.CreateModule();
            EngineModule anotherAnonymousScope = standardEngine.CreateModule();
            standardEngine.Execute("x = 0");
            standardEngine.Execute("x = 1", anonymousScope);
            anotherAnonymousScope.Globals["x"] = 2;

            // Ensure that the default EngineModule is not affected
            AreEqual(0, standardEngine.EvaluateAs<int>("x"));
            AreEqual(0, (int)standardEngine.DefaultModule.Globals["x"]);
            // Ensure that the anonymous scope has been updated as expected
            AreEqual(1, standardEngine.EvaluateAs<int>("x", anonymousScope));
            AreEqual(1, (int)anonymousScope.Globals["x"]);
            // Ensure that other anonymous scopes are not affected
            AreEqual(2, standardEngine.EvaluateAs<int>("x", anotherAnonymousScope));
            AreEqual(2, (int)anotherAnonymousScope.Globals["x"]);
        }

        public void ScenarioEvaluateInPublishedEngineModule() {
            EngineModule publishedScope = standardEngine.CreateModule("published_scope_test", true);
            standardEngine.Execute("x = 0");
            standardEngine.Execute("x = 1", publishedScope);
            // Ensure that the default EngineModule is not affected
            AreEqual(0, standardEngine.EvaluateAs<int>("x"));
            // Ensure that the published scope has been updated as expected
            AreEqual(1, standardEngine.EvaluateAs<int>("x", publishedScope));

            // Ensure that the published scope is accessible from other scopes using sys.modules
            standardEngine.Import("sys");
            standardEngine.Execute("x_from_published_scope_test = sys.modules['published_scope_test'].x");
            AreEqual(1, standardEngine.EvaluateAs<int>("x_from_published_scope_test"));
        }


        class CustomDictionary : IDictionary<string, object> {
            // Make "customSymbol" always be accessible. This could have been accomplished just by
            // doing SetGlobal. However, this mechanism could be used for other extensibility
            // purposes like getting a callback whenever the symbol is read
            internal static readonly string customSymbol = "customSymbol";
            internal const int customSymbolValue = 100;

            Dictionary<string, object> dict = new Dictionary<string, object>();

            #region IDictionary<string,object> Members

            public void Add(string key, object value) {
                if (key.Equals(customSymbol))
                    throw new PythonNameErrorException("Cannot assign to customSymbol");
                dict.Add(key, value);
            }

            public bool ContainsKey(string key) {
                if (key.Equals(customSymbol))
                    return true;
                return dict.ContainsKey(key);
            }

            public ICollection<string> Keys {
                get { throw new NotImplementedException("The method or operation is not implemented."); }
            }

            public bool Remove(string key) {
                if (key.Equals(customSymbol))
                    throw new PythonNameErrorException("Cannot delete customSymbol");
                return dict.Remove(key);
            }

            public bool TryGetValue(string key, out object value) {
                if (key.Equals(customSymbol)) {
                    value = customSymbolValue;
                    return true;
                }

                return dict.TryGetValue(key, out value);
            }

            public ICollection<object> Values {
                get { throw new NotImplementedException("The method or operation is not implemented."); }
            }

            public object this[string key] {
                get {
                    if (key.Equals(customSymbol))
                        return customSymbolValue;
                    return dict[key];
                }
                set {
                    Add(key, value);
                }
            }

            #endregion

            #region ICollection<KeyValuePair<string,object>> Members

            public void Add(KeyValuePair<string, object> item) {
                throw new NotImplementedException("The method or operation is not implemented.");
            }

            public void Clear() {
                throw new NotImplementedException("The method or operation is not implemented.");
            }

            public bool Contains(KeyValuePair<string, object> item) {
                throw new NotImplementedException("The method or operation is not implemented.");
            }

            public void CopyTo(KeyValuePair<string, object>[] array, int arrayIndex) {
                throw new NotImplementedException("The method or operation is not implemented.");
            }

            public int Count {
                get { throw new NotImplementedException("The method or operation is not implemented."); }
            }

            public bool IsReadOnly {
                get { throw new NotImplementedException("The method or operation is not implemented."); }
            }

            public bool Remove(KeyValuePair<string, object> item) {
                throw new NotImplementedException("The method or operation is not implemented.");
            }

            #endregion

            #region IEnumerable<KeyValuePair<string,object>> Members

            public IEnumerator<KeyValuePair<string, object>> GetEnumerator() {
                throw new NotImplementedException("The method or operation is not implemented.");
            }

            #endregion

            #region IEnumerable Members

            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
                throw new NotImplementedException("The method or operation is not implemented.");
            }

            #endregion
        }

        public void ScenarioCustomDictionary() {
            CustomDictionary customGlobals = new CustomDictionary();
            EngineModule customModule = standardEngine.CreateModule("customModule", customGlobals, true);

            // Evaluate
            AreEqual(standardEngine.EvaluateAs<int>("customSymbol + 1", customModule), CustomDictionary.customSymbolValue + 1);

            // Execute
            standardEngine.Execute("customSymbolPlusOne = customSymbol + 1", customModule);
            AreEqual(standardEngine.EvaluateAs<int>("customSymbolPlusOne", customModule), CustomDictionary.customSymbolValue + 1);
            AreEqual((int)customModule.Globals["customSymbolPlusOne"], CustomDictionary.customSymbolValue + 1);

            // Compile
            CompiledCode compiledCode = standardEngine.Compile("customSymbolPlusTwo = customSymbol + 2");
            compiledCode.Execute(customModule);
            AreEqual(standardEngine.EvaluateAs<int>("customSymbolPlusTwo", customModule), CustomDictionary.customSymbolValue + 2);
            AreEqual((int)customModule.Globals["customSymbolPlusTwo"], CustomDictionary.customSymbolValue + 2);

            // check overriding of Add
            try {
                standardEngine.Execute("customSymbol = 1", customModule);
                throw new Exception("We should not reach here");
            } catch (PythonNameErrorException) { }

            try {
                standardEngine.Execute(@"global customSymbol
customSymbol = 1", customModule);
                throw new Exception("We should not reach here");
            } catch (PythonNameErrorException) { }

            // check overriding of Remove
            try {
                standardEngine.Execute("del customSymbol", customModule);
                throw new Exception("We should not reach here");
            } catch (PythonNameErrorException) { }

            try {
                standardEngine.Execute(@"global customSymbol
del customSymbol", customModule);
                throw new Exception("We should not reach here");
            } catch (PythonNameErrorException) { }

            // vars()
            IDictionary vars = standardEngine.EvaluateAs<IDictionary>("vars()", customModule);
            AreEqual(true, vars.Contains("customSymbol"));

            // Miscellaneous APIs
            customModule.Import("sys");
            AreEqual(standardEngine.EvaluateAs<string>("sys.ps1", customModule), ">>> ");
            IntIntDelegate d = standardEngine.CreateLambda<IntIntDelegate>("customSymbol + arg", customModule);
            AreEqual(d(1), CustomDictionary.customSymbolValue + 1);
        }

        public void ScenarioModuleWithLocals() {
            // Ensure that the namespace is not already polluted
            if (standardEngine.Globals.ContainsKey("global_variable"))
                standardEngine.Globals.Remove("global_variable");
            if (standardEngine.Globals.ContainsKey("local_variable"))
                standardEngine.Globals.Remove("local_variable");

            // Simple use of locals
            Dictionary<string, object> locals = new Dictionary<string, object>();
            locals["local_variable"] = 100;
            AreEqual(100, standardEngine.EvaluateAs<int>("local_variable", standardEngine.DefaultModule, locals));

            // Ensure simple writes go to locals, not globals
            standardEngine.Execute("local_variable = 200", standardEngine.DefaultModule, locals);
            AreEqual(200, standardEngine.EvaluateAs<int>("local_variable", standardEngine.DefaultModule, locals));
            AreEqual(200, (int)locals["local_variable"]);
            if (standardEngine.Globals.ContainsKey("local_variable"))
                throw new Exception("local_variable is set in the Globals dictionary");
            try {
                standardEngine.Evaluate("local_variable", standardEngine.DefaultModule, null);
            } catch (PythonNameErrorException) { }

            // Ensure writes to globals go to globals, not locals
            standardEngine.Execute(@"global global_variable
global_variable = 300", standardEngine.DefaultModule, locals);
            AreEqual(300, standardEngine.EvaluateAs<int>("global_variable", standardEngine.DefaultModule, locals));
            AreEqual(300, (int)standardEngine.Globals["global_variable"]);
            if (locals.ContainsKey("global_variable"))
                throw new Exception("global_variable is set in the locals dictionary");

            // vars()
            IDictionary vars = standardEngine.EvaluateAs<IDictionary>("vars()", standardEngine.DefaultModule, locals);
            AreEqual(true, vars.Contains("local_variable"));
        }

        static long GetTotalMemory() {
            // Critical objects can take upto 3 GCs to be collected
            for (int i = 0; i < 3; i++) {
                GC.Collect();
                GC.WaitForPendingFinalizers();
            }
            return GC.GetTotalMemory(true);
        }

        const string scenarioGCModuleName = "scenario_gc";

        void CreateModules(PythonEngine engine) {
            for (int scopeCount = 0; scopeCount < 100; scopeCount++) {
                EngineModule scope = engine.CreateModule(scenarioGCModuleName, true);
                scope.Globals["x"] = "Hello";
                engine.ExecuteFile(Common.InputTestDirectory + "\\simpleCommand.py", scope);
                AreEqual(engine.EvaluateAs<int>("x", scope), 1);
            }
        }

        public void ScenarioGC() {
            long initialMemory = GetTotalMemory();

            // Create multiple engines and scopes
            for (int engineCount = 0; engineCount < 100; engineCount++) {
                PythonEngine engine = new PythonEngine();
                CreateModules(engine);
            }

            // Create multiple scopes in an engine that is not collected
            CreateModules(standardEngine);
            standardEngine.Sys.modules.Remove(scenarioGCModuleName);

            long finalMemory = GetTotalMemory();
            long memoryUsed = finalMemory - initialMemory;
            const long memoryThreshold = 100000;
            if (memoryUsed > memoryThreshold)
                throw new Exception(String.Format("ScenarioGC used {0} bytes of memory. The threshold is {1} bytes", memoryUsed, memoryThreshold));
        }

        // Evaluate
        public void ScenarioEvaluate() {
            PythonEngine pe = new PythonEngine();

            AreEqual(10, (int)pe.Evaluate("4+6"));
            AreEqual(10, pe.EvaluateAs<int>("4+6"));

            AreEqual("abab", (string)pe.Evaluate("'ab' * 2"));
            AreEqual("abab", pe.EvaluateAs<string>("'ab' * 2"));

            ClsPart clsPart = new ClsPart();
            pe.Globals[clspartName] = clsPart;
            AreEqual(clsPart, pe.Evaluate("clsPart") as ClsPart);
            AreEqual(clsPart, pe.EvaluateAs<ClsPart>("clsPart"));

            pe.Execute("clsPart.Field = 100");
            AreEqual(100, (int)pe.Evaluate("clsPart.Field"));
            AreEqual(100, pe.EvaluateAs<int>("clsPart.Field"));

            // Ensure that we can get back a delegate to a Python method
            pe.Execute("def IntIntMethod(a): return a * 100");
            IntIntDelegate d = pe.EvaluateAs<IntIntDelegate>("IntIntMethod");
            AreEqual(d(2), 2 * 100);
        }

        // ExecuteFile
        public void ScenarioExecuteFile() {
            PythonEngine pe = new PythonEngine();

            string tempFile1 = Path.GetTempFileName();
            string tempFile2 = Path.GetTempFileName();

            try {
                using (StreamWriter sw = new StreamWriter(tempFile1)) {
                    sw.WriteLine("var1 = (10, 'z')");
                    sw.WriteLine("");
                    sw.WriteLine("clsPart.Field = 100");
                    sw.WriteLine("clsPart.Property = clsPart.Field * 5");
                    sw.WriteLine("clsPart.Event += (lambda x: x*x)");
                }

                ClsPart clsPart = new ClsPart();
                pe.Globals[clspartName] = clsPart;
                pe.ExecuteFile(tempFile1);

                using (StreamWriter sw = new StreamWriter(tempFile2)) {
                    sw.WriteLine("if var1[0] != 10: raise AssertionError('test failed')");
                    sw.WriteLine("if var1[1] != 'z': raise AssertionError('test failed')");
                    sw.WriteLine("");
                    sw.WriteLine("if clsPart.Property != clsPart.Field * 5: raise AssertionError('test failed')");
                    sw.WriteLine("var2 = clsPart.Method(var1[0])");
                    sw.WriteLine("if var2 != 10 * 10: raise AssertionError('test failed')");
                }

                pe.ExecuteFile(tempFile2);
            } finally {
                File.Delete(tempFile1);
                File.Delete(tempFile2);
            }
        }

        // Bug: 542
        public void Scenario542() {
            PythonEngine pe = new PythonEngine();
            string tempFile1 = Path.GetTempFileName();

            try {
                using (StreamWriter sw = new StreamWriter(tempFile1)) {
                    sw.WriteLine("def M1(): return -1");
                    sw.WriteLine("def M2(): return +1");

                    sw.WriteLine("class C:");
                    sw.WriteLine("    def M1(self): return -1");
                    sw.WriteLine("    def M2(self): return +1");

                    sw.WriteLine("class C1:");
                    sw.WriteLine("    def M(): return -1");
                    sw.WriteLine("class C2:");
                    sw.WriteLine("    def M(): return +1");
                }

                pe.ExecuteFile(tempFile1);

                AreEqual(-1, pe.EvaluateAs<int>("M1()"));
                AreEqual(+1, pe.EvaluateAs<int>("M2()"));

                AreEqual(-1, (int)pe.Evaluate("M1()"));
                AreEqual(+1, (int)pe.Evaluate("M2()"));

                pe.Execute("if M1() != -1: raise AssertionError('test failed')");
                pe.Execute("if M2() != +1: raise AssertionError('test failed')");


                pe.Execute("c = C()");
                AreEqual(-1, pe.EvaluateAs<int>("c.M1()"));
                AreEqual(+1, pe.EvaluateAs<int>("c.M2()"));

                AreEqual(-1, (int)pe.Evaluate("c.M1()"));
                AreEqual(+1, (int)pe.Evaluate("c.M2()"));

                pe.Execute("if c.M1() != -1: raise AssertionError('test failed')");
                pe.Execute("if c.M2() != +1: raise AssertionError('test failed')");


                //AreEqual(-1, pe.EvaluateAs<int>("C1.M()"));
                //AreEqual(+1, pe.EvaluateAs<int>("C2.M()"));

                //AreEqual(-1, (int)pe.Evaluate("C1.M()"));
                //AreEqual(+1, (int)pe.Evaluate("C2.M()"));

                //pe.Execute("if C1.M() != -1: raise AssertionError('test failed')");
                //pe.Execute("if C2.M() != +1: raise AssertionError('test failed')");

            } finally {
                File.Delete(tempFile1);
            }
        }

        // Bug: 167 
        public void Scenario167() {
            PythonEngine pe = new PythonEngine();

            pe.Execute("a=1\r\nb=-1");
            AreEqual(1, pe.EvaluateAs<int>("a"));
            AreEqual(-1, pe.EvaluateAs<int>("b"));
        }

        // AddToPath
        public void ScenarioAddToPath() {
            PythonEngine pe = new PythonEngine();
            string ipc_dll = typeof(PythonEngine).Assembly.Location;
            string ipc_path = Path.GetDirectoryName(ipc_dll);
            pe.InitializeModules(ipc_path, ipc_path + "\\ipy.exe", PythonEngine.VersionString);
            string tempFile1 = Path.GetTempFileName();

            try {
                File.WriteAllText(tempFile1, "from lib.assert_util import *");

                try {
                    pe.ExecuteFile(tempFile1);
                    throw new Exception("Scenario7");
                } catch (IronPython.Runtime.Exceptions.PythonImportErrorException) { }

                pe.AddToPath(Common.ScriptTestDirectory);

                pe.ExecuteFile(tempFile1);
                pe.Execute("AreEqual(5, eval('2 + 3'))");
            } finally {
                File.Delete(tempFile1);
            }
        }

        // Options.ClrDebuggingEnabled

        delegate void ThrowExceptionDelegate();
        static void ThrowException() {
            throw new Exception("Exception from ThrowException");
        }

        public void ScenarioClrDebuggingEnabled() {
            const string lineNumber1 = "raise.py:line 21";
            const string lineNumber2 = "raise.py:line 23";

            EngineOptions engineOptions = new EngineOptions();
            engineOptions.ClrDebuggingEnabled = true;
            PythonEngine debuggableEngine = new PythonEngine(engineOptions);

            // Ensure that you do get good line numbers with ExecutionOptions.Default
            try {
                debuggableEngine.ExecuteFile(Common.InputTestDirectory + "\\raise.py");
                throw new Exception("We should not get here");
            } catch (IronPython.Runtime.Exceptions.StringException e1) {
                if (!e1.StackTrace.Contains(lineNumber1) || !e1.StackTrace.Contains(lineNumber2))
                    throw new Exception("Debugging is not enabled even though Options.ClrDebuggingEnabled is specified");
            }

            // Ensure that you do not get good line numbers without ExecutionOptions.Default
            try {
                standardEngine.ExecuteFile(Common.InputTestDirectory + "\\raise.py");
                throw new Exception("We should not get here");
            } catch (StringException e2) {
                if (e2.StackTrace.Contains(lineNumber1) || e2.StackTrace.Contains(lineNumber2))
                    throw new Exception("Debugging is enabled even though Options.ClrDebuggingEnabled is not specified");
            }

            // Ensure that all APIs work
            AreEqual(debuggableEngine.EvaluateAs<int>("x"), 1);
            IntIntDelegate d = debuggableEngine.CreateLambda<IntIntDelegate>("arg + x");
            AreEqual(d(100), 101);
            d = debuggableEngine.CreateMethod<IntIntDelegate>("var = arg + x\nreturn var");
            AreEqual(d(100), 101);
        }

        public void ScenarioExecuteFileOptimized() {
            PythonEngine pe = new PythonEngine();
            OptimizedEngineModule engineModule = pe.CreateOptimizedModule(Common.InputTestDirectory + "\\simpleCommand.py", "__main__", false);
            engineModule.Execute();
            AreEqual(1, pe.EvaluateAs<int>("x", engineModule));

            // Ensure that we can set new globals in the scope, and execute further code
            engineModule.Globals["y"] = 2;
            AreEqual(3, pe.EvaluateAs<int>("x+y", engineModule));
        }

        public void ScenarioOptimizedModuleBeforeExecutingGlobalCode() {
            PythonEngine pe = new PythonEngine();
            OptimizedEngineModule engineModule = pe.CreateOptimizedModule(Common.InputTestDirectory + "\\uninitializedGlobal.py", "__main__", true);
            AreEqual(engineModule.Globals.ContainsKey("x"), false);
            AreEqual(engineModule.Globals.ContainsKey("y"), false);

            // Set a global variable before executing global code
            engineModule.Globals["x"] = 1;
            engineModule.Execute();

            // Ensure that the global code picked up the value of "x" that was pumped into the module
            AreEqual(101, pe.EvaluateAs<int>("x", engineModule));
            AreEqual(2, pe.EvaluateAs<int>("y", engineModule));
        }

        public void ScenarioCreateOptimizedModule_ScriptThrows() {
            // We should be able to use the EngineModule even if an exception is thrown by the script
            OptimizedEngineModule engineModule = standardEngine.CreateOptimizedModule(Common.InputTestDirectory + "\\raise.py", "__main__", true);
            try {
                engineModule.Execute();
                throw new Exception("We should not reach here");
            } catch (StringException) {
                AreEqual(1, standardEngine.EvaluateAs<int>("x", engineModule));

                // Ensure that we can set new globals in the scope, and execute further code
                engineModule.Globals["y"] = 2;
                AreEqual(3, standardEngine.EvaluateAs<int>("x+y", engineModule));
            }
        }

        // Compile and Run
        public void ScenarioCompileAndRun() {
            PythonEngine pe = new PythonEngine();
            ClsPart clsPart = new ClsPart();

            pe.Globals[clspartName] = clsPart;
            CompiledCode compiledCode = pe.Compile("def f(): clsPart.Field += 10");
            compiledCode.Execute();

            compiledCode = pe.Compile("f()");
            compiledCode.Execute();
            AreEqual(10, clsPart.Field);
            compiledCode.Execute();
            AreEqual(20, clsPart.Field);
        }

        public void ScenarioStreamRedirect() {
            MemoryStream stdout = new MemoryStream();
            MemoryStream stdin = new MemoryStream();
            MemoryStream stderr = new MemoryStream();
            ASCIIEncoding encoding = new ASCIIEncoding();
            byte[] buffer = new byte[15];

            PythonEngine pe = new PythonEngine();
            pe.SetStandardError(stderr);
            pe.SetStandardInput(stdin);
            pe.SetStandardOutput(stdout);
            pe.Import("sys");

            stdin.Write(encoding.GetBytes("This is stdout"), 0, 14);
            stdin.Position = 0;
            pe.Execute("output = sys.__stdin__.readline()");
            AreEqual("This is stdout", pe.EvaluateAs<string>("output"));
            pe.Execute("sys.__stdout__.write(output)");
            stdout.Flush();
            stdout.Position = 0;
            int len = stdout.Read(buffer, 0, 14);
            AreEqual(14, len);
            AreEqual("This is stdout", encoding.GetString(buffer, 0, len));

            pe.Execute("sys.__stderr__.write(\"This is stderr\")");
            stderr.Flush();
            stderr.Position = 0;
            len = stderr.Read(buffer, 0, 14);
            AreEqual(14, len);
            AreEqual("This is stderr", encoding.GetString(buffer, 0, len));
        }

        public void ScenarioCreateMethodAndLambda() {
            PythonEngine pe = new PythonEngine();

            //Generic delegate types
            GenericDelegate<int, int, int> del = pe.CreateLambda<GenericDelegate<int, int, int>>("arg1+arg2");
            AreEqual(9, del(6, 3));
            del = pe.CreateMethod<GenericDelegate<int, int, int>>("return arg1+arg2");
            AreEqual(12, del(8, 4));

            //ByRef reference type arguments
            string arg = "Hello";
            RefStrDelegate rsdel = null;
            rsdel = pe.CreateLambda<RefStrDelegate>("'%s World!' % arg.Value");
            AreEqual("Hello World!", rsdel(ref arg));

            arg = "Hello";
            rsdel = pe.CreateMethod<RefStrDelegate>("arg.Value = '%s World!' % arg.Value\nreturn arg.Value");
            AreEqual("Hello World!", rsdel(ref arg));
            AreEqual("Hello World!", arg);

            //ByRef value type arguments
            int argi = 323;
            RefIntDelegate ridel = null;
            ridel = pe.CreateLambda<RefIntDelegate>("arg.Value+27");
            AreEqual(350, ridel(ref argi));

            argi = 323;
            ridel = pe.CreateMethod<RefIntDelegate>("arg.Value+=127\nreturn arg.Value+1");
            AreEqual(451, ridel(ref argi));
            AreEqual(450, argi);

            //Specify our own parameter names
            List<string> args = new List<string>(new string[] { "dir", "secondarg" });
            del = pe.CreateLambda<GenericDelegate<int, int, int>>("dir+secondarg", args);
            AreEqual(6, del(3, 3));
            del = pe.CreateMethod<GenericDelegate<int, int, int>>("return dir+secondarg", args);
            AreEqual(6, del(3, 3));
        }

        public void ScenarioCoverage() {
            PythonEngine pe = new PythonEngine();
            CompiledCode compiledCode = pe.Compile("arg1+arg2", "MyNewSourceFile");
            AreEqual("<code MyNewSourceFile>", compiledCode.ToString());
        }

        public void ScenarioNullArguments() {
            PythonEngine pe = new PythonEngine();
            string tempFile1 = Path.GetTempFileName();

            try {
                pe.Compile(null, tempFile1);
                throw new Exception();
            } catch (ArgumentNullException) {
                //Pass
            }

            try {
                pe.CompileFile(null);
                throw new Exception();
            } catch (ArgumentNullException) {
                //Pass
            }

            try {
                pe.CreateLambda<IntIntDelegate>("x=2", null, null);
                throw new Exception();
            } catch (ArgumentNullException) {
                //Pass
            }

            try {
                pe.CreateMethod<IntIntDelegate>("x=2", null, null);
                throw new Exception();
            } catch (ArgumentNullException) {
                //Pass
            }

            try {
                pe.CreateModule(null, new Dictionary<string, object>(), false);
                throw new Exception();
            } catch (ArgumentNullException) {
                //Pass
            }

            try {
                pe.CreateModule("moduleName", null, false);
                throw new Exception();
            } catch (ArgumentNullException) {
                //Pass
            }

            try {
                pe.CreateOptimizedModule(null, "moduleName", false);
                throw new Exception();
            } catch (ArgumentNullException) {
                //Pass
            }

            try {
                pe.CreateOptimizedModule(tempFile1, null, false);
                throw new Exception();
            } catch (ArgumentNullException) {
                //Pass
            }

            try {
                pe = new PythonEngine(null);
                throw new Exception();
            } catch (ArgumentNullException) {
                //Pass
            }
        }

        private static void ExecuteAndVerify(PythonEngine pe, EngineModule module, IDictionary<string, object> locals, string text, string expectedText) {
            MemoryStream output = new MemoryStream();
            pe.SetStandardOutput(output);
            pe.Execute(text, module, locals);
            output.Flush();
            output.Position = 0;
            AreEqual((new StreamReader(output)).ReadToEnd(), expectedText);
        }

        public void ScenarioVariableScoping() {
            PythonEngine pe = new PythonEngine();

            string tempFile1 = Path.GetTempFileName();

            try {
                Dictionary<string, object> globals = new Dictionary<string, object>();
                Dictionary<string, object> locals = new Dictionary<string, object>();
                EngineModule module1 = pe.CreateModule("module1", globals, true);

                //Set variables in each scope
                globals["x"] = 1;
                File.WriteAllText(tempFile1, "y = 2");
                pe.ExecuteFile(tempFile1, module1, locals);
                locals["z"] = 3;

                //Query for a variable from each scope
                AreEqual((int)pe.Evaluate("x", module1, locals), 1);
                AreEqual((int)pe.Evaluate("y", module1, locals), 2);
                AreEqual((int)pe.Evaluate("z", module1, locals), 3);

                AreEqual(pe.EvaluateAs<int>("x", module1, locals), 1);
                AreEqual(pe.EvaluateAs<int>("y", module1, locals), 2);
                AreEqual(pe.EvaluateAs<int>("z", module1, locals), 3);

                ExecuteAndVerify(pe, module1, locals, "print x", "1" + Environment.NewLine);
                ExecuteAndVerify(pe, module1, locals, "print y", "2" + Environment.NewLine);
                ExecuteAndVerify(pe, module1, locals, "print z", "3" + Environment.NewLine);

                //Set conflicting variables in each scope and verify resolution
                module1.Globals["scope"] = "module";
                AreEqual("module", pe.EvaluateAs<string>("scope", module1, locals));
                locals["scope"] = "local";
                AreEqual("local", pe.EvaluateAs<string>("scope", module1, locals));
                pe.Execute("scope='also local'", module1, locals);
                AreEqual("also local", pe.EvaluateAs<string>("scope", module1, locals));
                AreEqual("also local", (string)locals["scope"]);

                //Validate that the module dict hasn't changed in an unexpected way
                AreEqual(false, module1.Globals.ContainsKey("y"));
                AreEqual(false, module1.Globals.ContainsKey("z"));

                //Remove the conflicting variables from each scope and verify resolution
                pe.Execute("del(scope)", module1, locals);
                AreEqual(false, locals.ContainsKey("scope"));
                AreEqual("module", pe.EvaluateAs<string>("scope", module1, locals));
                module1.Globals.Remove("scope");
                try {
                    AreEqual("module", pe.EvaluateAs<string>("scope", module1, locals));
                    Console.WriteLine("All references to scope have been removed, we should get an error when looking up this name");
                    throw new Exception();
                } catch (PythonNameErrorException) {
                    //Pass
                }

                //Change an existing variable in each scope
                locals["z"] = 6;
                pe.Execute("y = 5", module1, locals);
                globals["x"] = 4;

                //Verify that the engine gets the updated values
                AreEqual(4, (int)pe.Evaluate("x", module1, locals));
                AreEqual(5, (int)pe.Evaluate("y", module1, locals));
                AreEqual(6, (int)pe.Evaluate("z", module1, locals));

                AreEqual(4, pe.EvaluateAs<int>("x", module1, locals));
                AreEqual(5, pe.EvaluateAs<int>("y", module1, locals));
                AreEqual(6, pe.EvaluateAs<int>("z", module1, locals));

                ExecuteAndVerify(pe, module1, locals, "print x", "4" + Environment.NewLine);
                ExecuteAndVerify(pe, module1, locals, "print y", "5" + Environment.NewLine);
                ExecuteAndVerify(pe, module1, locals, "print z", "6" + Environment.NewLine);

                //Hide a builtin from each scope and verify we can recover it
                globals["dir"] = "overriden in module";
                AreEqual("overriden in module", pe.EvaluateAs<string>("dir", module1, locals));
                globals.Remove("dir");
                AreEqual(true, pe.Evaluate("dir", module1, locals) is IronPython.Runtime.Calls.BuiltinFunction);

                locals["dir"] = "overriden in locals";
                AreEqual("overriden in locals", pe.EvaluateAs<string>("dir", module1, locals));
                locals.Remove("dir");
                AreEqual(true, pe.Evaluate("dir", module1, locals) is IronPython.Runtime.Calls.BuiltinFunction);
            } finally {
                File.Delete(tempFile1);
            }
        }

        public void Scenario12() {
            PythonEngine pe = new PythonEngine();

            string script = @"
class R(object):
    def __init__(self, a, b):
        self.a = a
        self.b = b
   
    def M(self):
        return self.a + self.b

    sum = property(M, None, None, None)

r = R(10, 100)
if r.sum != 110:
    raise AssertionError('Scenario 12 failed')
";
            CompiledCode compiledCode = pe.Compile(script);
            compiledCode.Execute();
        }

        public void ScenarioTrueDivision1() {
            PythonEngine pe = new PythonEngine();
            TestOldDivision(pe);
            pe.DefaultModule.TrueDivision = true;
            TestNewDivision(pe);
        }

        public void ScenarioTrueDivision2() {
            PythonEngine pe = new PythonEngine();
            TestOldDivision(pe);
            EngineModule em = pe.CreateModule("__future__", true);
            em.Globals["division"] = 1;
            pe.Execute("from __future__ import division");
            TestNewDivision(pe);
        }

        public void ScenarioTrueDivision3() {
            PythonEngine pe = new PythonEngine();
            CompiledCode cc = pe.Compile("from __future__ import division");
            TestOldDivision(pe);
            EngineModule future = pe.CreateModule("__future__", true);
            future.Globals["division"] = 1;
            EngineModule td = pe.CreateModule("truediv", false);
            cc.Execute(td);
            TestOldDivision(pe);
        }

        public void ScenarioTrueDivision4() {
            PythonEngine pe = new PythonEngine();
            pe.AddToPath(Common.ScriptTestDirectory);

            string file = System.IO.Path.Combine(Common.ScriptTestDirectory, "engine_td_test.py");
            System.IO.File.WriteAllText(file, "result = 1/2");

            DivisionOption old = Options.Division;

            try {
                Options.Division = DivisionOption.Old;
                pe.DefaultModule.TrueDivision = true;
                pe.Execute("import engine_td_test");
                int res = pe.EvaluateAs<int>("engine_td_test.result");
                AreEqual(res, 0);
            } finally {
                Options.Division = old;
                try {
                    System.IO.File.Delete(file);
                } catch { }
            }
        }

        public void ScenarioTrueDivision5() {
            PythonEngine pe = new PythonEngine();
            pe.AddToPath(Common.ScriptTestDirectory);

            string file = System.IO.Path.Combine(Common.ScriptTestDirectory, "engine_td_test.py");
            System.IO.File.WriteAllText(file, "from __future__ import division; result = 1/2");

            try {
                pe.Execute("import engine_td_test");
                double res = pe.EvaluateAs<double>("engine_td_test.result");
                AreEqual(res, 0.5);
                AreEqual(pe.DefaultModule.TrueDivision, false);
            } finally {
                try {
                    System.IO.File.Delete(file);
                } catch { }
            }
        }

        private static void TestOldDivision(PythonEngine pe) {
            pe.Execute("result = 1/2");
            AreEqual((int)pe.Globals["result"], 0);
            AreEqual(pe.EvaluateAs<int>("1/2"), 0);
            pe.Execute("exec 'result = 3/2'");
            AreEqual((int)pe.Globals["result"], 1);
            AreEqual(pe.EvaluateAs<int>("eval('3/2')"), 1);
        }

        private static void TestNewDivision(PythonEngine pe) {
            pe.Execute("result = 1/2");
            AreEqual((double)pe.Globals["result"], 0.5);
            AreEqual(pe.EvaluateAs<double>("1/2"), 0.5);
            pe.Execute("exec 'result = 3/2'");
            AreEqual((double)pe.Globals["result"], 1.5);
            AreEqual(pe.EvaluateAs<double>("eval('3/2')"), 1.5);
        }

        // More to come: exception related...

        public static int Negate(int arg) { return -1 * arg; }

        static void AreEqual<T>(T expected, T actual) {
            if (expected == null && actual == null) return;

            if (!expected.Equals(actual)) {
                Console.WriteLine("{0} {1}", expected, actual);
                throw new Exception();
            }
        }
    }
}
