# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*-
#
# This file is part of the LibreOffice project.
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This file incorporates work covered by the following license notice:
#
#   Licensed to the Apache Software Foundation (ASF) under one or more
#   contributor license agreements. See the NOTICE file distributed
#   with this work for additional information regarding copyright
#   ownership. The ASF licenses this file to you under the Apache
#   License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 .
#
import uno
import pyuno
import os
import sys

from com.sun.star.lang import XTypeProvider, XSingleComponentFactory, XServiceInfo
from com.sun.star.uno import RuntimeException, XCurrentContext
from com.sun.star.beans.MethodConcept import ALL as METHOD_CONCEPT_ALL
from com.sun.star.beans.PropertyConcept import ALL as PROPERTY_CONCEPT_ALL

from com.sun.star.reflection.ParamMode import \
     IN as PARAM_MODE_IN, \
     OUT as PARAM_MODE_OUT, \
     INOUT as PARAM_MODE_INOUT

from com.sun.star.beans.PropertyAttribute import \
     MAYBEVOID as PROP_ATTR_MAYBEVOID, \
     BOUND as PROP_ATTR_BOUND, \
     CONSTRAINED as PROP_ATTR_CONSTRAINED, \
     TRANSIENT as PROP_ATTR_TRANSIENT, \
     READONLY as PROP_ATTR_READONLY, \
     MAYBEAMBIGUOUS as PROP_ATTR_MAYBEAMBIGUOUS, \
     MAYBEDEFAULT as PROP_ATTR_MAYBEDEFAULT, \
     REMOVABLE as PROP_ATTR_REMOVABLE

def _mode_to_str( mode ):
    ret = "[]"
    if mode == PARAM_MODE_INOUT:
        ret = "[inout]"
    elif mode == PARAM_MODE_OUT:
        ret = "[out]"
    elif mode == PARAM_MODE_IN:
        ret = "[in]"
    return ret

def _propertymode_to_str( mode ):
    ret = ""
    if PROP_ATTR_REMOVABLE & mode:
        ret = ret + "removable "
    if PROP_ATTR_MAYBEDEFAULT & mode:
        ret = ret + "maybedefault "
    if PROP_ATTR_MAYBEAMBIGUOUS & mode:
        ret = ret + "maybeambiguous "
    if PROP_ATTR_READONLY & mode:
        ret = ret + "readonly "
    if PROP_ATTR_TRANSIENT & mode:
        ret = ret + "transient "
    if PROP_ATTR_CONSTRAINED & mode:
        ret = ret + "constrained "
    if PROP_ATTR_BOUND & mode:
        ret = ret + "bound "
    if PROP_ATTR_MAYBEVOID & mode:
        ret = ret + "maybevoid "
    return ret.rstrip()

def inspect( obj , out ):
    if isinstance( obj, uno.Type ) or \
       isinstance( obj, uno.Char ) or \
       isinstance( obj, uno.Bool ) or \
       isinstance( obj, uno.ByteSequence ) or \
       isinstance( obj, uno.Enum ) or \
       isinstance( obj, uno.Any ):
        out.write( str(obj) + "\n")
        return

    ctx = uno.getComponentContext()
    introspection = \
         ctx.ServiceManager.createInstanceWithContext( "com.sun.star.beans.Introspection", ctx )

    out.write( "Supported services:\n" )
    if hasattr( obj, "getSupportedServiceNames" ):
        names = obj.getSupportedServiceNames()
        for ii in names:
            out.write( "  " + ii + "\n" )
    else:
        out.write( "  unknown\n" )

    out.write( "Interfaces:\n" )
    if hasattr( obj, "getTypes" ):
        interfaces = obj.getTypes()
        for ii in interfaces:
            out.write( "  " + ii.typeName + "\n" )
    else:
        out.write( "  unknown\n" )

    access = introspection.inspect( obj )
    methods = access.getMethods( METHOD_CONCEPT_ALL )
    out.write( "Methods:\n" )
    for ii in methods:
        out.write( "  " + ii.ReturnType.Name + " " + ii.Name )
        args = ii.ParameterTypes
        infos = ii.ParameterInfos
        out.write( "( " )
        for i in range( 0, len( args ) ):
            if i > 0:
                out.write( ", " )
            out.write( _mode_to_str( infos[i].aMode ) + " " + args[i].Name + " " + infos[i].aName )
        out.write( " )\n" )

    props = access.getProperties( PROPERTY_CONCEPT_ALL )
    out.write ("Properties:\n" )
    for ii in props:
        out.write( "  ("+_propertymode_to_str( ii.Attributes ) + ") "+ii.Type.typeName+" "+ii.Name+ "\n" )

def createSingleServiceFactory( clazz, implementationName, serviceNames ):
    return _FactoryHelper_( clazz, implementationName, serviceNames )

class _ImplementationHelperEntry:
    def __init__(self, ctor,serviceNames):
        self.ctor = ctor
        self.serviceNames = serviceNames

class ImplementationHelper:
    def __init__(self):
        self.impls = {}

    def addImplementation( self, ctor, implementationName, serviceNames ):
        self.impls[implementationName] =  _ImplementationHelperEntry(ctor,serviceNames)

    def writeRegistryInfo( self, regKey, smgr ):
        for i in list(self.impls.items()):
            keyName = "/"+ i[0] + "/UNO/SERVICES"
            key = regKey.createKey( keyName )
            for serviceName in i[1].serviceNames:
                key.createKey( serviceName )
        return 1

    def getComponentFactory( self, implementationName , regKey, smgr ):
        entry = self.impls.get( implementationName, None )
        if entry is None:
            raise RuntimeException( implementationName + " is unknown" , None )
        return createSingleServiceFactory( entry.ctor, implementationName, entry.serviceNames )

    def getSupportedServiceNames( self, implementationName ):
        entry = self.impls.get( implementationName, None )
        if entry is None:
            raise RuntimeException( implementationName + " is unknown" , None )
        return entry.serviceNames

    def supportsService( self, implementationName, serviceName ):
        entry = self.impls.get( implementationName,None )
        if entry is None:
            raise RuntimeException( implementationName + " is unknown", None )
        return serviceName in entry.serviceNames


class ImplementationEntry:
    def __init__(self, implName, supportedServices, clazz ):
        self.implName = implName
        self.supportedServices = supportedServices
        self.clazz = clazz

def writeRegistryInfoHelper( smgr, regKey, seqEntries ):
    for entry in seqEntries:
        keyName = "/"+ entry.implName + "/UNO/SERVICES"
        key = regKey.createKey( keyName )
        for serviceName in entry.supportedServices:
            key.createKey( serviceName )

def systemPathToFileUrl( systemPath ):
    "returns a file-url for the given system path"
    return pyuno.systemPathToFileUrl( systemPath )

def fileUrlToSystemPath( url ):
    "returns a system path (determined by the system, the python interpreter is running on)"
    return pyuno.fileUrlToSystemPath( url )

def getComponentFactoryHelper( implementationName, smgr, regKey, seqEntries ):
    for x in seqEntries:
        if x.implName == implementationName:
            return createSingleServiceFactory( x.clazz, implementationName, x.supportedServices )

def addComponentsToContext( toBeExtendedContext, contextRuntime, componentUrls, loaderName ):
    smgr = contextRuntime.ServiceManager
    loader = smgr.createInstanceWithContext( loaderName, contextRuntime )
    implReg = smgr.createInstanceWithContext( "com.sun.star.registry.ImplementationRegistration",contextRuntime)

    isWin = os.name == 'nt' or os.name == 'dos'
    isMac = sys.platform == 'darwin'
    #   create a temporary registry
    for componentUrl in componentUrls:
        reg = smgr.createInstanceWithContext( "com.sun.star.registry.SimpleRegistry", contextRuntime )
        reg.open( "", 0, 1 )
        if not isWin and componentUrl.endswith( ".uno" ):  # still allow platform independent naming
            if isMac:
                componentUrl = componentUrl + ".dylib"
            else:
                componentUrl = componentUrl + ".so"

        implReg.registerImplementation( loaderName,componentUrl, reg )
        rootKey = reg.getRootKey()
        implementationKey = rootKey.openKey( "IMPLEMENTATIONS" )
        implNames = implementationKey.getKeyNames()
        extSMGR = toBeExtendedContext.ServiceManager
        for x in implNames:
            fac = loader.activate( max(x.split("/")),"",componentUrl,rootKey)
            extSMGR.insert( fac )
        reg.close()

# never shrinks !
_g_typeTable = {}
def _unohelper_getHandle( self):
    ret = None
    if self.__class__ in _g_typeTable:
        ret = _g_typeTable[self.__class__]
    else:
        names = {}
        traverse = list(self.__class__.__bases__)
        while len( traverse ) > 0:
            item = traverse.pop()
            bases = item.__bases__
            if uno.isInterface( item ):
                names[item.__pyunointerface__] = None
            elif len(bases) > 0:
                # the "else if", because we only need the most derived interface
                traverse = traverse + list(bases)#

        lst = list(names.keys())
        types = []
        for x in lst:
            t = uno.getTypeByName( x )
            types.append( t )

        ret = tuple(types)
        _g_typeTable[self.__class__] = ret
    return ret

class Base(XTypeProvider):
    def getTypes( self ):
        return _unohelper_getHandle( self )
    def getImplementationId(self):
        return ()

class CurrentContext(XCurrentContext, Base ):
    """a current context implementation, which first does a lookup in the given
       hashmap and if the key cannot be found, it delegates to the predecessor
       if available
    """
    def __init__( self, oldContext, hashMap ):
        self.hashMap = hashMap
        self.oldContext = oldContext

    def getValueByName( self, name ):
        if name in self.hashMap:
            return self.hashMap[name]
        elif self.oldContext is not None:
            return self.oldContext.getValueByName( name )
        else:
            return None

# -------------------------------------------------
# implementation details
# -------------------------------------------------
class _FactoryHelper_( XSingleComponentFactory, XServiceInfo, Base ):
    def __init__( self, clazz, implementationName, serviceNames ):
        self.clazz = clazz
        self.implementationName = implementationName
        self.serviceNames = serviceNames

    def getImplementationName( self ):
        return self.implementationName

    def supportsService( self, ServiceName ):
        return ServiceName in self.serviceNames

    def getSupportedServiceNames( self ):
        return self.serviceNames

    def createInstanceWithContext( self, context ):
        return self.clazz( context )

    def createInstanceWithArgumentsAndContext( self, args, context ):
        return self.clazz( context, *args )

# vim: set shiftwidth=4 softtabstop=4 expandtab:
