# This module is used by the Pmw package system.
# The PmwLoader class can be used to simulate a python module,
# but also supports importing of submodules on demand.  This technique
# reduces startup time because Pmw submodules which are not used are
# not loaded.
#
# The PmwLoader class also supports runtime selection of the Pmw
# version(s) to use.

import sys
import os
import types

_PMW_DEF = 'Pmw.def'           # Pmw definition file
_BASEMODULE = 'Base'           # Name of Base module

class PmwLoader:

    def __init__(self, dirpath, instdirs, dirs):
        self._dirpath = dirpath
        self._instdirs = instdirs
        self._dirs = dirs
        self._initialised = 0
        self._version = str.replace(instdirs[0][4:], '_', '.')
        self._alpha_versions = ()
    #======================================================================

    # Public methods.  These methods will be seen as "module methods".

    def setversion(self, version):
        if self._version == version:
            return
        if self._initialised:
            raise ValueError('Cannot change Pmw version after initialisation')
        self._version = version

    def setalphaversions(self, *alpha_versions):
        if self._alpha_versions == alpha_versions:
            return
        if self._initialised:
            raise ValueError('Cannot change Pmw alpha versions after initialisation')
        self._alpha_versions = alpha_versions

    def version(self, alpha = 0):
        if alpha:
            return self._alpha_versions
        else:
            return self._version

    def installedversions(self, alpha = 0):
        rtn = []
        if alpha:
            dirs = [x for x in self._dirs if x[:5] == 'Alpha']
            dirs.sort()
            dirs.reverse()
            for dir in dirs:
                rtn.append(str.replace(dir[6:], '_', '.'))
        else:
            for dir in self._instdirs:
                rtn.append(str.replace(dir[4:], '_', '.'))
        return rtn

    #======================================================================

    # Private methods

    def _getmodule(self,modpath):
        __import__(modpath)
        mod = sys.modules[modpath]
        return mod

    def _initialise(self):
        searchpath = []

        for version in self._alpha_versions:
            alphadir = '_Pmw.Alpha_%s.lib' % str.replace(version, '.', '_')
            searchpath.append(alphadir)
        libdir = '_Pmw.Pmw_%s.lib' % str.replace(self._version, '.', '_')
        searchpath.append(libdir)

        # Create attributes for the PmwBase classes and functions.
        for path in searchpath:
            try:
                basemodule = self._getmodule(path + '.Pmw' + _BASEMODULE)
                break
            except ImportError as msg:
                if path == searchpath[-1]:
                    # No PmwBase module found.
                    raise ImportError(msg)

        for k,v in list(basemodule.__dict__.items()):
            if k[0] is not '_' and type(v) != types.ModuleType:
                self.__dict__[k] = v

        # Set the Pmw definitions from the Pmw.def file.
		#former variable name 'dict' clashed when checking isinstance(v, dict)
        dictionary = {
            '_widgets'      : {},
            '_extraWidgets' : {},
            '_functions'    : {},
            '_modules'      : {},
        }
        for name in list(dictionary.keys()):
            self.__dict__[name] = {}
        searchpath.reverse()
        for path in searchpath:
            pathbit = os.path.join(*tuple(str.split(path[5:], '.')))
            lpath = os.path.join(self._dirpath, pathbit)
            d = {}
            exec(compile(open(os.path.join(lpath,_PMW_DEF)).read(), os.path.join(lpath,_PMW_DEF), 'exec'), d)
            for k,v in list(d.items()):
                if k in dictionary:
                    if type(v) == tuple:
                        for item in v:
                            modpath = path + '.Pmw' + item
                            dictionary[k][item] = modpath
                    elif isinstance(v, dict):
                        for k1, v1 in list(v.items()):
                            modpath = path + '.Pmw' + v1
                            dictionary[k][k1] = modpath
        self.__dict__.update(dictionary)
        self._widgets_keys = list(self._widgets.keys())
        self._extraWidgets_keys = list(self._extraWidgets.keys())
        self._functions_keys = list(self._functions.keys())
        self._modules_keys = list(self._modules.keys())

        self._initialised = 1

    def __getattr__(self, name):
        if not self._initialised:
            self._initialise()
            # Beware: _initialise may have defined 'name'
            if name in list(self.__dict__.keys()):
                return self.__dict__[name]

        # The requested attribute is not yet set. Look it up in the
        # tables set by Pmw.def, import the appropriate module and
        # set the attribute so that it will be found next time.

        if name in self._widgets_keys:
            # The attribute is a widget name.
            mod = self._getmodule(self._widgets[name])
            cls = getattr(mod,name)
            self.__dict__[name] = cls
            return cls

        if name in self._functions_keys:
            # The attribute is a function from one of the modules.
            modname = self._functions[name]
            mod  = self._getmodule(modname)
            func = getattr(mod, name)
            self.__dict__[name] = func
            return func

        if name in self._modules_keys:
            # The attribute is a module
            mod = self._getmodule(self._modules[name])
            self.__dict__[name] = mod
            return mod

        if name in self._extraWidgets_keys:
            # XXX I should import them all, once I've started.
            # The attribute is a widget name in a module of another name
            modname = self._extraWidgets[name]
            mod = self._getmodule(modname)
            cls = getattr(mod, name)
            self.__dict__[name] = cls
            return cls

        # The attribute is not known by Pmw, report an error.
        raise AttributeError(name)
