#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# © 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

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

from appy.all import *
from appy.utils import asDict
from appy.model.base import Base
from appy.model.fields.calendar import Calendar

from .medical.Care import Care
from .medical.daily import Daily
from .medical.filters import Filters
from .medical.mediData import MediData
from .journeyman.Beneficiary import Beneficiary

#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
B_D       = 'Beneficiary data'
BD_UPD    = f'{B_D} "%s" %s.'
BD_DEL    = f'{B_D} "%s" deleted.'

#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
class BenDataWorkflow:
    '''Workflow for BenData objects'''

    # ~~~ Roles ~~~
    ma = 'Manager'
    ow = Role('Owner', local=True)
    # Consult Utils.py for an explanation about these local roles
    ga = Role('GroupAdmin'      , local=True)
    gm = Role('GroupManager'    , local=True)
    ge = Role('GroupEditor'     , local=True)
    gw = Role('GroupWriter'     , local=True)
    gr = Role('GroupReader'     , local=True)
    gn = Role('GroupCaremanager', local=True)
    gc = Role('GroupCaregiver'  , local=True)

    # ~~~ Groups of roles ~~~
    managers = (ma, ga, gn)
    editors  = (ma, ow, ga, gn)
    all      = (ma, ow, ga, gm, ge, gw, gr, gn, gc)

    # ~~~ States ~~~
    active = State({r:all, w:editors, d:managers}, initial=True)
    inactive = State({r:all, w:managers, d:ma})

    # ~~~ Transitions ~~~
    tp = {'condition': managers, 'confirm': True}
    deactivate = Transition( (active, inactive), **tp)
    reactivate = Transition( (inactive, active), **tp)

#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
class BenData:
    '''Data related to a beneficiary in the context of a given group'''

    # A BenData object is therefore a kind of association class between a
    # beneficiary and a group. The Ref to the beneficiary is below. The Ref to
    # the group is defined on the group. Currently, such data is 100% related to
    # the medical module.

    # Some elements will be traversable
    traverse = Base.traverse.copy()

    # Make MediData class available and traversable here
    traverse['MediData'] = True
    MediData = MediData

    DateTime = DateTime
    workflow = BenDataWorkflow

    # CSS classes
    styles = {'title': 'titlet'}

    @staticmethod
    def update(class_):
        '''Configures the title'''
        title = class_.fields['title']
        title.show = Show.VE_

    mainGroup = Group('main', ['200em', ''], style='grid', hasLabel=False)
    p = {'group': mainGroup}
    listColumns = ('title', 'state*100px|')

    # Let anyone create BenData instances, because the write permission on
    # HsGroup.benData will block anyone excepted authorized people.
    creators = ['Authenticated']

    def validBen(self, value):
        '''Ensure, when creating a new BenData object, that such an object does
           not exist yet for the chosen beneficiary (p_value).'''
        # Execute this only when creating a new UserData instance
        if not self.isTemp(): return True
        ben = value[0]
        for data in self.container.benData:
            if ben == data.ben:
                return self.translate('ben_data_duplicate')
        return True

    ben = Ref(Beneficiary, add=False, link='popup', render='links',
              back=Ref(attribute='data', page=Page('data', show='view'),
                       multiplicity=(1,None), showHeaders=True,
                       shownInfo=listColumns, showActions=False),
              show=lambda o: True if o.isTemp() else Show.E_,
              multiplicity=(1,1), select=Beneficiary.popupSearch,
              validator=validBen, **p)

    #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    #                            Main fields
    #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    # Has this beneficiary medical data in this group ? For the moment, because
    # ben data is only used for medical-related data, it has no sense to define
    # a BenData object for a beneficiary not being managed by the medical
    # module. Consequently, field "medical" below is currently hidden.

    medical = Boolean(layouts=Boolean.Layouts.gd, default=True, show='xml', **p)
    comment = Text(Layouts.gd, **p)

    #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    #                               Cares
    #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    def getCareOrder(self, care):
        '''Gets the order for this p_care, allowing to sort cares
           antichronologically in Refs "cares" and "pastCares".'''
        start = care.start
        return -start.millis() if start else 0

    cpf = {'link': False, 'multiplicity': (0,None), 'composite': True,
           'page': Page('cares', show='view'), 'layouts': Ref.Layouts.wdb,
           'showHeaders': True, 'shownInfo': Care.listColumns,
           'insert': getCareOrder, 'changeOrder': False,
           'actionsDisplay': 'right'}

    cares = Ref(Care, add=True, back=Ref(attribute='data', show=False), **cpf)

    pastCares = Ref(Care, add=False, delete=True,
                    back=Ref(attribute='pdata', show=False), **cpf)

    #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    #                              Planning
    #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    plan = {'page': Page('planning', show='view')}

    # The planning stores every measure taken and care provided to this
    # beneficiary.

    def listEventTypes(self):
        '''Lists the event types one may enter into this calendar'''
        # Events of the following types can be defined:
        #   a. cares
        #   b. measures
        #
        # For both, the stored eventType will be the stringified IID for the
        # corresponding CareType or Measure object, as found on the related HP
        # group, within ref HpGroup::careTypes or HpGroup::measures.
        group = self.container
        r = []
        for name in ('careTypes', 'measures'):
            if not group.isEmpty(name):
                for o in getattr(group, name):
                    if o.state == 'active':
                        r.append(str(o.iid))
        return r

    # i18n label for a prefix to set for naming every event type
    eventPrefix = {'CareType': 'Care', 'Measure': 'Measure'}

    def getEventName(self, eventType):
        '''Gets the complete name of the given p_eventType'''
        # p_eventType is an object's IID
        o = self.getObject(eventType)
        if not o: return f'Unknown :: {eventType}'
        prefix = self.translate(self.eventPrefix[o.class_.name])
        return f'{prefix} · {o.getShownValue()}' if o else '?'

    planning = Calendar(eventTypes=listEventTypes, eventNameMethod=getEventName,
      editable=False, timeslots=None, render='week',
      filters=[
        Calendar.Filter.Select('section', 'Section_plural',
          Selection(Filters.listSections), Filters.showSection),
        Calendar.Filter.Select('measure', 'Measure_plural',
          Selection(Filters.listMeasures), Filters.showMeasure),
        Calendar.Filter.Select('careType', 'CareType_plural',
          Selection(Filters.listCareTypes), Filters.showCareType),
      ], **plan)

    # Pod export for the planning
    docPlanning = Pod(template='pod/Events.odt', getChecked='planning',
                      layouts=Pod.Layouts.l.clone(css='topSpace'), **plan)

    #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    #                               Sidebar
    #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    # Pages where the sidebar will be visible
    medicalPages = asDict(('main', 'history', 'cares', 'planning'))

    @classmethod
    def getSidebar(class_, o, layout):
        '''Render a sidebar on the appropriate pages'''
        if layout == 'view' and o.req.page in class_.medicalPages:
            return o.config.mediSidebar

    sidebar = getSidebar

    mediDaily = Computed(method=Daily.px, show='sidebar', layouts=Layouts.wf,
                         context=Daily.getContext, label='HpGroup')

    #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    #                             Main methods
    #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    def mayDelete(self):
        '''It is not possible anymore to delete p_self if some sub-data are
           defined.'''
        empty = self.isEmpty
        return empty('cares') and empty('pastCares')

    def onDelete(self):
        '''Logs the deletion'''
        self.log(BD_DEL % self.title)

    def onEdit(self, created):
        # Create a title
        group = self.mgroup
        self.title = f'{self.ben.title} ↔ {group.title}'
        # Set local roles
        group.setLocalRoles(self)
        verb = 'created' if created else 'edited'
        self.log(BD_UPD % (self.title, verb))
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
