# Kanbanara Members Component
# Written by Rebecca Shalfield between 2013 and 2017
# Copyright (c) Rebecca Shalfield 2013-2017
# Released under the GNU AGPL v3

'''Kanbanara's Members Component'''

import datetime
import logging
import os
import re
import urllib.parse

import cherrypy
from kanbanara import Kanbanara
from pymongo import MongoClient


class Members(Kanbanara):
    '''Kanbanara's Members Component'''

    @cherrypy.expose
    def absence(self):
        """Allows a user's absence settings to be updated"""
        Kanbanara.check_authentication(f'/{self.component}/absence')
        session_id         = Kanbanara.cookie_handling(self)
        session_document   = self.sessions_collection.find_one({"session_id": session_id})
        member_document    = Kanbanara.get_member_document(self, session_document)
        absence_start_date = member_document.get('absencestartdate', 0)
        absence_end_date   = member_document.get('absenceenddate', 0)
        content = []
        content.append(Kanbanara.header(self, 'absence', 'Absence'))
        content.append(Kanbanara.filter_bar(self, 'absence'))
        content.append(Kanbanara.menubar(self))
        content.append(self.insert_page_title_and_online_help(session_document,
                                                              'absence', 'Absence'))
        content.append('<div align="center">')
        content.append('<form action="/members/update_absence" method="post"><table class="form">')
        content.append('<tr><th>Absence Start Date</th><td>')
        if absence_start_date:
            date_format = self.convert_time_to_display_format(absence_start_date)
            content.append(f'<input class="absencestartdate" type="text" name="absence_start_date" value="{date_format}">')
        else:
            content.append('<input class="absencestartdate" type="text" name="absence_start_date">')

        content.append('</td></tr>')
        content.append('<tr><th>Absence End Date</th><td>')
        if absence_end_date:
            date_format = self.convert_time_to_display_format(absence_end_date)
            content.append(f'<input class="absenceenddate" type="text" name="absence_end_date" value="{date_format}">')
        else:
            content.append('<input class="absenceenddate" type="text" name="absence_end_date">')

        content.append('</td></tr>')
        content.append('</tr><td colspan="2"><input type="submit" value="Update"></td></tr>')
        content.append('</table></form>')
        content.append('</div>')
        content.append(Kanbanara.footer(self))
        return "".join(content)

    @cherrypy.expose
    def logoff(self):
        """Allows a user to cleanly log off Kanbanara"""
        username = Kanbanara.check_authentication(f'/{self.component}/logoff')
        session_id = Kanbanara.cookie_handling(self)
        session_document = self.sessions_collection.find_one({"session_id": session_id})
        if session_document and 'username' in session_document and session_document['username']:
            del session_document['username']
            session_document['lastaccess'] = datetime.datetime.utcnow()
            self.sessions_collection.save(session_document)

        pattern = re.compile(r'^('+username+r')_\d+\.svg$')
        for filename in os.listdir(os.path.join(self.current_dir, '..', 'svgcharts')):
            if pattern.findall(filename):
                os.remove(os.path.join(self.current_dir, '..', 'svgcharts', filename))

        raise cherrypy.HTTPRedirect("/authenticate/logoff", 302)

    @cherrypy.expose
    def personal_work_in_progress_limits(self):
        """Allows a user's personal Work In Progress (WIP) settings to be updated"""
        # TODO - Need to add min and max wips for a step rather than individual columns
        Kanbanara.check_authentication(f'/{self.component}/personal_work_in_progress_limits')
        session_id = Kanbanara.cookie_handling(self)
        session_document = self.sessions_collection.find_one({"session_id": session_id})
        member_document = Kanbanara.get_member_document(self, session_document)
        if 'project_wips' in member_document:
            project_wips = member_document['project_wips']
        else:
            project_wips = {}

        project = member_document.get('project', '')
        content = []
        content.append(Kanbanara.header(self, 'personal_work_in_progress_limits',
                                        'Personal Work In Progress (WIP) Limits'))
        content.append(Kanbanara.filter_bar(self, 'personal_work_in_progress_limits'))
        content.append(Kanbanara.menubar(self))
        content.append(self.insert_page_title_and_online_help(session_document,
                                                              'personal_work_in_progress_limits',
                                                              'Personal Work In Progress (WIP) Limits'))
        for project_document in self.projects_collection.find({'project': project,
                                                               'workflow': {'$exists': True}}):
            enforce_wip_limits = False
            workflow = project_document['workflow']
            if project in project_wips:
                personal_wips = project_wips[project]
            else:
                personal_wips = {}

            if 'enforcewiplimits' in personal_wips:
                enforce_wip_limits = personal_wips['enforcewiplimits']

            workflow_index = project_document.get('workflow_index', {})
            condensed_column_states = workflow_index.get('condensed_column_states', [])
            stepsharedmaxwip = {}
            stepmainminwip = {}
            stepbufferminwip = {}
            stepmainmaxwip = {}
            stepbuffermaxwip = {}
            stepcounterpartminwip = {}
            stepcounterpartmaxwip = {}
            for step_no in range(12):
                stepsharedmaxwip[step_no] = 0
                stepmainminwip[step_no] = 0
                stepmainmaxwip[step_no] = -1
                stepcounterpartminwip[step_no] = 0
                stepcounterpartmaxwip[step_no] = -1
                stepbufferminwip[step_no] = 0
                stepbuffermaxwip[step_no] = -1
                if member_document:
                    if 'step'+str(step_no)+'sharedmaxwip' in personal_wips:
                        stepsharedmaxwip[step_no] = personal_wips['step'+str(step_no)+'sharedmaxwip']

                    if 'step'+str(step_no)+'mainminwip' in personal_wips:
                        stepmainminwip[step_no] = personal_wips['step'+str(step_no)+'mainminwip']

                    if 'step'+str(step_no)+'mainmaxwip' in personal_wips:
                        stepmainmaxwip[step_no] = personal_wips['step'+str(step_no)+'mainmaxwip']

                    if 'step'+str(step_no)+'counterpartminwip' in personal_wips:
                        stepcounterpartminwip[step_no] = personal_wips['step'+str(step_no)+'counterpartminwip']

                    if 'step'+str(step_no)+'counterpartmaxwip' in personal_wips:
                        stepcounterpartmaxwip[step_no] = personal_wips['step'+str(step_no)+'counterpartmaxwip']

                    if 'step'+str(step_no)+'bufferminwip' in personal_wips:
                        stepbufferminwip[step_no] = personal_wips['step'+str(step_no)+'bufferminwip']

                    if 'step'+str(step_no)+'buffermaxwip' in personal_wips:
                        stepbuffermaxwip[step_no] = personal_wips['step'+str(step_no)+'buffermaxwip']

            content.append('<div align="center">')
            content.append('<table class="form">')

            # Step Headings
            content.append('<tr><td></td>')
            for step_document in workflow:
                step_name = step_document['step']
                if step_name:
                    number_of_columns = 0
                    metastate = ""
                    for column in ['maincolumn', 'counterpartcolumn', 'buffercolumn']:
                        if column in step_document:
                            number_of_columns += 1
                            if not metastate:
                                variable_column = step_document[column]
                                state = variable_column['state']
                                if state in self.metastates_list:
                                    metastate = state
                                else:
                                    custom_states = project_document.get('customstates', {})
                                    if state in custom_states:
                                        metastate = custom_states[state]

                    if number_of_columns:
                        if number_of_columns > 1:
                            content.append(f'<th id="{metastate}step" colspan="{number_of_columns}">{step_name}</th>')
                        else:
                            content.append(f'<th id="{metastate}step">{step_name}</th>')

            content.append('</tr>')

            # Column Headings
            number_of_columns = 0
            content.append('<tr><td></td>')
            for step_document in workflow:
                step_name = step_document['step']
                if step_name:
                    if 'maincolumn' in step_document:
                        number_of_columns += 1
                        main_column = step_document['maincolumn']
                        column_name = main_column['name']
                        state = main_column['state']
                        if state in self.metastates_list:
                            metastate = state
                        else:
                            custom_states = project_document.get('customstates', {})
                            metastate = custom_states[state]

                        content.append('<th id="'+metastate+'columnheader">'+column_name+'</th>')

                    if 'counterpartcolumn' in step_document:
                        number_of_columns += 1
                        counterpart_column = step_document['counterpartcolumn']
                        column_name = counterpart_column['name']
                        state = counterpart_column['state']
                        if state in self.metastates_list:
                            metastate = state
                        else:
                            custom_states = project_document.get('customstates', {})
                            metastate = custom_states[state]

                        content.append('<th id="'+metastate+'columnheader">'+column_name+'</th>')

                    if 'buffercolumn' in step_document:
                        number_of_columns += 1
                        buffer_column = step_document['buffercolumn']
                        column_name = buffer_column['name']
                        state = buffer_column['state']
                        if state in self.metastates_list:
                            metastate = state
                        else:
                            custom_states = project_document.get('customstates', {})
                            metastate = custom_states[state]

                        content.append('<th id="'+metastate+'columnheader">'+column_name+'</th>')

            content.append('</tr>')
            content.append('<form action="/members/update_personal_wip" method="post">')

            # Minimum Values
            content.append('<tr><th>Minimum</th>')
            for step_no, step_document in enumerate(workflow):
                step_name = step_document['step']
                if step_name:
                    if 'maincolumn' in step_document:
                        main_column = step_document['maincolumn']
                        state = main_column['state']
                        if state == condensed_column_states[-1]:
                            if 'closedwip' in personal_wips and personal_wips['closedwip']:
                                closedwip = personal_wips['closedwip']
                            else:
                                closedwip = '1 month'

                            content.append('<td rowspan="2" align="center">')
                            content.append(self.create_html_select_block('closedwip', self.potential_closed_periods, default='Please select...', current_value=closedwip))
                            content.append('</td>')
                        else:
                            content.append(f'<td><input type="number" name="step{step_no}mainminwip" size="4" min="-1" value="{stepmainminwip[step_no]}"></td>')

                    if 'counterpartcolumn' in step_document:
                        counterpart_column = step_document['counterpartcolumn']
                        state = counterpart_column['state']
                        if state == condensed_column_states[-1]:
                            if 'closedwip' in personal_wips and personal_wips['closedwip']:
                                closedwip = personal_wips['closedwip']
                            else:
                                closedwip = '1 month'

                            content.append('<td rowspan="2" align="center">')
                            content.append(self.create_html_select_block('closedwip', self.potential_closed_periods, default='Please select...', current_value=closedwip))
                            content.append('</td>')
                        else:
                            content.append(f'<td><input type="number" name="step{step_no}counterpartminwip" size="4" min="-1" value="{stepcounterpartminwip[step_no]}"></td>')

                    if 'buffercolumn' in step_document:
                        content.append(f'<td><input type="number" name="step{step_no}bufferminwip" size="4" min="-1" value="{stepbufferminwip[step_no]}"></td>')

            content.append('</tr>')

            # Maximum Values
            content.append('<tr><th>Maximum</th>')
            for step_no, step_document in enumerate(workflow):
                step_name = step_document['step']
                if step_name:
                    if 'maincolumn' in step_document:
                        main_column = step_document['maincolumn']
                        state = main_column['state']
                        if state == condensed_column_states[-1]:
                            continue
                        else:
                            content.append(f'<td><input type="number" name="step{step_no}mainmaxwip" size="4" min="-1" value="{stepmainmaxwip[step_no]}"></td>')

                    if 'counterpartcolumn' in step_document:
                        counterpart_column = step_document['counterpartcolumn']
                        state = counterpart_column['state']
                        if state == condensed_column_states[-1]:
                            continue
                        else:
                            content.append(f'<td><input type="number" name="step{step_no}counterpartmaxwip" size="4" min="-1" value="{stepcounterpartmaxwip[step_no]}"></td>')

                    if 'buffercolumn' in step_document:
                        content.append(f'<td><input type="number" name="step{step_no}buffermaxwip" size="4" min="-1" value="{stepbuffermaxwip[step_no]}"></td>')

            content.append('</tr>')

            content.append(f'<tr><td colspan="{number_of_columns+1}"><hr></td></tr>')

            content.append('<tr><th>Step&nbsp;Alternative<br>Shared&nbsp;Maximum</th>')
            for step_no, step_document in enumerate(workflow):
                if step_document['step']:
                    number_of_columns = 0
                    for column in ['maincolumn', 'counterpartcolumn', 'buffercolumn']:
                        if column in step_document:
                            number_of_columns += 1

                    if number_of_columns:
                        if number_of_columns > 1:
                            content.append(f'<td colspan="{number_of_columns}" align="center">')
                            content.append(f'<input type="number" name="step{step_no}sharedmaxwip" size="4" min="-1" value="{stepsharedmaxwip[step_no]}">')
                            content.append('</td>')
                        else:
                            content.append('<td></td>')

            content.append('</tr>')
            content.append(f'<tr><td colspan="{number_of_columns+1}" align="center"><input name="enforcewiplimits" type="checkbox"')
            if enforce_wip_limits:
                content.append(' checked')

            content.append('> Enforce WIP Limits</td></tr>')
            content.append(f'<tr><td colspan="{number_of_columns+1}" align="center"><input type="submit" value="Update"></form><input type="button" value="Cancel" onclick="window.location=\'/kanban\'" /></td></tr>')
            content.append('</table>')
            content.append('</div>')
            break

        content.append(Kanbanara.footer(self))
        return "".join(content)

    @cherrypy.expose
    def update_absence(self, absence_start_date, absence_end_date):
        Kanbanara.check_authentication(f'/{self.component}')
        session_id = Kanbanara.cookie_handling(self)
        session_document = self.sessions_collection.find_one({"session_id": session_id})
        member_document = Kanbanara.get_member_document(self, session_document)
        for variable, attribute in [(absence_start_date, 'absencestartdate'),
                                    (absence_end_date, 'absenceenddate')]:
            if variable:
                member_document[attribute] = self.dashed_date_to_datetime_object(variable)
            elif attribute in member_document:
                del member_document[attribute]

        self.members_collection.save(member_document)
        self.save_member_as_json(member_document)
        raise cherrypy.HTTPRedirect("/kanban/index", 302)

    @cherrypy.expose
    def update_personal_wip(self,
                            step0mainminwip="0", step0counterpartminwip="0", step0bufferminwip="0",
                            step1mainminwip="0", step1counterpartminwip="0", step1bufferminwip="0",
                            step2mainminwip="0", step2counterpartminwip="0", step2bufferminwip="0",
                            step3mainminwip="0", step3counterpartminwip="0", step3bufferminwip="0",
                            step4mainminwip="0", step4counterpartminwip="0", step4bufferminwip="0",
                            step5mainminwip="0", step5counterpartminwip="0", step5bufferminwip="0",
                            step6mainminwip="0", step6counterpartminwip="0", step6bufferminwip="0",
                            step7mainminwip="0", step7counterpartminwip="0", step7bufferminwip="0",
                            step8mainminwip="0", step8counterpartminwip="0", step8bufferminwip="0",
                            step9mainminwip="0", step9counterpartminwip="0", step9bufferminwip="0",
                            step10mainminwip="0", step10counterpartminwip="0",
                            step10bufferminwip="0", step11mainminwip="0",
                            step11counterpartminwip="0", step11bufferminwip="0",
                            step0mainmaxwip="-1", step0counterpartmaxwip="-1",
                            step0buffermaxwip="-1", step1mainmaxwip="-1",
                            step1counterpartmaxwip="-1", step1buffermaxwip="-1",
                            step2mainmaxwip="-1", step2counterpartmaxwip="-1",
                            step2buffermaxwip="-1", step3mainmaxwip="-1",
                            step3counterpartmaxwip="-1", step3buffermaxwip="-1",
                            step4mainmaxwip="-1", step4counterpartmaxwip="-1",
                            step4buffermaxwip="-1", step5mainmaxwip="-1",
                            step5counterpartmaxwip="-1", step5buffermaxwip="-1",
                            step6mainmaxwip="-1", step6counterpartmaxwip="-1",
                            step6buffermaxwip="-1", step7mainmaxwip="-1",
                            step7counterpartmaxwip="-1", step7buffermaxwip="-1",
                            step8mainmaxwip="-1", step8counterpartmaxwip="-1",
                            step8buffermaxwip="-1", step9mainmaxwip="-1",
                            step9counterpartmaxwip="-1", step9buffermaxwip="-1",
                            step10mainmaxwip="-1", step10counterpartmaxwip="-1",
                            step10buffermaxwip="-1", step11mainmaxwip="-1",
                            step11counterpartmaxwip="-1", step11buffermaxwip="-1",
                            closedwip='1 month', step0sharedmaxwip="0", step1sharedmaxwip="0",
                            step2sharedmaxwip="0", step3sharedmaxwip="0", step4sharedmaxwip="0",
                            step5sharedmaxwip="0", step6sharedmaxwip="0", step7sharedmaxwip="0",
                            step8sharedmaxwip="0", step9sharedmaxwip="0", step10sharedmaxwip="0",
                            step11sharedmaxwip="0", enforcewiplimits=False):
        """Updates a user's personal WIP limits"""
        Kanbanara.check_authentication(f'/{self.component}/personal_work_in_progress_limits')
        session_id = Kanbanara.cookie_handling(self)
        session_document = self.sessions_collection.find_one({"session_id": session_id})
        member_document = Kanbanara.get_member_document(self, session_document)
        project = member_document.get('project', '')
        if project:
            if 'project_wips' in member_document:
                project_wips = member_document['project_wips']
            else:
                project_wips = {}

            personal_wips = {}
            # Step through the local variables passed into the function
            # returning each as a tuple of its name as a string together with its value
            for (var_as_string, value) in locals().items():
                if var_as_string.startswith('step') and var_as_string.endswith('wip'):
                    if ((var_as_string.endswith('mainminwip') or
                         var_as_string.endswith('counterpartminwip') or
                         var_as_string.endswith('bufferminwip')
                        ) and value != '0'):
                        personal_wips[var_as_string] = int(value)
                    elif ((var_as_string.endswith('mainmaxwip') or
                           var_as_string.endswith('counterpartmaxwip') or
                           var_as_string.endswith('buffermaxwip')
                          ) and value != '-1'):
                        personal_wips[var_as_string] = int(value)
                    elif var_as_string.endswith('sharedmaxwip') and value != '0':
                        personal_wips[var_as_string] = int(value)

            personal_wips['closedwip'] = closedwip
            personal_wips['enforcewiplimits'] = enforcewiplimits
            project_wips[project] = personal_wips
            member_document['project_wips'] = project_wips
            self.members_collection.save(member_document)
            self.save_member_as_json(member_document)

        raise cherrypy.HTTPRedirect("/kanban/index", 302)

    def __init__(self):
        """Initialisation"""
        self.component = 'filters'

        Kanbanara.__init__(self)

        self.current_dir = os.path.dirname(os.path.abspath(__file__))

        logging.basicConfig(level=logging.DEBUG,
                            format='%(asctime)s - %(levelname)s - %(message)s',
                            filename=os.path.join(self.current_dir, '..', 'logs', 'kanbanara.log'),
                            filemode='w'
                           )

        # Initialisation Settings
        (self.mongodb_host, self.mongodb_port, self.mongodb_username, self.mongodb_password,
         self.mongodb_bindir) = Kanbanara.read_mongodb_ini_file(self)

        # Connect to MongoDB on given host and port
        if self.mongodb_username and self.mongodb_password:
            modified_username = urllib.parse.quote_plus(self.mongodb_username)
            modified_password = urllib.parse.quote_plus(self.mongodb_password)
            connection = MongoClient('mongodb://' + modified_username + ':' + modified_password +
                                     '@' + self.mongodb_host + ':' + str(self.mongodb_port))
        else:
            connection = MongoClient(self.mongodb_host, self.mongodb_port)

        # Connect to 'kanbanara' database, creating if not already exists
        kanbanara_db = connection['kanbanara']

        # Connect to 'projects' collection
        self.projects_collection = kanbanara_db['projects']
        for attribute in ['project']:
            self.projects_collection.create_index(attribute, unique=True, background=True)

        # Connect to 'sessions' collection
        self.sessions_collection = kanbanara_db['sessions']
        for attribute in ['session_id']:
            self.sessions_collection.create_index(attribute, unique=True, background=True)

        for attribute in ['lastaccess']:
            self.sessions_collection.create_index(attribute, unique=False, background=True)

        # Connect to 'members' collection
        self.members_collection = kanbanara_db['members']
        for attribute in ['username']:
            # TODO - username attribute should be unique but get error when unique=true set
            self.members_collection.create_index(attribute, unique=False, background=True)

        # Connect to 'cards' collection
        self.cards_collection = kanbanara_db['cards']

        self.recent_activities = []

    @cherrypy.expose
    def avatar(self, uploaded_file=""):
        """comment"""
        username = Kanbanara.check_authentication(f'/{self.component}/avatar')
        session_id = Kanbanara.cookie_handling(self)
        session_document = self.sessions_collection.find_one({"session_id": session_id})
        member_document = Kanbanara.get_member_document(self, session_document)
        existing_avatar = self.get_teammember_avatar(username)
        content = []
        content.append(Kanbanara.header(self, 'avatar', "Avatar"))
        content.append(Kanbanara.filter_bar(self, 'avatar'))
        content.append(Kanbanara.menubar(self))
        content.append('<div align="center">')
        content.append(self.insert_page_title_and_online_help(session_document, 'avatar', 'Avatar'))
        if uploaded_file == '' or not uploaded_file.filename:
            content.append('<div align="center"><table border="0"><tr><td>')

            if existing_avatar:
                content.append('<img src="/images/avatars/'+existing_avatar+'">')
            else:
                content.append('<img src="/images/avatars/default.jpg">')

            content.append('</td><td><table border="0">')
            content.append('<form action="/members/avatar" method="post" enctype="multipart/form-data">')
            content.append('<tr><td align="right"><p>Avatar</p></td><td><p><input type="file" name="uploaded_file">')
            content.append('</p></td></tr><tr><td align="right" valign="top"><input type="submit" value="Upload Avatar"></form></td><td valign="top"><form><input type="button" name="cancel" value="Cancel" onClick="javascript:history.back();"></form></td></tr></table>')
            content.append('</td></tr></table></div>')
        else:
            filename = uploaded_file.filename
            (_, ext) = os.path.splitext(filename)
            with open(os.path.join(self.current_dir, 'images', 'avatars', username+ext),
                      'wb') as handle:
                while True:
                    chunk = uploaded_file.file.read(4096)
                    if not chunk:
                        break
                    else:
                        handle.write(chunk)

            if existing_avatar and existing_avatar != username+ext:
                os.remove(os.path.join(self.current_dir, 'images', 'avatars', existing_avatar))

            member_document['avatar'] = username+ext
            self.members_collection.save(member_document)
            raise cherrypy.HTTPRedirect('/kanban', 302)

        content.append('</div>')
        content.append(Kanbanara.footer(self))
        return "".join(content)

    @cherrypy.expose
    def index(self):
        """Redirects you to the kanban board"""
        raise cherrypy.HTTPRedirect("/kanban", 302)

    @cherrypy.expose
    def member_as_json(self):
        """ Displays a member in JSON format """
        page = "member_as_json"
        Kanbanara.check_authentication(f'/{self.component}/'+page)
        session_id = Kanbanara.cookie_handling(self)
        session_document = self.sessions_collection.find_one({"session_id": session_id})
        member_document = Kanbanara.get_member_document(self, session_document)
        if member_document:
            content = []
            title = "Member As JSON"
            content.append(Kanbanara.header(self, page, title))
            content.append(Kanbanara.filter_bar(self, page))
            content.append(Kanbanara.menubar(self))
            content.append(self.insert_page_title_and_online_help(session_document, page, title))
            content.append('<p class="json">')
            content.append(self.dictionary_as_json('html', member_document, 0))
            content.append('</p>')
            content.append(Kanbanara.footer(self))
            return "".join(content)
        else:
            raise cherrypy.HTTPRedirect("/kanban/index", 302)

CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
conf = {'/': {'tools.staticdir.root':   CURRENT_DIR,
              'tools.sessions.on':      True,
              'tools.sessions.locking': 'explicit'
             }}
for directory in ['css', 'images']:
    if os.path.exists(CURRENT_DIR+os.sep+directory):
        conf['/'+directory] = {'tools.staticdir.on':  True,
                               'tools.staticdir.dir': directory}

cherrypy.tree.mount(Members(), '/members', config=conf)
