# w.c.s. - web application for online forms
# Copyright (C) 2005-2015  Entr'ouvert
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <http://www.gnu.org/licenses/>.

import time

from django.utils.safestring import mark_safe

from quixote import get_publisher, get_request, get_response, get_session, redirect
from quixote.directory import Directory
from quixote.html import TemplateIO, htmltext

from ..qommon import _
from ..qommon.backoffice.menu import html_top
from ..qommon.form import *
from ..qommon import errors, misc

from wcs.formdata import FormData
from wcs.formdef import FormDef
from wcs.categories import Category
from wcs.forms.root import FormPage as PublicFormFillPage


class RemoveDraftDirectory(Directory):
    def __init__(self, parent_directory):
        self.parent_directory = parent_directory
        self.formdef = parent_directory.formdef

    def _q_lookup(self, component):
        try:
            formdata = self.formdef.data_class().get(component)
        except KeyError:
            raise errors.TraversalError()
        if not formdata.is_draft():
            raise errors.AccessForbiddenError()
        if not formdata.backoffice_submission:
            raise errors.AccessForbiddenError()

        self.parent_directory.check_role()
        if self.parent_directory.edit_mode:
            raise errors.AccessForbiddenError()

        self.parent_directory.html_top(title=_('Remove'))

        form = Form(enctype='multipart/form-data')
        form.widgets.append(HtmlWidget('<p>%s</p>' % _(
                        'You are about to irrevocably delete this form.')))
        form.add_submit('delete', _('Delete'))
        form.add_submit('cancel', _('Cancel'))
        if form.get_widget('cancel').parse():
            return redirect('../..')
        if not form.is_submitted() or form.has_errors():
            r = TemplateIO(html=True)
            r += htmltext('<h2>%s</h2>') % (_('Deleting Form'))
            r += form.render()
            return r.getvalue()

        if formdata.tracking_code:
            get_publisher().tracking_code_class.remove_object(formdata.tracking_code)
        formdata.remove_self()
        return redirect('../..')


class FormFillPage(PublicFormFillPage):
    _q_exports = ['', 'tempfile', 'autosave', 'code',
            ('remove', 'remove_draft'), 'live']

    filling_templates = ['wcs/formdata_filling.html']
    validation_templates = ['wcs/formdata_validation.html']
    steps_templates = ['wcs/formdata_steps.html']

    def __init__(self, *args, **kwargs):
        super(FormFillPage, self).__init__(*args, **kwargs)
        self.selected_submission_channel = None
        self.remove_draft = RemoveDraftDirectory(self)

    def _q_index(self, *args, **kwargs):
        # if NameID, return URL or submission channel are in query string,
        # create a new draft with these parameters, and redirect to it
        submission_channel = get_request().form.get('channel')
        name_id = get_request().form.get('NameID')
        return_url = get_request().form.get('ReturnURL')
        if name_id or submission_channel or return_url:
            formdata = self.formdef.data_class()()
            formdata.data = {}
            formdata.backoffice_submission = True
            formdata.submission_channel = submission_channel or ''
            formdata.submission_context = {'agent_id': get_request().user.id}
            formdata.status = 'draft'
            formdata.receipt_time = time.localtime()
            if name_id:
                users = list(get_publisher().user_class.get_users_with_name_identifier(name_id))
                if users:
                    formdata.user_id = users[0].id
            if return_url:
                formdata.submission_context['return_url'] = return_url
            formdata.store()
            self.set_tracking_code(formdata)
            return redirect('%s' % formdata.id)

        self.selected_submission_channel = get_request().form.get('submission_channel') or ''
        return super(FormFillPage, self)._q_index(*args, **kwargs)

    def html_top(self, *args, **kwargs):
        return html_top('submission', *args, **kwargs)

    def check_authentication_context(self):
        pass

    def check_role(self):
        if self.edit_mode:
            return True
        if not self.formdef.backoffice_submission_roles:
            raise errors.AccessUnauthorizedError()
        for role in get_request().user.get_roles():
            if role in self.formdef.backoffice_submission_roles:
                break
        else:
            raise errors.AccessUnauthorizedError()

    def get_sidebar(self, data):
        r = TemplateIO(html=True)

        formdata = None
        if self.edit_mode:
            formdata = self.edited_data
        else:
            draft_formdata_id = data.get('draft_formdata_id')
            if draft_formdata_id:
                try:
                    formdata = self.formdef.data_class().get(draft_formdata_id)
                except KeyError:  # it may not exist
                    pass

        if self.formdef.enable_tracking_codes and not self.edit_mode:
            r += htmltext('<h3>%s</h3>') % _('Tracking Code')
            if formdata and formdata.tracking_code:
                r += htmltext('<p>%s</p>') % formdata.tracking_code
            else:
                r += htmltext('<p>-</p>')

        welco_url = get_publisher().get_site_option('welco_url', 'options')
        if formdata and formdata.submission_context:
            from .management import FormBackOfficeStatusPage
            r += FormBackOfficeStatusPage(self.formdef, formdata).get_extra_context_bar()
        elif not welco_url and not self.edit_mode:
            r += htmltext('<div class="submit-channel-selection" style="display: none;">')
            r += htmltext('<h3>%s</h3>') % _('Channel')
            r += htmltext('<select>')
            for channel_key, channel_label in [('', '-')] + list(FormData.get_submission_channels().items()):
                selected = ''
                if self.selected_submission_channel == channel_key:
                    selected = 'selected="selected"'
                r += htmltext('<option value="%s" %s>' % (channel_key, selected))
                r += htmltext('%s</option>') % channel_label
            r += htmltext('</select>')
            r += htmltext('</div>')

        return r.getvalue()

    def create_view_form(self, *args, **kwargs):
        form = super(FormFillPage, self).create_view_form(*args, **kwargs)
        welco_url = get_publisher().get_site_option('welco_url', 'options')
        if not welco_url:
            form.add_hidden('submission_channel', self.selected_submission_channel)
        return form

    def create_form(self, *args, **kwargs):
        form = super(FormFillPage, self).create_form(*args, **kwargs)
        form.attrs['data-live-url'] = self.formdef.get_backoffice_submission_url() + 'live'
        welco_url = get_publisher().get_site_option('welco_url', 'options')
        if not welco_url:
            form.add_hidden('submission_channel', self.selected_submission_channel)
        return form

    def form_side(self, step_no, page, data=None, magictoken=None):
        r = TemplateIO(html=True)
        get_response().filter['sidebar'] = self.get_sidebar(data)
        r += htmltext('<div id="appbar">')
        r += htmltext('<h2>%s</h2>') % self.formdef.name
        if not self.edit_mode:
            draft_formdata_id = data.get('draft_formdata_id')
            if draft_formdata_id:
                r += htmltext('<a rel="popup" href="remove/%s">%s</a>') % (draft_formdata_id, _('Delete this form'))
        r += htmltext('</div>')
        r += htmltext('<div id="side">')
        r += self.step(step_no, page)
        r += htmltext('</div> <!-- #side -->')
        return mark_safe(str(r.getvalue()))

    def submitted(self, form, *args):
        filled = self.get_current_draft() or self.formdef.data_class()()
        if filled.id and filled.status != 'draft':
            get_session().message = ('error', _('This form has already been submitted.'))
            return redirect(get_publisher().get_backoffice_url() + '/submission/')
        filled.just_created()
        filled.data = self.formdef.get_data(form)
        filled.backoffice_submission = True
        if not filled.submission_context:
            filled.submission_context = {}
        if self.selected_submission_channel:
            filled.submission_channel = self.selected_submission_channel
        filled.submission_context['agent_id'] = get_request().user.id
        filled.store()

        self.set_tracking_code(filled)
        get_session().remove_magictoken(get_request().form.get('magictoken'))
        self.clean_submission_context()

        url = filled.perform_workflow()
        if url:
            pass # always redirect to an URL the workflow returned
        elif not self.formdef.is_of_concern_for_user(self.user, filled):
            # if the agent is not allowed to see the submitted formdef,
            # redirect to the defined return URL or to the submission
            # homepage
            if filled.submission_context.get('return_url'):
                url = filled.submission_context['return_url']
            else:
                url = get_publisher().get_backoffice_url() + '/submission/'
        else:
            url = filled.get_url(backoffice=True)

        return redirect(url)

    def cancelled(self):
        url = get_publisher().get_backoffice_url() + '/submission/'
        formdata = self.get_current_draft() or self.formdef.data_class()()
        if formdata.submission_context and formdata.submission_context.get('return_url'):
            url = formdata.submission_context.get('return_url')
        if formdata.id:
            formdata.remove_self()
        return redirect(url)

    def save_draft(self, data, page_no):
        formdata = super(FormFillPage, self).save_draft(data, page_no)
        formdata.backoffice_submission = True
        if not formdata.submission_context:
            formdata.submission_context = {}
        formdata.submission_context['agent_id'] = get_request().user.id
        formdata.store()
        return formdata


class SubmissionDirectory(Directory):
    _q_exports = ['', 'count']

    def is_accessible(self, user):
        if not user.can_go_in_backoffice():
            return False
        # check user has at least one role set for backoffice submission
        for role_id in (user.roles or []):
            ids = FormDef.get_ids_with_indexed_value('backoffice_submission_roles', role_id)
            if ids:
                return True
        return False

    def get_submittable_formdefs(self):
        user = get_request().user

        list_forms = []
        for formdef in FormDef.select(order_by='name', ignore_errors=True):
            if formdef.is_disabled():
                continue
            if not formdef.backoffice_submission_roles:
                continue
            for role in user.get_roles():
                if role in formdef.backoffice_submission_roles:
                    break
            else:
                continue
            list_forms.append(formdef)

        return list_forms

    def _q_index(self):
        get_response().breadcrumb.append(('submission/', _('Submission')))
        html_top('submission', _('Submission'))

        list_forms = self.get_submittable_formdefs()
        cats = Category.select()
        Category.sort_by_position(cats)
        for cat in cats:
            cat.formdefs = [x for x in list_forms if str(x.category_id) == str(cat.id)]
        misc_cat = Category(name=_('Misc'))
        misc_cat.formdefs = [x for x in list_forms if not x.category]
        cats.append(misc_cat)

        welco_url = get_publisher().get_site_option('welco_url', 'options')

        r = TemplateIO(html=True)
        r += get_session().display_message()
        modes = ['empty', 'create', 'existing']
        if welco_url:
            modes.remove('create')
        empty = True
        for mode in modes:
            list_content = TemplateIO()
            for cat in cats:
                if not cat.formdefs:
                    continue
                list_content += self.form_list(cat.formdefs, title=cat.name, mode=mode)
            if not list_content.getvalue().strip():
                continue
            empty = False
            r += htmltext('<h2>%s</h2>') % {
                    'create': _('New submission'),
                    'existing': _('Running submission'),
                    'empty': _('Submission to complete'),
                    }.get(mode)
            r += htmltext('<ul class="biglist">')
            r += htmltext(list_content.getvalue())
            r += htmltext('</ul>')

        if empty and welco_url:
            return redirect(welco_url)

        return r.getvalue()

    def form_list(self, formdefs, title=None, mode='create'):
        r = TemplateIO(html=True)
        if mode != 'create':
            skip = True
            for formdef in formdefs:
                if not hasattr(formdef, '_formdatas'):
                    data_class = formdef.data_class()
                    formdata_ids = data_class.get_ids_with_indexed_value('status', 'draft')
                    formdef._formdatas = [x for x in data_class.get_ids(formdata_ids)
                            if x.backoffice_submission is True]
                skip &= not(bool(formdef._formdatas))
            if skip:
                return

        first = True

        for formdef in formdefs:
            if mode != 'create':
                formdatas = formdef._formdatas[:]
                if mode == 'empty':
                    formdatas = [x for x in formdatas if x.has_empty_data()]
                elif mode == 'existing':
                    formdatas = [x for x in formdatas if not x.has_empty_data()]
                if not formdatas:
                    continue

            if first and title:
                r += htmltext('<li><h3>%s</h3></li>') % title
                first = False

            r += htmltext('<li>')
            if mode == 'create':
                r += htmltext('<strong class="label"><a href="%s/">%s</a></strong>') % (
                        formdef.url_name, formdef.name)
            else:
                r += htmltext('<strong class="label"><a class="fake">%s</a></strong>') % formdef.name
            r += htmltext('</li>')
            if mode == 'create':
                continue
            for formdata in formdatas:
                r += htmltext('<li class="smallitem">')
                label = ''
                if formdata.submission_channel:
                    label = '%s ' % formdata.get_submission_channel_label()
                label += _('#%(id)s, %(time)s') % {'id': formdata.id,
                        'time': misc.localstrftime(formdata.receipt_time)}
                if formdata.submission_context and 'agent_id' in formdata.submission_context:
                    agent_user = get_publisher().user_class.get(
                            formdata.submission_context['agent_id'], ignore_errors=True)
                    if agent_user:
                        label += ' (%s)' % agent_user.display_name
                r += htmltext('<a href="%s/%s">%s</a>') % (
                        formdef.url_name, formdata.id, label)
                r += htmltext('</li>')

        return r.getvalue()

    def count(self):
        formdefs = self.get_submittable_formdefs()
        count = 0
        mode = get_request().form.get('mode')
        for formdef in formdefs:
            if not hasattr(formdef, '_formdatas'):
                data_class = formdef.data_class()
                formdata_ids = data_class.get_ids_with_indexed_value('status', 'draft')
                formdatas = [x for x in data_class.get_ids(formdata_ids)
                        if x.backoffice_submission is True]
                if mode == 'empty':
                    formdatas = [x for x in formdatas if x.has_empty_data()]
                elif mode == 'existing':
                    formdatas = [x for x in formdatas if not x.has_empty_data()]
                count += len(formdatas)
        return misc.json_response({'count': count})

    def _q_lookup(self, component):
        get_response().breadcrumb.append(('submission/', _('Submission')))
        return FormFillPage(component)
