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

using System.Reflection.Emit;

namespace IronPython.Compiler {
    public abstract partial class ScopeStatement : Stmt {
        public class Binding {
            [Flags]
            private enum Kind {
                Free = 0,
                Bound = 0x1,        // Defined in the scope
                Global = 0x2,       // Global name
                Environment = 0x4,  // Part of the scope's environment (referenced from enlosed scopes)
                Deleted = 0x8,      // del was detected for this name in the scope
            }
            private Kind kind;
            private int index;          // Flow analysis - index into the bit vector
            private bool unassigned;    // Name ever referenced without being bound

            public override string ToString() {
                return string.Format("Binding [({0}), idx {1}, unb {2}]", kind, index, unassigned);
            }

            public bool IsFree {
                get { return kind == Kind.Free; }
            }

            public bool IsBound {
                get { return (kind & Kind.Bound) != 0; }
            }

            public bool IsEnvironment {
                get { return (kind & Kind.Environment) != 0; }
            }

            public bool IsLocal {
                get { return (kind & Kind.Bound) != 0 && (kind & Kind.Global) == 0; }
            }

            public bool IsGlobal {
                get { return (kind & Kind.Global) != 0; }
            }

            public bool IsDeleted {
                get { return (kind & Kind.Deleted) != 0; }
            }

            public void Bind() {
                kind |= Kind.Bound;
            }

            public void BindGlobal() {
                kind |= Kind.Bound | Kind.Global;
            }

            public void BindDeleted() {
                kind |= Kind.Bound | Kind.Deleted;
            }

            public void Reference() {
            }

            public void MakeEnvironment() {
                kind |= Kind.Environment;
            }

            public void MakeUnboundGlobal() {
                kind |= Kind.Global;
            }

            internal int Index {
                get { return index; }
                set { index = value; }
            }

            internal bool Unassigned {
                get { return unassigned; }
            }

            internal void UnassignedUse() {
                unassigned = true;
            }

            [Conditional("DEBUG")]
            public void Validate() {
                switch (kind) {
                    case Kind.Free:                             // free variable
                    case Kind.Global:                           // free bound to global
                    case Kind.Bound:                            // bound local
                    case Kind.Bound | Kind.Global:              // bound global
                    case Kind.Bound | Kind.Environment:         // bound local promoted to environment
                        break;

                    // invalid cases
                    case Kind.Environment:
                    case Kind.Environment | Kind.Global:
                    case Kind.Bound | Kind.Environment | Kind.Global:
                        Debug.Fail("invalid binding");
                        break;
                }
            }
        }

        public ScopeStatement parent;
        public Stmt body;

        // Is a closure (receives environment as parameter)
        internal bool isClosure = false;

        // Defines its own environment for lexically nested scopes
        // (or passes environment through for access across scopes)
        protected bool hasEnvironment = false;

        // Names referenced in the scope with and their binding kind
        protected Dictionary<Name, Binding> names = new Dictionary<Name, Binding>();

        // Names that need to be promoted to the environment and their assigned index in the environment
        protected Dictionary<Name, EnvironmentReference> environment;
        protected EnvironmentFactory environmentFactory;
        public int tempsCount;

        protected ScopeStatement(Stmt body) {
            this.body = body;
        }

        public Dictionary<Name, Binding> Bindings {
            get { return names; }
        }

        protected Binding Get(Name name) {
            Binding binding;
            if (!names.TryGetValue(name, out binding)) {
                binding = new Binding();
                names[name] = binding;
            }
            return binding;
        }

        public void Bind(Name name) {
            Get(name).Bind();
        }

        public void Reference(Name name) {
            Get(name).Reference();
        }

        public void BindGlobal(Name name) {
            Get(name).BindGlobal();
        }

        public void BindDeleted(Name name) {
            Get(name).BindDeleted();
        }

        private void MakeEnvironment(Name name) {
            Get(name).MakeEnvironment();
        }

        public virtual bool TryGetBinding(Name name, out Binding binding) {
            binding = null;
            return false;
        }

        public bool BindInParent(Name name, Binder binder) {
            Binding binding;
            if (TryGetBinding(name, out binding)) {
                if (binding.IsGlobal) {
                    return false;
                } else {
                    if (binding.IsDeleted) {
                        binder.ReportSyntaxError(string.Format("can not delete variable '{0}' referenced in nested scope", name.GetString()), this);
                    }
                    MakeEnvironment(name);
                    hasEnvironment = true;
                    return true;
                }
            }
            if (parent != null) {
                if (parent.BindInParent(name, binder)) {
                    isClosure = true;
                    hasEnvironment = true;
                    return true;
                }
            }
            return false;
        }

        public void BindNames(GlobalSuite globals, Binder binder) {
            foreach (KeyValuePair<Name, Binding> kv in names) {
                Binding b = kv.Value;
                Name n = kv.Key;

                // Global binding
                if (b.IsGlobal) {
                    globals.Bind(n);
                    continue;
                }

                // Free variable binding
                if (b.IsFree) {
                    // Bind in the parent
                    if (parent != null) {
                        if (parent.BindInParent(kv.Key, binder)) {
                            isClosure = true;
                            continue;
                        }
                    }

                    // Free, but not defined in parent scopes ==> global
                    globals.Bind(n);
                    b.MakeUnboundGlobal();
                    continue;
                }

                // Defined in this scope
                if (b.IsBound) {
                    continue;
                }

                Debug.Fail("Unbound name", n.GetString());
            }
        }

        protected void PromoteLocalsToEnvironment() {
            hasEnvironment = true;
            foreach (KeyValuePair<Name, Binding> kv in names) {
                if (kv.Value.IsLocal) {
                    kv.Value.MakeEnvironment();
                }
            }
        }

        private int CalculateEnvironmentSize() {
            int environmentSize = tempsCount;
            foreach (KeyValuePair<Name, Binding> kv in names) {
                if (kv.Value.IsEnvironment) {
                    environmentSize++;
                }
            }
            return environmentSize;
        }

        protected Slot CreateEnvironment(CodeGen cg) {
            // Get the environment size
            int size = CalculateEnvironmentSize();

            // Create the array for the names
            Slot namesSlot = cg.GetLocalTmp(typeof(SymbolId[]));
            cg.EmitInt(size - tempsCount);
            cg.Emit(System.Reflection.Emit.OpCodes.Newarr, typeof(SymbolId));
            namesSlot.EmitSet(cg);

            // Find the right environment type
            ConstructorInfo ctor;
            EnvironmentFactory esf;
            Type envType = GetEnvironmentType(size, cg, out ctor, out esf);

            // Emit the static link for the environment constructor
            cg.EmitStaticLinkOrNull();
            cg.EmitCallerContext();
            // Emit the names array for the environment constructor
            namesSlot.EmitGet(cg);
            cg.EmitNew(ctor);

            // Store the environment reference in the local
            Slot environmentSlot = cg.GetFrameSlot(envType);
            environmentSlot.EmitSet(cg);

            // Remember the environment factory for parent access
            environmentFactory = esf;

            // Create environment references
            environment = new Dictionary<Name, EnvironmentReference>();

            foreach (KeyValuePair<Name, Binding> kv in names) {
                if (!kv.Value.IsEnvironment) continue;
                Name name = kv.Key;
                EnvironmentReference er = esf.MakeEnvironmentReference(name);
                Slot slot = er.CreateSlot(environmentSlot);
                Slot current;
                if (cg.Names.Slots.TryGetValue(name, out current)) {
                    slot.EmitSet(cg, current);
                } else {
                    slot.EmitSetUninitialized(cg, name);
                }

                // Set the name into the array
                namesSlot.EmitGet(cg);
                cg.EmitInt(er.Index);
                cg.Emit(OpCodes.Ldelema, typeof(SymbolId));
                cg.EmitSymbolIdInt(name.GetString());
                cg.Emit(OpCodes.Call, typeof(SymbolId).GetConstructor(new Type[]{typeof(int)}));

                // The full slot goes to the codegen's namespace
                cg.Names.SetSlot(name, slot);
                // Environment reference goes to the environment
                environment[name] = er;
            }

            return environmentSlot;
        }

        protected void CreateLocalSlots(CodeGen cg) {
            foreach (KeyValuePair<Name, Binding> kv in names) {
                if (kv.Value.IsLocal) {
                    Slot local = cg.Names.EnsureLocalSlot(kv.Key);
                    if (kv.Value.Unassigned) {
                        local.EmitSetUninitialized(cg, kv.Key);
                    }
                }
            }
        }
        protected void CreateGlobalSlots(CodeGen to, CodeGen from) {
            to.Names.Globals = from.Names.Globals.Relocate(to.ContextSlot);

            foreach (KeyValuePair<Name, Binding> kv in names) {
                if (kv.Value.IsGlobal) {
                    to.Names.SetSlot(kv.Key, to.Names.Globals.GetSlot(kv.Key));
                }
            }
        }

        private bool TryGetEnvironmentReference(Name name, out EnvironmentReference er) {
            Binding binding;
            if (TryGetBinding(name, out binding)) {
                Debug.Assert(binding.IsBound);
                Debug.Assert(environment.ContainsKey(name));
                er = environment[name];
                return true;
            }
            er = null;
            return false;
        }

        protected void CreateClosureSlots(CodeGen cg) {
            CreateClosureSlots(cg.StaticLinkSlot, cg.Names);
        }

        private void CreateClosureSlots(Slot staticLink, Namespace nspace) {
            foreach (KeyValuePair<Name, Binding> kv in names) {
                if (!kv.Value.IsFree) continue;

                // Find the slot
                Slot instance = staticLink;
                ScopeStatement current = parent;

                for (; ; ) {
                    instance = new CastSlot(instance, current.environmentFactory.EnvironmentType);
                    Debug.Assert(current != null, "cannot resolve closure", kv.Key.GetString());
                    if (current.environment != null) {
                        EnvironmentReference er;

                        // Is the slot in this environment?
                        if (current.TryGetEnvironmentReference(kv.Key, out er)) {
                            nspace.SetSlot(kv.Key, er.CreateSlot(instance));
                            break;
                        }
                    }
                    instance = current.environmentFactory.MakeParentSlot(instance);
                    current = current.parent;
                }
            }
        }

        private bool? emitLocalDictionary = null;

        protected bool EmitLocalDictionary {
            get {
                if (Options.Frames) {
                    return true;
                }

                if (emitLocalDictionary == null) {
                    emitLocalDictionary = MightNeedLocalsWalker.CheckMightNeedLocalsDictionary(body);
                }
                return (bool)emitLocalDictionary;
            }
        }

        public override string GetDocString() {
            return body.GetDocString();
        }
    }

    internal class MightNeedLocalsWalker : AstWalker {
        public static bool CheckMightNeedLocalsDictionary(Stmt s) {
            MightNeedLocalsWalker w = new MightNeedLocalsWalker();
            s.Walk(w);
            return w.MightNeedLocals;
        }

        private bool MightNeedLocals = false;

        private MightNeedLocalsWalker() { }

        #region AstWalker Method Overrides

        public override void PostWalk(CallExpr node) {
            MightNeedLocals |= node.MightNeedLocalsDictionary();
        }

        public override void PostWalk(ExecStmt node) {
            MightNeedLocals |= node.NeedsLocalsDictionary();
        }

        #endregion
    }

    public class GlobalSuite : ScopeStatement {
        public GlobalSuite(Stmt body)
            : base(body) {
        }

        public override void Emit(CodeGen cg) {
            CreateGlobalSlots(cg);
            body.Emit(cg);
        }

        public override void Walk(IAstWalker w) {
            if (w.Walk(this)) {
                body.Walk(w);
            }
            w.PostWalk(this);
        }

        private void CreateGlobalSlots(CodeGen cg) {
            foreach (KeyValuePair<Name, Binding> kv in names) {
                Slot slot = cg.Names.Globals.GetOrMakeSlot(kv.Key);
                if (kv.Value.IsGlobal) {
                    cg.Names.SetSlot(kv.Key, slot);
                } else {
                    cg.Names.EnsureLocalSlot(kv.Key);
                }
            }
        }
    }
}
