using System;
using System.Collections.Specialized;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using System.Collections;
using System.Reflection;

namespace Python.Runtime
{
    //========================================================================
    // Implements a Python type that provides access to CLR namespaces. The
    // type behaves like a Python module, and can contain other sub-modules.
    //========================================================================

    internal class ModuleObject : ExtensionType
    {
        Dictionary<string, ManagedType> cache;
        internal string moduleName;
        internal IntPtr dict;
        protected string _namespace;

        public ModuleObject(string name) : base()
        {
            if (name == String.Empty)
            {
                throw new ArgumentException("Name must not be empty!");
            }
            moduleName = name;
            cache = new Dictionary<string, ManagedType>();
            _namespace = name;

            // Use the filename from any of the assemblies just so there's something for
            // anything that expects __file__ to be set.
            string filename = "unknown";
            string docstring = "Namespace containing types from the following assemblies:\n\n";
            foreach (Assembly a in AssemblyManager.GetAssemblies(name))
            {
                if (!a.IsDynamic && a.Location != null)
                {
                    filename = a.Location;
                }
                docstring += "- " + a.FullName + "\n";
            }

            dict = Runtime.PyDict_New();
            IntPtr pyname = Runtime.PyString_FromString(moduleName);
            IntPtr pyfilename = Runtime.PyString_FromString(filename);
            IntPtr pydocstring = Runtime.PyString_FromString(docstring);
            IntPtr pycls = TypeManager.GetTypeHandle(this.GetType());
            Runtime.PyDict_SetItemString(dict, "__name__", pyname);
            Runtime.PyDict_SetItemString(dict, "__file__", pyfilename);
            Runtime.PyDict_SetItemString(dict, "__doc__", pydocstring);
            Runtime.PyDict_SetItemString(dict, "__class__", pycls);
            Runtime.XDecref(pyname);
            Runtime.XDecref(pyfilename);
            Runtime.XDecref(pydocstring);

            Marshal.WriteIntPtr(this.pyHandle, ObjectOffset.DictOffset(this.pyHandle), dict);

            InitializeModuleMembers();
        }


        //===================================================================
        // Returns a ClassBase object representing a type that appears in
        // this module's namespace or a ModuleObject representing a child
        // namespace (or null if the name is not found). This method does
        // not increment the Python refcount of the returned object.
        //===================================================================

        public ManagedType GetAttribute(string name, bool guess)
        {
            ManagedType cached = null;
            this.cache.TryGetValue(name, out cached);
            if (cached != null)
            {
                return cached;
            }

            ModuleObject m;
            ClassBase c;
            Type type;

            //if (AssemblyManager.IsValidNamespace(name))
            //{
            //    IntPtr py_mod_name = Runtime.PyString_FromString(name);
            //    IntPtr modules = Runtime.PyImport_GetModuleDict();
            //    IntPtr module = Runtime.PyDict_GetItem(modules, py_mod_name);
            //    if (module != IntPtr.Zero)
            //        return (ManagedType)this;
            //    return null;
            //}

            string qname = (_namespace == String.Empty)
                ? name
                : _namespace + "." + name;

            // If the fully-qualified name of the requested attribute is
            // a namespace exported by a currently loaded assembly, return
            // a new ModuleObject representing that namespace.

            if (AssemblyManager.IsValidNamespace(qname))
            {
                m = new ModuleObject(qname);
                StoreAttribute(name, m);
                return (ManagedType)m;
            }

            // Look for a type in the current namespace. Note that this
            // includes types, delegates, enums, interfaces and structs.
            // Only public namespace members are exposed to Python.

            type = AssemblyManager.LookupType(qname);
            if (type != null)
            {
                if (!type.IsPublic)
                {
                    return null;
                }
                c = ClassManager.GetClass(type);
                StoreAttribute(name, c);
                return (ManagedType)c;
            }

            // This is a little repetitive, but it ensures that the right
            // thing happens with implicit assembly loading at a reasonable
            // cost. Ask the AssemblyManager to do implicit loading for each
            // of the steps in the qualified name, then try it again.
            bool ignore = name.StartsWith("__");
            if (AssemblyManager.LoadImplicit(qname, !ignore))
            {
                if (AssemblyManager.IsValidNamespace(qname))
                {
                    m = new ModuleObject(qname);
                    StoreAttribute(name, m);
                    return (ManagedType)m;
                }

                type = AssemblyManager.LookupType(qname);
                if (type != null)
                {
                    if (!type.IsPublic)
                    {
                        return null;
                    }
                    c = ClassManager.GetClass(type);
                    StoreAttribute(name, c);
                    return (ManagedType)c;
                }
            }

            // We didn't find the name, so we may need to see if there is a
            // generic type with this base name. If so, we'll go ahead and
            // return it. Note that we store the mapping of the unmangled
            // name to generic type -  it is technically possible that some
            // future assembly load could contribute a non-generic type to
            // the current namespace with the given basename, but unlikely
            // enough to complicate the implementation for now.

            if (guess)
            {
                string gname = GenericUtil.GenericNameForBaseName(
                    _namespace, name);
                if (gname != null)
                {
                    ManagedType o = GetAttribute(gname, false);
                    if (o != null)
                    {
                        StoreAttribute(name, o);
                        return o;
                    }
                }
            }

            return null;
        }


        //===================================================================
        // Stores an attribute in the instance dict for future lookups.
        //===================================================================

        private void StoreAttribute(string name, ManagedType ob)
        {
            Runtime.PyDict_SetItemString(dict, name, ob.pyHandle);
            cache[name] = ob;
        }


        //===================================================================
        // Preloads all currently-known names for the module namespace. This
        // can be called multiple times, to add names from assemblies that
        // may have been loaded since the last call to the method.
        //===================================================================

        public void LoadNames()
        {
            ManagedType m = null;
            foreach (string name in AssemblyManager.GetNames(_namespace))
            {
                this.cache.TryGetValue(name, out m);
                if (m == null)
                {
                    ManagedType attr = this.GetAttribute(name, true);
                }
            }
        }

        /// <summary>
        /// Initialize module level functions and attributes
        /// </summary>
        internal void InitializeModuleMembers()
        {
            Type funcmarker = typeof(ModuleFunctionAttribute);
            Type propmarker = typeof(ModulePropertyAttribute);
            Type ftmarker = typeof(ForbidPythonThreadsAttribute);
            Type type = this.GetType();

            BindingFlags flags = BindingFlags.Public | BindingFlags.Static;

            while (type != null)
            {
                MethodInfo[] methods = type.GetMethods(flags);
                for (int i = 0; i < methods.Length; i++)
                {
                    MethodInfo method = methods[i];
                    object[] attrs = method.GetCustomAttributes(funcmarker, false);
                    object[] forbid = method.GetCustomAttributes(ftmarker, false);
                    bool allow_threads = (forbid.Length == 0);
                    if (attrs.Length > 0)
                    {
                        string name = method.Name;
                        MethodInfo[] mi = new MethodInfo[1];
                        mi[0] = method;
                        ModuleFunctionObject m = new ModuleFunctionObject(type, name, mi, allow_threads);
                        StoreAttribute(name, m);
                    }
                }

                PropertyInfo[] properties = type.GetProperties();
                for (int i = 0; i < properties.Length; i++)
                {
                    PropertyInfo property = properties[i];
                    object[] attrs = property.GetCustomAttributes(propmarker, false);
                    if (attrs.Length > 0)
                    {
                        string name = property.Name;
                        ModulePropertyObject p = new ModulePropertyObject(property);
                        StoreAttribute(name, p);
                    }
                }
                type = type.BaseType;
            }
        }


        //====================================================================
        // ModuleObject __getattribute__ implementation. Module attributes
        // are always either classes or sub-modules representing subordinate
        // namespaces. CLR modules implement a lazy pattern - the sub-modules
        // and classes are created when accessed and cached for future use.
        //====================================================================

        public static IntPtr tp_getattro(IntPtr ob, IntPtr key)
        {
            ModuleObject self = (ModuleObject)GetManagedObject(ob);

            if (!Runtime.PyString_Check(key))
            {
                Exceptions.SetError(Exceptions.TypeError, "string expected");
                return IntPtr.Zero;
            }

            IntPtr op = Runtime.PyDict_GetItem(self.dict, key);
            if (op != IntPtr.Zero)
            {
                Runtime.XIncref(op);
                return op;
            }

            string name = Runtime.GetManagedString(key);
            if (name == "__dict__")
            {
                Runtime.XIncref(self.dict);
                return self.dict;
            }

            ManagedType attr = self.GetAttribute(name, true);

            if (attr == null)
            {
                Exceptions.SetError(Exceptions.AttributeError, name);
                return IntPtr.Zero;
            }

            Runtime.XIncref(attr.pyHandle);
            return attr.pyHandle;
        }

        //====================================================================
        // ModuleObject __repr__ implementation.
        //====================================================================

        public static IntPtr tp_repr(IntPtr ob)
        {
            ModuleObject self = (ModuleObject)GetManagedObject(ob);
            string s = String.Format("<module '{0}'>", self.moduleName);
            return Runtime.PyString_FromString(s);
        }
    }

    /// <summary>
    /// The CLR module is the root handler used by the magic import hook
    /// to import assemblies. It has a fixed module name "clr" and doesn't
    /// provide a namespace.
    /// </summary>
    internal class CLRModule : ModuleObject
    {
        protected static bool hacked = false;
        protected static bool interactive_preload = true;
        internal static bool preload;
        // XXX Test performance of new features //
        internal static bool _SuppressDocs = false;
        internal static bool _SuppressOverloads = false;

        public CLRModule() : base("clr")
        {
            _namespace = String.Empty;

            // This hackery is required in order to allow a plain Python to
            // import the managed runtime via the CLR bootstrapper module.
            // The standard Python machinery in control at the time of the
            // import requires the module to pass PyModule_Check. :(
            if (!hacked)
            {
                IntPtr type = this.tpHandle;
                IntPtr mro = Marshal.ReadIntPtr(type, TypeOffset.tp_mro);
                IntPtr ext = Runtime.ExtendTuple(mro, Runtime.PyModuleType);
                Marshal.WriteIntPtr(type, TypeOffset.tp_mro, ext);
                Runtime.XDecref(mro);
                hacked = true;
            }
        }

        /// <summary>
        /// The initializing of the preload hook has to happen as late as
        /// possible since sys.ps1 is created after the CLR module is
        /// created.
        /// </summary>
        internal void InitializePreload()
        {
            if (interactive_preload)
            {
                interactive_preload = false;
                if (Runtime.PySys_GetObject("ps1") != IntPtr.Zero)
                {
                    preload = true;
                }
                else
                {
                    Exceptions.Clear();
                    preload = false;
                }
            }
        }

        [ModuleFunctionAttribute()]
        public static bool getPreload()
        {
            return preload;
        }

        [ModuleFunctionAttribute()]
        public static void setPreload(bool preloadFlag)
        {
            preload = preloadFlag;
        }

        //[ModulePropertyAttribute]
        public static bool SuppressDocs
        {
            get { return _SuppressDocs; }
            set { _SuppressDocs = value; }
        }

        //[ModulePropertyAttribute]
        public static bool SuppressOverloads
        {
            get { return _SuppressOverloads; }
            set { _SuppressOverloads = value; }
        }

        [ModuleFunctionAttribute()]
        [ForbidPythonThreadsAttribute()]
        public static Assembly AddReference(string name)
        {
            AssemblyManager.UpdatePath();
            Assembly assembly = null;
            assembly = AssemblyManager.FindLoadedAssembly(name);
            if (assembly == null)
            {
                assembly = AssemblyManager.LoadAssemblyPath(name);
            }
            if (assembly == null)
            {
                assembly = AssemblyManager.LoadAssembly(name);
            }
            if (assembly == null)
            {
                assembly = AssemblyManager.LoadAssemblyFullPath(name);
            }
            if (assembly == null)
            {
                string msg = String.Format("Unable to find assembly '{0}'.", name);
                throw new System.IO.FileNotFoundException(msg);
            }

            return assembly;
        }

        [ModuleFunctionAttribute()]
        [ForbidPythonThreadsAttribute()]
        public static string FindAssembly(string name)
        {
            AssemblyManager.UpdatePath();
            return AssemblyManager.FindAssembly(name);
        }

        [ModuleFunctionAttribute()]
        public static String[] ListAssemblies(bool verbose)
        {
            AssemblyName[] assnames = AssemblyManager.ListAssemblies();
            String[] names = new String[assnames.Length];
            for (int i = 0; i < assnames.Length; i++)
            {
                if (verbose)
                    names[i] = assnames[i].FullName;
                else
                    names[i] = assnames[i].Name;
            }
            return names;
        }

        [ModuleFunctionAttribute()]
        public static int _AtExit()
        {
            return Runtime.AtExit();
        }
    }
}