# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is the Python XPCOM language bindings.
#
# The Initial Developer of the Original Code is
# ActiveState Tool Corp.
# Portions created by the Initial Developer are Copyright (C) 2000, 2001
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
#   Mark Hammond <MarkH@ActiveState.com> (original author)
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****

# This module provides the JavaScript "components" interface
from . import xpt
import xpcom
import xpcom._xpcom as _xpcom
import xpcom.client
import xpcom.server

StringTypes = [bytes, str]

def _get_good_iid(iid):
    if iid is None:
        iid = _xpcom.IID_nsISupports
    elif type(iid) in StringTypes and len(iid)>0 and iid[0] != "{":
        iid = getattr(interfaces, iid)
    return iid

# The "manager" object.
manager = xpcom.client.Component(_xpcom.GetComponentManager(), _xpcom.IID_nsIComponentManager)

# The component registrar
registrar = xpcom.client.Component(_xpcom.GetComponentManager(), _xpcom.IID_nsIComponentRegistrar)

# The "interfaceInfoManager" object - JS doesnt have this.
interfaceInfoManager = _xpcom.XPTI_GetInterfaceInfoManager()

# The serviceManager - JS doesnt have this either!
serviceManager = _xpcom.GetServiceManager()

# The "Exception" object
Exception = xpcom.COMException

# Base class for our collections.
# It appears that all objects supports "." and "[]" notation.
# eg, "interface.nsISupports" or interfaces["nsISupports"]
class _ComponentCollection:
    # Bases are to over-ride 2 methods.
    # _get_one(self, name) - to return one object by name
    # _build_dict - to return a dictionary which provide access into
    def __init__(self):
        self._dict_data = None
    def keys(self):
        if self._dict_data is None:
            self._dict_data = self._build_dict()
        return list(self._dict_data.keys())
    def items(self):
        if self._dict_data is None:
            self._dict_data = self._build_dict()
        return list(self._dict_data.items())
    def values(self):
        if self._dict_data is None:
            self._dict_data = self._build_dict()
        return list(self._dict_data.values())
#    def has_key(self, key):
#        if self._dict_data is None:
#            self._dict_data = self._build_dict()
#        return self._dict_data.has_key(key)

    def __len__(self):
        if self._dict_data is None:
            self._dict_data = self._build_dict()
        return len(self._dict_data)

    def __getattr__(self, attr):
        if self._dict_data is not None and attr in self._dict_data:
            return self._dict_data[attr]
        return self._get_one(attr)
    def __getitem__(self, item):
        if self._dict_data is not None and item in self._dict_data:
            return self._dict_data[item]
        return self._get_one(item)

_constants_by_iid_map = {}

class _Interface:
    # An interface object.
    def __init__(self, name, iid):
        # Bypass self.__setattr__ when initializing attributes.
        d = self.__dict__
        d['_iidobj_'] = iid # Allows the C++ framework to treat this as a native IID.
        d['name'] = name
    def __cmp__(self, other):
        this_iid = self._iidobj_
        other_iid = getattr(other, "_iidobj_", other)
        return cmp(this_iid, other_iid)
    def __eq__(self, other):
        this_iid = self._iidobj_
        other_iid = getattr(other, "_iidobj_", other)
        return this_iid == other_iid
    def __hash__(self):
        return hash(self._iidobj_)
    def __str__(self):
        return str(self._iidobj_)
    def __getitem__(self, item):
        raise TypeError("components.interface objects are not subscriptable")
    def __setitem__(self, item, value):
        raise TypeError("components.interface objects are not subscriptable")
    def __setattr__(self, attr, value):
        raise AttributeError("Can not set attributes on components.Interface objects")
    def __getattr__(self, attr):
        # Support constants as attributes.
        c = _constants_by_iid_map.get(self._iidobj_)
        if c is None:
            c = {}
            i = xpt.Interface(self._iidobj_)
            for c_ob in i.constants:
                c[c_ob.name] = c_ob.value
            _constants_by_iid_map[self._iidobj_] = c
        if attr in c:
            return c[attr]
        raise AttributeError("'%s' interfaces do not define a constant '%s'" % (self.name, attr))


class _Interfaces(_ComponentCollection):
    def _get_one(self, name):
        try:
            item = interfaceInfoManager.GetInfoForName(name)
        except xpcom.COMException as why:
            # Present a better exception message, and give a more useful error code.
            from . import nsError
            raise xpcom.COMException(nsError.NS_ERROR_NO_INTERFACE, "The interface '%s' does not exist" % (name,))
        return _Interface(item.GetName(), item.GetIID())

    def _build_dict(self):
        ret = {}
        enum = interfaceInfoManager.EnumerateInterfaces()
        while not enum.IsDone():
            # Call the Python-specific FetchBlock, to keep the loop in C.
            items = enum.FetchBlock(500, _xpcom.IID_nsIInterfaceInfo)
            # This shouldnt be necessary, but appears to be so!
            for item in items:
                ret[item.GetName()] = _Interface(item.GetName(), item.GetIID())
        return ret

# And the actual object people use.
interfaces = _Interfaces()

del _Interfaces # Keep our namespace clean.

#################################################
class _Class:
    def __init__(self, contractid):
        self.contractid = contractid
    def __getattr__(self, attr):
        if attr == "clsid":
            rc = registrar.contractIDToCID(self.contractid)
            # stash it away - it can never change!
            self.clsid = rc
            return rc
        raise AttributeError("%s class has no attribute '%s'" % (self.contractid, attr))
    def createInstance(self, iid = None):
        import xpcom.client
        try:
            return xpcom.client.Component(self.contractid, _get_good_iid(iid))
        except xpcom.COMException as details:
            from . import nsError
            # Handle "no such component" in a cleaner way for the user.
            if details.errno == nsError.NS_ERROR_FACTORY_NOT_REGISTERED:
                raise xpcom.COMException(details.errno, "No such component '%s'" % (self.contractid,))
            raise # Any other exception reraise.
    def getService(self, iid = None):
        return serviceManager.getServiceByContractID(self.contractid, _get_good_iid(iid))

class _Classes(_ComponentCollection):
    def __init__(self):
        _ComponentCollection.__init__(self)
    def _get_one(self, name):
        # XXX - Need to check the contractid is valid!
        return _Class(name)

    def _build_dict(self):
        ret = {}
        enum = registrar.enumerateContractIDs()
        while enum.hasMoreElements():
            # Call the Python-specific FetchBlock, to keep the loop in C.
            items = enum.fetchBlock(2000, _xpcom.IID_nsISupportsCString)
            for item in items:
                name = str(item.data)
                ret[name] = _Class(name)
        return ret

classes = _Classes()

del _Classes

del _ComponentCollection

# The ID function
ID = _xpcom.ID

# A helper to cleanup our namespace as xpcom shuts down.
class _ShutdownObserver:
    _com_interfaces_ = interfaces.nsIObserver
    def observe(self, service, topic, extra):
        global manager, registrar, classes, interfaces, interfaceInfoManager, _shutdownObserver, serviceManager, _constants_by_iid_map
        manager = registrar = classes = interfaces = interfaceInfoManager = _shutdownObserver = serviceManager = _constants_by_iid_map = None
        xpcom.client._shutdown()
        xpcom.server._shutdown()

svc = _xpcom.GetServiceManager().getServiceByContractID("@mozilla.org/observer-service;1", interfaces.nsIObserverService)
# Observers will be QI'd for a weak-reference, so we must keep the
# observer alive ourself, and must keep the COM object alive,
# _not_ just the Python instance!!!
_shutdownObserver = xpcom.server.WrapObject(_ShutdownObserver(), interfaces.nsIObserver)
# Say we want a weak ref due to an assertion failing.  If this is fixed, we can pass 0,
# and remove the lifetime hacks above!  See http://bugzilla.mozilla.org/show_bug.cgi?id=99163
svc.addObserver(_shutdownObserver, "xpcom-shutdown", 1)
del svc, _ShutdownObserver
