#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# © Hub People 2024› https://www.hubpeople.be · https://www.geezteem.com/455
# AGPL-3.0-or-later - https://www.gnu.org/licenses/agpl-3.0.txt

'''Options class that initializes module data'''

#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
from DateTime import DateTime

from appy.all import *

from . import Utils
import HubPeople.medical.booter
import HubPeople.timetracker.booter

#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
BOOT_F_G  = '%s module :: Group "%s" (%s) initialized from group "%s" (%s).'

#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
class Booter:
    '''Options class for enabling the timetracker on some HP group'''

    creators = True # Let anyone "create" a Booter object
    popup = ('450px', '390px')
    indexable = False
    layouts = Layouts.Page.centered

    @staticmethod
    def update(class_):
        '''Configure the title'''
        # The title is unused: hide it
        title = class_.fields['title']
        title.show = False

    p = {'group': Group('main', ['19%','81%'], style='grid', hasLabel=False)}

    def getModuleName(self, lowered=False, withModule=False):
        '''Retrieve, from the initiator, the name of the module to work on, with
           the module object itself if p_withModule is True.'''
        name = self.initiator.field.name[6:]
        r = name.lower() if lowered else name
        if withModule:
            module = eval(f'HubPeople.{r.lower()}.booter')
            r = r, module
        return r

    # Display the name of the module whose data will be initialized
    def getModuleTitle(self):
        '''Returns a sentence explaining which module is going to be
           initialized.'''
        return self.translate(f'HpGroup_enable{self.getModuleName()}')

    infoModule = Computed(method=getModuleTitle, focus=True, show='edit',
                          layouts=Layout('f|', css='bottomSpace'))

    # The module start date: it will be copied afterwards on field
    # HpGroup::<module>Start.

    start = Date(format=Date.WITHOUT_HOUR, native=True, layouts=Layouts.gd,
                 default=lambda o: DateTime(), **p)

    # Choose the source of data for initialising module sub-objects
    p['group'] = Group('dataSource', group=p['group'])

    def showSource(self):
        '''Letting the user choose the way to initialise the module is only
           possible if there is at least one group having the same module
           already configured on this workspace.'''
        return bool(self.selectableGroups())

    source = Select(validator=('default', 'peer'), multiplicity=(1,1),
                    default='default', render='radio', layouts=Layouts.f,
                    show=showSource, **p)

    def selectableGroups(self):
        '''Lists the groups whose module is already configured'''
        # Get the result from the cache if present
        cache = self.cache
        if 'booterGroups' in cache: return cache.booterGroups
        r = []
        enabled = f'{self.getModuleName(lowered=True)}Enabled'
        for group in self.search('HpGroup', secure=True):
            if getattr(group, enabled):
                r.append((str(group.iid), group.title))
        r.sort(key=lambda o:o[1])
        # Store the result in the cache and return it
        cache.booterGroups = r
        return r

    peer = Select(validator=Selection(selectableGroups), multiplicity=(1,1),
                  show=showSource, master=source, masterValue='peer',
                  layouts=Layout('f-d'), **p)

    # Expression for getting all forward Refs from a Appy class
    forwardRefs = 'isinstance(field, Ref) and not field.isBack'

    def clone(self, group, peer, name, clones, counts):
        '''Create, within Ref p_group.<p_name>, a clone of every active object
           from Ref p_peer.<p_name>.'''
        for o in getattr(peer, name):
            # Ignore inactive ones, but count it
            if o.state != 'active':
                counts.no += 1
                continue
            # Create the clone and add it on the target p_group. Exclude refs
            # for now: refs will be managed later and will be updated with
            # clones, not with originals.
            class_ = o.class_
            refs = class_.getFields(self.forwardRefs, names=True)
            clone = group.createFrom(name, o, exclude=refs)
            # Update p_clones
            clones[o.iid] = clone
            # Count it
            className = class_.name
            if className in counts:
                setattr(counts, className, getattr(counts, className) + 1)
            else:
                setattr(counts, className, 1)
            # Set Refs on the clone, if appropriate
            for rname in refs:
                link = bool(clone.getField(rname).link)
                if link:
                    # Reify links on the cloned object
                    for tied in (o.values.get(rname) or ()):
                        iid = tied.iid
                        if iid in clones:
                            clone.link(rname, clones[iid])
                else:
                    # Create sub-objects on the clone
                    for sub in (o.values.get(rname) or ()):
                        clone.createFrom(rname, sub)

    def initFrom(self, group, module, moduleName):
        '''Creates, in p_group, configuration objects related to this m_module
           by cloning those from another group, defined in p_self.peer.'''
        # Get the group from which to clone configuration objects
        peer = self.getObject(self.peer)
        # Count the number of configuration elements that were created
        counts = Utils.Counts(no=0)
        # Get all the cloned objects, keyed by the iid of their originals
        clones = {}
        for name in getattr(group, f'{moduleName}Refs'):
            self.clone(group, peer, name, clones, counts)
        # Return a detailed message and log
        _ = self.translate
        mapping = {'peer': peer.title, 'module': _(f'module_{moduleName}')}
        message = _('module_configured_from', mapping=mapping)
        textFun = lambda n: _('cloned_no') if n == 'no' else _(f'{n}_plural')
        self.log(BOOT_F_G % (moduleName, group.identifier, group.title,
                             peer.identifier, peer.title))
        return f'{message}{counts.getDetails(textFun=textFun)}'

    def run(self):
        '''Enable the selected module and create configuration objects in the
           current HP group.'''
        group = self.initiator.o
        moduleName, module = self.getModuleName(lowered=True, withModule=True)
        # Ensure configuration objects do not exist on the current group
        if module.Booter.isConfiguredOn(group):
            # This should not happen
            return True, self.translate('module_conf_exists')
        # Enable the module
        setattr(group, f'{moduleName}Enabled', True)
        setattr(group, f'{moduleName}Start', self.start)
        # Initialise configuration elements, either from the standard set, or by
        # copying configuration elements from another group.
        if self.source == 'default':
            # Delegate this to the module
            r = module.Booter.initDefault(group)
        else:
            r = self.initFrom(group, module, moduleName)
        # Ensure all local roles are correctly set on v_group
        group.setLocalRoles()
        group.reindex()
        # Optional final step
        module.Booter.finalize(group)
        self.resp.fleetingMessage = False
        return True, r
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
