import copy
import operator
import os
import re
import time
from collections import OrderedDict
from datetime import datetime

from axes.utils import reset
from django.conf import settings
from django.contrib.auth import REDIRECT_FIELD_NAME, login, logout
from django.contrib.auth.decorators import login_required
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
from django.contrib.auth.models import User
from django.contrib.auth.views import password_change
from django.core.exceptions import ObjectDoesNotExist
from django.core.files.storage import FileSystemStorage
from django.core.urlresolvers import reverse_lazy, reverse
from django.db import IntegrityError
from django.db.models import Count
from django.db.models import Q
from django.forms import formset_factory
from django.http import HttpResponse
from django.http import HttpResponseRedirect
from django.shortcuts import render,redirect, resolve_url, render_to_response
from django.template import RequestContext
from django.utils import timezone
from django.utils.decorators import method_decorator
from django.views import generic
from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_protect
from django.views.decorators.debug import sensitive_post_parameters
from django.views.generic import FormView, RedirectView
from formtools.wizard.views import SessionWizardView
from ipware.ip import get_ip

try:
    from StringIO import StringIO
except ImportError:
    from io import StringIO
try:
    import urlparse
except ImportError:
    from urllib import parse as urlparse # python3 support
# import the logging library
import logging
# Get an instance of a logger
logger = logging.getLogger(__name__)
###Local classes
from .models import Questionnaire, Choice, TestResult, SubjectQuestionnaire,Category,SubjectVisit,Question
from .forms import AxesCaptchaForm, AnswerForm, TestResultDeleteForm, BaseQuestionFormSet, replaceTwinNames
from .tables import FilteredSingleTableView, TestResultTable,SubjectQuestionnaireTable,SubjectVisitTable
from .filters import TestResultFilter,SubjectQuestionnaireFilter,SubjectVisitFilter



## Login
class LoginView(FormView):
    """
    Provides the ability to login as a user with a username and password
    """
    template_name = 'questionnaires/index.html'
    success_url = '/questionnaires'
    form_class = AuthenticationForm
    redirect_field_name = REDIRECT_FIELD_NAME

    @method_decorator(sensitive_post_parameters('password'))
    @method_decorator(csrf_protect)
    @method_decorator(never_cache)
    def dispatch(self, request, *args, **kwargs):
        # Sets a test cookie to make sure the user has cookies enabled
        request.session.set_test_cookie()

        return super(LoginView, self).dispatch(request, *args, **kwargs)

    def form_valid(self, form):
        user = form.get_user()
        if user is not None:
            msg = 'User: %s' % user
            if user.is_active:
                login(self.request, user)
                msg = '% has logged in' % msg
                logger.info(msg)
            else:
                # Return a 'disabled account' error message
                form.add_error = 'Your account has been disabled. Please contact admin.'
                msg = '% has disabled account' % msg
                logger.warning(msg)

        else:
            # Return an 'invalid login' error message.
            form.add_error = 'Login credentials are invalid. Please try again'
            msg = 'Login failed with invalid credentials'
            logger.error(msg)

        # If the test cookie worked, go ahead and
        # delete it since its no longer needed
        self.check_and_delete_test_cookie()
        return super(LoginView, self).form_valid(form)

    def form_invalid(self, form):
        """
        The user has provided invalid credentials (this was checked in AuthenticationForm.is_valid()). So now we
        set the test cookie again and re-render the form with errors.
        """
        self.set_test_cookie()
        return super(LoginView, self).form_invalid(form)

    def set_test_cookie(self):
        self.request.session.set_test_cookie()

    def check_and_delete_test_cookie(self):
        if self.request.session.test_cookie_worked():
            self.request.session.delete_test_cookie()
            return True
        return False

    def get_success_url(self):
        if self.success_url:
            redirect_to = self.success_url
        else:
            redirect_to = self.request.POST.get(
                self.redirect_field_name,
                self.request.GET.get(self.redirect_field_name, ''))

        netloc = urlparse.urlparse(redirect_to)[1]
        if not redirect_to:
            redirect_to = resolve_url(settings.LOGIN_REDIRECT_URL)
        # Security check -- don't allow redirection to a different host.
        elif netloc and netloc != self.request.get_host():
            redirect_to = resolve_url(settings.LOGIN_REDIRECT_URL)
        return redirect_to
        #return reverse(redirect_to)


class LogoutView(RedirectView):
    """
    Provides users the ability to logout
    """
    #template_name = 'questionnaires/index.html'
    successurl = '/questionnaires'

    def get(self, request, *args, **kwargs):
        logout(request)
        return redirect(self.successurl)

def locked_out(request):
    if request.POST:
        form = AxesCaptchaForm(request.POST)
        if form.is_valid():
            ip = get_ip(request)
            if ip is not None:
                msg = "User locked out with IP address=%s" % ip
                logger.warning(msg)
                reset(ip=ip)

            return HttpResponseRedirect(reverse_lazy('questionnaires:index'))
    else:
        form = AxesCaptchaForm()

    return render(request,'questionnaires/locked.html', dict(form=form))

def change_password(request):
    template_response = password_change(request)
    # Do something with `template_response`
    return template_response

def csrf_failure(request, reason=""):
    ctx = {'title': 'CSRF Failure', 'message': 'Your browser does not accept cookies and this can be a problem in ensuring a secure connection.'}
    template_name= 'admin/csrf_failure.html'
    return render_to_response(template_name, ctx)
###########################################################################################

class IndexView(generic.ListView):
    template_name = 'questionnaires/index.html'
    context_object_name = 'questionnaire_list'
    raise_exception = True

    def get_queryset(self):
        return Questionnaire.objects.order_by('order').order_by('code')

    def get_context_data(self, **kwargs):
        context = super(IndexView, self).get_context_data(**kwargs)
        user = self.request.user
        rlist = []
        qlist = {}
        cat1 = None
        if user is not None and user.is_active:
            # set defaults
            cat1 = Category.objects.get(name='Wave 1')  # Wave 1
            catlist = [c for c in Category.objects.filter(name__icontains='all')]  # include ALL
            qlist = self.get_queryset().distinct('code')
            #user_results = user.subjectquestionnaire_set.all() #SubjectQuestionnaire.objects.filter(subject=user)
            rlist = user.subjectquestionnaire_set.distinct('questionnaire')

            if not user.is_superuser:
                visit = SubjectVisit.objects.filter(subject=user)
                #If missing, create visit and set to first category
                if len(visit)==0:
                    visit = SubjectVisit(subject=user, category=cat1, date_visit=datetime.now())
                    visit.save()
                else:
                    visit = visit[0]
                cat1 = visit.category
                catlist.append(cat1)
                usergrouplist = user.groups.values_list('name')

                rlist = rlist.filter(questionnaire__category__in=catlist)
                qlist = qlist.filter(active=True).filter(category__in=catlist).filter(group__name__in=usergrouplist)
            #exclude completed
            for r in rlist:
                qlist = qlist.exclude(pk=r.questionnaire.id)
        #Reorder list by 'order' to allow manual override
        qlist = sorted(qlist, key=operator.attrgetter('order'))
        rlist = sorted(rlist, key=operator.attrgetter('date_stored'), reverse=True)
        # Reset session conditional data
        if self.request.session.get('conditionals'):
            #print("DEBUG: FORM_INITIAL: Reset conditionals")
            self.request.session['conditionals'] = {}


        context['questionnaire_list'] = qlist
        context['result_list']= rlist
        context['visit'] = cat1

        return context


# Questionnaire Intro page
class DetailView(LoginRequiredMixin, generic.DetailView):
    model = Questionnaire
    template_name = 'questionnaires/intro.html'

    def get_context_data(self, **kwargs):
        context = super(DetailView, self).get_context_data(**kwargs)
        context['now'] = timezone.now()
        return context

################REPORT TABLES ##########################################
##Reports
class SubjectReportView(LoginRequiredMixin, FilteredSingleTableView):
    template_name = 'questionnaires/results_summary.html'
    model = SubjectQuestionnaire
    table_class = SubjectQuestionnaireTable
    filter_class = SubjectQuestionnaireFilter
    raise_exception = True

    def get_queryset(self):
        return SubjectQuestionnaire.objects.order_by('questionnaire__code')

    def get_context_data(self, **kwargs):
        context = super(SubjectReportView, self).get_context_data(**kwargs)
        context['title'] = "Subject Reports"
        return context


##Results
class ResultFilterView(LoginRequiredMixin, FilteredSingleTableView):
    template_name = 'questionnaires/results_summary.html'
    model = TestResult
    table_class = TestResultTable
    filter_class = TestResultFilter
    raise_exception = True

    def get_queryset(self):
        return TestResult.objects.order_by('test_questionnaire__code')

    def get_context_data(self, **kwargs):
        context = super(ResultFilterView, self).get_context_data(**kwargs)
        context['title'] = "Questionnaire Results"
        return context

##Visits
class VisitView(LoginRequiredMixin, FilteredSingleTableView):
    template_name = 'questionnaires/results_summary.html'
    model = SubjectVisit
    table_class = SubjectVisitTable
    filter_class = SubjectVisitFilter
    raise_exception = True

    def get_context_data(self, **kwargs):
        context = super(VisitView, self).get_context_data(**kwargs)
        context['title'] = "Subject Visits"
        return context

## ACTIONS ##

def download_report(request, *args, **kwargs):

    # Get subject id
    sid = kwargs.get('subjectid')
    if (sid):
        qs = SubjectQuestionnaire.objects.get(pk=sid)
    else:
        raise Exception('E100: Download report cannot find id')
    filename = "QTAB_report_%s.csv" % sid
    # Create the HttpResponse object with the appropriate CSV header.
    import csv
    from django.utils.encoding import smart_str
    response = HttpResponse(content_type='text/csv')
    response['Content-Disposition'] = 'attachment; filename=%s' % filename
    response.write(u'\ufeff'.encode('utf8'))  # BOM (optional...Excel needs it to open UTF-8 file properly)
    writer = csv.writer(response, csv.excel)
    writer.writerow([
        smart_str(u"ID"),
        smart_str(u"Date"),
        smart_str(u"Questionnaire"),
        smart_str(u"Code"),
        smart_str(u"Description"),
        smart_str(u"Visit"),
        smart_str(u"Total Qns"),
    ])
    id = qs.subject.pk
    visit = "NA"
    if hasattr(qs.subject, 'subjectvisit'):
        if qs.subject.subjectvisit.xnatid:
            id = qs.subject.subjectvisit.xnatid
        visit = qs.subject.subjectvisit.category.name

    writer.writerow([
        smart_str(id),
        smart_str(qs.date_stored),
        smart_str(qs.questionnaire.title),
        smart_str(qs.questionnaire.code),
        smart_str(qs.questionnaire.description),
        smart_str(visit),
        smart_str(qs.questionnaire.question_set.count()),
    ])
    #Output question results
    writer.writerow([smart_str(u"**Results**")])
    writer.writerow([
            smart_str(u"Question"),
            smart_str(u"Options"),
            smart_str(u"Option values"),
            smart_str(u"Choice"),
            smart_str(u"Value"),
            smart_str(u"Text"),
            smart_str(u"Tested"),
     ])
    testresults = TestResult.objects.filter(test_token=qs.session_token)
    gender_re = re.compile(r'\[(FE)?MALE\sTwin\]')
    for qresult in testresults:
        testee = qresult.testee
        choicetext = ""
        choicevalue = ""
        freetext = ""
        choicetexts = ""
        choicevalues = ""
        if qresult.test_result_choice:
            choices = qresult.test_result_question.choice_set.all()
            choicetexts = [replaceTwinNames(qs.subject, choice.choice_text) for choice in choices]
            choicevalues = [choice.choice_value for choice in choices]
            choicetext = replaceTwinNames(qs.subject,qresult.test_result_choice.choice_text)
            choicevalue = qresult.test_result_choice.choice_value
        elif qresult.test_result_text:
            freetext = qresult.test_result_text
        elif qresult.test_result_date:
            freetext = qresult.test_result_date
        qtext = replaceTwinNames(qresult.testee,qresult.test_result_question.question_text)
        if bool(gender_re.search(qtext)):
            qtext = gender_re.sub(testee.first_name,qtext)
        writer.writerow([
            smart_str(qtext),
            smart_str(choicetexts),
            smart_str(choicevalues),
            smart_str(choicetext),
            smart_str(choicevalue),
            smart_str(freetext),
            smart_str(testee),
        ])
        #Output any custom data as table
        if qs.questionnaire.type =='custom':
            if qs.questionnaire.code == 'Wav1-P-08b':
                t = eval(qresult.test_result_text)
                if type(t) is dict:
                    for r,val in t.items():
                        writer.writerow(val)

    return response



class ResultsView(LoginRequiredMixin, PermissionRequiredMixin, generic.TemplateView):
    template_name = 'questionnaires/results.html'
    raise_exception = True
    permission_required = 'questionnaires.choice.can_add_choice'

    def get_queryset(self, **kwargs):
        qchoices = Choice.objects.annotate(choice_count=Count('testresult'))

        return qchoices.order_by('question')

    def get_context_data(self, **kwargs):
        context = super(ResultsView, self).get_context_data(**kwargs)
        table = self.get_queryset() #ResultsReportTable(self.get_queryset())
        #RequestConfig(self.request).configure(table)
        context['results_table'] = table

        return context


class TestResultDelete(LoginRequiredMixin, PermissionRequiredMixin, generic.FormView):
    """Delete a result set"""
    template_name = 'questionnaires/confirm_delete.html'
    form_class=TestResultDeleteForm
    raise_exception = True
    permission_required = 'questionnaires.delete_testresult'

    def post(self, request, *args, **kwargs):
        mylist = self.get_queryset()
        token = mylist[0].test_token
        sc = SubjectQuestionnaire.objects.get(session_token = token)
        try:
            r = mylist.delete()
            sc.delete()
            message = 'Successfully deleted questionnaire with ' + str(r[0]) + ' questions'
        except IntegrityError:
            message = 'Unable to delete - please refer to db administrator'
            #transaction.rollback()
            #print('DELETED: ', message, ' r=', r)
            message = message + ' ' + token
        logger.info(message)
        return render(request, self.template_name, {'msg': message})

    def get_context_data(self, **kwargs):
        context = super(TestResultDelete, self).get_context_data(**kwargs)
        resultlist = self.get_queryset()
        if resultlist.count() > 0:
            context.update({
                'qnaire': resultlist[0].test_questionnaire,
                'subject' : resultlist[0].testee,
                'date': resultlist[0].test_datetime
            })
        else:
            context['msg'] = 'There are NO results to delete'
        return context

    def get_queryset(self):
        fid = self.kwargs.get('token')
        try:
            sc = SubjectQuestionnaire.objects.get(pk=fid)
        except Exception as error:
            logger.error('Unable to delete test result: ' + repr(error))
        return TestResult.objects.filter(test_token=sc.session_token)

    def get_success_url(self):
        return reverse('questionnaires:reports')




################QUESTIONNAIRE FORMS ###################################
def parse_twin_question(subject,q, questions):
    """
    Some questions require replacement of male or female twin names
    This handles duplication where two males or two females and otherwise rejects other gender
    :param subject: parent subjectvisit
    :param q: question to be parsed
    :return: dict with twin as key and question as value with altered text or NONE if no replacements
    """
    rtn = {}
    male_re = re.compile(r'\[MALE\sTwin\]')
    female_re = re.compile(r'\[FEMALE\sTwin\]')
    twins = subject.get_twins()
    # has 2 males so duplicate this question
    if subject.has_mm() and bool(male_re.search(q.question_text)):
        q2 = copy.copy(q)
        q.question_text = male_re.sub(twins[0].subject.first_name,q.question_text)
        q2.question_text = male_re.sub(twins[1].subject.first_name, q2.question_text)
        q2.pk = str(q.pk) + '-1'
        # Pick up conditionals
        t1 = [q]
        t2 = [q2]
        if q.conditional:
            # test if order numbering starts at 0
            if questions[q.order] == q:
                start = q.order + 1
                end = start + int(q.condskip)
            else:  # assume order numbering starts at 1
                start = q.order
                end = start + int(q.condskip)

            for con in range(start,end):
                qsub = questions[con]
                qsub2 = copy.copy(qsub)
                qsub2.pk = str(qsub.pk) + '-1'
                qsub.question_text = male_re.sub(twins[0].subject.first_name, qsub.question_text)
                qsub2.question_text = male_re.sub(twins[1].subject.first_name, qsub2.question_text)
                t1.append(qsub)
                t2.append(qsub2)
        rtn = {twins[0]: t1, twins[1]: t2}

    elif subject.has_ff() and bool(female_re.search(q.question_text)):
        q2 = copy.copy(q)
        q.question_text = female_re.sub(twins[0].subject.first_name,q.question_text)
        q2.question_text = female_re.sub(twins[1].subject.first_name, q2.question_text)
        q2.pk = str(q.pk) + '-1'
        #Pick up conditionals
        t1 = [q]
        t2 = [q2]
        if q.conditional:
            #test if order numbering starts at 0
            if questions[q.order] == q:
                start = q.order + 1
                end = start + int(q.condskip)
            else: #assume order numbering starts at 1
                start = q.order
                end = start + int(q.condskip)

            for con in range(start, end):
                qsub = questions[con]
                qsub2 = copy.copy(qsub)
                qsub2.pk = str(qsub.pk) + '-1'
                qsub.question_text = female_re.sub(twins[0].subject.first_name, qsub.question_text)
                qsub2.question_text = female_re.sub(twins[1].subject.first_name, qsub2.question_text)
                t1.append(qsub)
                t2.append(qsub2)
        rtn = {twins[0]: t1, twins[1]: t2}

    elif subject.has_mf():
        for twin in twins:
            if twin.gender ==1 and bool(male_re.search( q.question_text)):
                q.question_text = male_re.sub(twin.subject.first_name, q.question_text)
                rtn = {twin: [q]}
            elif twin.gender ==2 and bool(female_re.search(q.question_text)):
                q.question_text = female_re.sub(twin.subject.first_name, q.question_text)
                rtn = {twin: [q]}

    return rtn


def get_conditional_string(q):
    conditional_actions = {0: True, 1: 'showif', 2: 'skipif', 3: 'skipmore', 4: 'skipless', 5: 'showchecked'}
    return '%s_%d_%d' % (conditional_actions[q.conditional], q.condval, q.condskip)

@login_required
def load_questionnaire(request, *args, **kwargs):
    """ Prepare questionnaire wizard with required questions
        Note: initial values are reloaded every step - so need process_step with session vars to maintain conditionals
    """
    raise_exception = True
    qid = kwargs.get('pk')
    if qid is not None:
        qnaire = Questionnaire.objects.get(pk=qid)
        try:
            subject = SubjectVisit.objects.get(subject=request.user)
        except ObjectDoesNotExist:
            subject = None
            pass
        gender_re = re.compile(r'\[(FE)?MALE\sTwin\]')
        usergrouplist = request.user.groups.all()
        questions = qnaire.question_set.filter(
            Q(group__isnull=True)|Q(group__in=usergrouplist)).order_by('order')

        # Detect question for MALE or FEMALE twin and duplicate if necessary
        twinlist = {}
        if subject is not None and subject.is_parent:
            temp = list()
            tempids = []
            for q in questions:
                if bool(gender_re.search(q.question_text)):
                    qrtn = parse_twin_question(subject, q, questions)
                    if not qrtn:
                        continue
                    else:
                        for twin,question in qrtn.items():
                            for qs in question:
                                temp.append(qs)
                                tempids.append(qs.id)
                                twinlist[qs.pk] = twin.subject
                elif q.id not in tempids:
                    temp.append(q)
                    tempids.append(q.id)
            questions = temp


        #Set initial data
        linkdata = {}
        condata = {}

        #Setup empty forms
        if qnaire.type == 'single':
            return singlepage_questionnaire(request, qnaire, questions)
        elif qnaire.type =='custom':
            code = qnaire.code.replace('-','')
            customurl = 'questionnaires:%s' % code
            return HttpResponseRedirect(reverse(customurl, kwargs={'code': qnaire.code}))
        else:
            num = 0
            for q in questions:
                if twinlist and (q.pk in twinlist.keys()):
                    twin = twinlist.get(q.pk)
                    if isinstance(q.pk,str):
                        #reset back to int
                        q.pk = int(q.pk.split('-')[0])
                else:
                    twin = ''
                linkdata[str(num)] = {'qid': q, 'twin':twin}
                if q.conditional:
                    condata[str(num)] = get_conditional_string(q)
                num += 1
            formlist = [AnswerForm] * len(questions) #questions.count()
            initdata = OrderedDict(linkdata)
            form = QuestionnaireWizard.as_view(form_list=formlist, initial_dict=initdata, condition_dict =condata)
    return form(context=RequestContext(request), request=request)

def is_number(s):
    try:
        float(s)
        return True
    except ValueError:
        return False

class QuestionnaireWizard(LoginRequiredMixin, SessionWizardView):
    template_name = 'questionnaires/qpage.html'
    file_storage = FileSystemStorage(location=os.path.join(settings.MEDIA_ROOT, 'photos'))

    def get_context_data(self, form, **kwargs):
        context = super(QuestionnaireWizard, self).get_context_data(form=form, **kwargs)
        #print("DEBUG: Current=", self.steps.current)
        if self.steps.current == '0':
            self.request.session['start'] = str(time.time())
        return context

    def dispatch(self, request, *args, **kwargs):
        return super(QuestionnaireWizard, self).dispatch(request, *args, **kwargs)

    def process_step(self, form):
        currentstep = self.steps.current
        nextstep = int(currentstep) + 1
        fdata = self.get_form_step_data(form)
        # Modify conditional dict - true/false: this is used when generating form_list
        condition = self.condition_dict.get(currentstep)
        if (condition is not None and not isinstance(condition,bool)):
            field = '%d-question' % int(currentstep)
            cond_type = condition.split('_')[0]
            check_val = condition.split('_')[1]
            skip_val = condition.split('_')[2]
            if cond_type =='showif':
                self.condition_dict[str(nextstep)] = (check_val == fdata[field])
                #If back step - need to regenerate conditional
                if self.condition_dict[str(nextstep)] and self.initial_dict[str(nextstep)]['qid'].conditional:
                    self.condition_dict[str(nextstep)] = get_conditional_string(self.initial_dict[str(nextstep)]['qid'])
            elif cond_type =='skipif':
                for i in range(nextstep, int(skip_val) + nextstep):
                    self.condition_dict[str(i)] = (check_val != fdata[field])
                    if self.condition_dict[str(i)] and self.initial_dict[str(i)]['qid'].conditional:
                        self.condition_dict[str(i)] = get_conditional_string(self.initial_dict[str(i)]['qid'])
            elif cond_type =='skipmore':
                if (is_number(fdata[field])):
                    for i in range(nextstep, int(skip_val) + nextstep):
                        self.condition_dict[str(i)] = (int(fdata[field]) <= int(check_val))
                        if self.condition_dict[str(i)] and self.initial_dict[str(i)]['qid'].conditional:
                            self.condition_dict[str(i)] = get_conditional_string(self.initial_dict[str(i)]['qid'])
            elif cond_type == 'skipless':
                if (is_number(fdata[field])):
                    for i in range(nextstep, int(skip_val) + nextstep):
                        self.condition_dict[str(i)] = (int(fdata[field]) >= int(check_val))
                        if self.condition_dict[str(i)] and self.initial_dict[str(i)]['qid'].conditional:
                            self.condition_dict[str(i)] = get_conditional_string(self.initial_dict[str(i)]['qid'])
            elif cond_type == 'showchecked':
                # Questions must be ordered from zero to work properly
                # Set every option to false then overwrite
                choices = [k for k,v in form.fields['question'].choices]

                for k in choices:
                    if k not in fdata.getlist(field):
                        nextcondition = self.condition_dict.get(k)
                        if (nextcondition is not None and not isinstance(nextcondition, bool)):
                            nextskip_val = nextcondition.split('_')[2]
                            for nextk in range(int(k), int(nextskip_val)+int(k)+1):
                                self.condition_dict[str(nextk)] = False
                        else:
                            self.condition_dict[k] = False

            #Update dict
            self.request.session['conditionals'] = self.condition_dict

        return self.get_form_step_data(form)

    def get_form_initial(self, step):
        initial = self.initial_dict.get(step)
        initial.update({'myuser': self.request.user})
        #Sesion conditionals - do not allow overwrite but need to detect new questionnaire
        if self.request.session.get('conditionals'):
            self.condition_dict = self.request.session.get('conditionals')

        return initial

    # In addition to form_list, the done() method is passed a form_dict, which allows you to access the wizard’s forms based on their step names.
    def done(self, form_list, form_dict, **kwargs):
        # Find question and questionnaire
        qn = self.initial_dict.get('0')['qid']
        qnaire = qn.qid
        store_token = self.request.POST['csrfmiddlewaretoken'] + str(time.time())
        idx = [key for key in form_dict]
        formuser =self.request.user
        start = self.request.session.get('start')
        if start:
            start = datetime.fromtimestamp(float(start))
        else:
            start = timezone.now()

        for key in form_dict:
            f = form_dict.get(key)
            i = idx.index(key)
            qn = self.initial_dict.get(key)['qid']
            twin = self.initial_dict.get(key)['twin']

            #Get response
            response = list(form_list)[i].cleaned_data

            if qn.duplicate:
                questions = ['question','question2']
                if (hasattr(f,'t1') and isinstance(f.t1,User)):
                    users = [f.t2,f.t1]
                else:
                    users = [formuser, formuser]
            else:
                questions = ['question']
                if isinstance(twin,User):
                    users = [twin]
                else:
                    users = [formuser]

            for q in questions:
                choiceidx = response[q]
                if qn.question_type in [3,5]:
                    # Not choice but free text
                    answers = [choiceidx]
                elif qn.question_type == 2:
                    answers = qn.choice_set.filter(choice_value__in=choiceidx)
                else:
                    answers = qn.choice_set.filter(choice_value=choiceidx)
                testee = users.pop()
                # Load save for each choice
                for answer in answers:
                    tresult = TestResult()
                    tresult.testee = testee
                    tresult.test_questionnaire = qnaire
                    tresult.test_result_question = qn
                    if qn.question_type == 5:
                        tresult.test_result_date = answer
                    elif qn.question_type == 3:
                        tresult.test_result_text = answer
                    else:
                        tresult.test_result_choice = answer

                    tresult.test_token = store_token
                    tresult.save()

        # Store category for user
        subjectcat = SubjectQuestionnaire(subject=self.request.user,
                                          questionnaire=qnaire,
                                          start=start,
                                          session_token=store_token)
        subjectcat.save()
        return render(self.request, 'questionnaires/done.html', {
            'form_data': [form.cleaned_data for form in form_list],
            'qnaire_title' : qnaire.title,
        })


###################################
# ############# SINGLE PAGE ##################

def singlepage_questionnaire(request,qnaire, questions):
    template = 'questionnaires/single.html'
    """
    Detect user
    """
    user = request.user
    messages = ''
    qbgcolor = qnaire.bgcolor
    qtextcolor = qnaire.textcolor
    # Create the formset, specifying the form and formset we want to use.
    LinkFormSet = formset_factory(AnswerForm, formset=BaseQuestionFormSet, validate_max=False)
    link_data = [{'qid': q, 'myuser': request.user} for q in questions]

    if request.method == 'POST':
        link_formset = LinkFormSet(request.POST) #cannot reload as dynamic
        token = request.POST['csrfmiddlewaretoken'] + str(time.time())
        start = request.session.get('start')
        if start:
            start = datetime.fromtimestamp(float(start))
        else:
            start = timezone.now()
        if link_formset.is_valid():
            # Now save the data for each form in the formset
            new_data = []
            for i in range(0, len(link_data)):
                formid = 'form-%d-question' % i
                val = request.POST[formid]  # TODO Validate data input
                qn = Question.objects.get(pk=link_data[i]['qid'].pk)
                if qn.question_type in [3,5]:
                    choices = [val]
                elif qn.question_type == 2:
                    choices = qn.choice_set.filter(choice_value__in=val)
                else:
                    choices = qn.choice_set.filter(choice_value=val)

                # Load save for each choice
                for answer in choices:
                    tresult = TestResult()
                    tresult.testee = user
                    tresult.test_questionnaire = qnaire
                    tresult.test_result_question = qn
                    if qn.question_type == 5:
                        tresult.test_result_date = answer
                    elif qn.question_type == 3:
                        tresult.test_result_text = answer
                    else:
                        tresult.test_result_choice = answer

                    tresult.test_token = token
                    tresult.save()

            # Save user info with category
            template='questionnaires/done.html'
            try:
                subjectcat = SubjectQuestionnaire(subject=user, questionnaire=qnaire, start=start, session_token=token)
                subjectcat.save()
                messages='Congratulations, %s!  You have completed the questionnaire.' % user
            except IntegrityError:
                messages.error(request, 'There was an error saving your result.')




    else:
        link_formset = LinkFormSet(initial=link_data)
        request.session['start'] = str(time.time())

    context = {
        'formset': link_formset,
        'qtitle' : qnaire.title,
        'messages': messages,
        'qbgcolor' : qbgcolor,
        'qtextcolor': qtextcolor,
    }

    return render(request, template, context)


import copy
import operator
import os
import re
import time
from collections import OrderedDict
from datetime import datetime

from axes.utils import reset
from django.conf import settings
from django.contrib.auth import REDIRECT_FIELD_NAME, login, logout
from django.contrib.auth.decorators import login_required
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
from django.contrib.auth.models import User
from django.contrib.auth.views import password_change
from django.core.exceptions import ObjectDoesNotExist
from django.core.files.storage import FileSystemStorage
from django.core.urlresolvers import reverse_lazy, reverse
from django.db import IntegrityError
from django.db.models import Count
from django.db.models import Q
from django.forms import formset_factory
from django.http import HttpResponse
from django.http import HttpResponseRedirect
from django.shortcuts import render,redirect, resolve_url, render_to_response
from django.template import RequestContext
from django.utils import timezone
from django.utils.decorators import method_decorator
from django.views import generic
from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_protect
from django.views.decorators.debug import sensitive_post_parameters
from django.views.generic import FormView, RedirectView
from formtools.wizard.views import SessionWizardView
from ipware.ip import get_ip

try:
    from StringIO import StringIO
except ImportError:
    from io import StringIO
try:
    import urlparse
except ImportError:
    from urllib import parse as urlparse # python3 support
# import the logging library
import logging
# Get an instance of a logger
logger = logging.getLogger(__name__)
###Local classes
from .models import Questionnaire, Choice, TestResult, SubjectQuestionnaire,Category,SubjectVisit,Question
from .forms import AxesCaptchaForm, AnswerForm, TestResultDeleteForm, BaseQuestionFormSet, replaceTwinNames
from .tables import FilteredSingleTableView, TestResultTable,SubjectQuestionnaireTable,SubjectVisitTable
from .filters import TestResultFilter,SubjectQuestionnaireFilter,SubjectVisitFilter



## Login
class LoginView(FormView):
    """
    Provides the ability to login as a user with a username and password
    """
    template_name = 'questionnaires/index.html'
    success_url = '/questionnaires'
    form_class = AuthenticationForm
    redirect_field_name = REDIRECT_FIELD_NAME

    @method_decorator(sensitive_post_parameters('password'))
    @method_decorator(csrf_protect)
    @method_decorator(never_cache)
    def dispatch(self, request, *args, **kwargs):
        # Sets a test cookie to make sure the user has cookies enabled
        request.session.set_test_cookie()

        return super(LoginView, self).dispatch(request, *args, **kwargs)

    def form_valid(self, form):
        user = form.get_user()
        if user is not None:
            msg = 'User: %s' % user
            if user.is_active:
                login(self.request, user)
                msg = '% has logged in' % msg
                logger.info(msg)
            else:
                # Return a 'disabled account' error message
                form.add_error = 'Your account has been disabled. Please contact admin.'
                msg = '% has disabled account' % msg
                logger.warning(msg)

        else:
            # Return an 'invalid login' error message.
            form.add_error = 'Login credentials are invalid. Please try again'
            msg = 'Login failed with invalid credentials'
            logger.error(msg)

        # If the test cookie worked, go ahead and
        # delete it since its no longer needed
        self.check_and_delete_test_cookie()
        return super(LoginView, self).form_valid(form)

    def form_invalid(self, form):
        """
        The user has provided invalid credentials (this was checked in AuthenticationForm.is_valid()). So now we
        set the test cookie again and re-render the form with errors.
        """
        self.set_test_cookie()
        return super(LoginView, self).form_invalid(form)

    def set_test_cookie(self):
        self.request.session.set_test_cookie()

    def check_and_delete_test_cookie(self):
        if self.request.session.test_cookie_worked():
            self.request.session.delete_test_cookie()
            return True
        return False

    def get_success_url(self):
        if self.success_url:
            redirect_to = self.success_url
        else:
            redirect_to = self.request.POST.get(
                self.redirect_field_name,
                self.request.GET.get(self.redirect_field_name, ''))

        netloc = urlparse.urlparse(redirect_to)[1]
        if not redirect_to:
            redirect_to = resolve_url(settings.LOGIN_REDIRECT_URL)
        # Security check -- don't allow redirection to a different host.
        elif netloc and netloc != self.request.get_host():
            redirect_to = resolve_url(settings.LOGIN_REDIRECT_URL)
        return redirect_to
        #return reverse(redirect_to)


class LogoutView(RedirectView):
    """
    Provides users the ability to logout
    """
    #template_name = 'questionnaires/index.html'
    successurl = '/questionnaires'

    def get(self, request, *args, **kwargs):
        logout(request)
        return redirect(self.successurl)

def locked_out(request):
    if request.POST:
        form = AxesCaptchaForm(request.POST)
        if form.is_valid():
            ip = get_ip(request)
            if ip is not None:
                msg = "User locked out with IP address=%s" % ip
                logger.warning(msg)
                reset(ip=ip)

            return HttpResponseRedirect(reverse_lazy('questionnaires:index'))
    else:
        form = AxesCaptchaForm()

    return render(request,'questionnaires/locked.html', dict(form=form))

def change_password(request):
    template_response = password_change(request)
    # Do something with `template_response`
    return template_response

def csrf_failure(request, reason=""):
    ctx = {'title': 'CSRF Failure', 'message': 'Your browser does not accept cookies and this can be a problem in ensuring a secure connection.'}
    template_name= 'admin/csrf_failure.html'
    return render_to_response(template_name, ctx)
###########################################################################################

class IndexView(generic.ListView):
    template_name = 'questionnaires/index.html'
    context_object_name = 'questionnaire_list'
    raise_exception = True

    def get_queryset(self):
        return Questionnaire.objects.order_by('order').order_by('code')

    def get_context_data(self, **kwargs):
        context = super(IndexView, self).get_context_data(**kwargs)
        user = self.request.user
        rlist = []
        qlist = {}
        cat1 = None
        if user is not None and user.is_active:
            # set defaults
            cat1 = Category.objects.get(name='Wave 1')  # Wave 1
            catlist = [c for c in Category.objects.filter(name__icontains='all')]  # include ALL
            qlist = self.get_queryset().distinct('code')
            #user_results = user.subjectquestionnaire_set.all() #SubjectQuestionnaire.objects.filter(subject=user)
            rlist = user.subjectquestionnaire_set.distinct('questionnaire')

            if not user.is_superuser:
                visit = SubjectVisit.objects.filter(subject=user)
                #If missing, create visit and set to first category
                if len(visit)==0:
                    visit = SubjectVisit(subject=user, category=cat1, date_visit=datetime.now())
                    visit.save()
                else:
                    visit = visit[0]
                cat1 = visit.category
                catlist.append(cat1)
                usergrouplist = user.groups.values_list('name')

                rlist = rlist.filter(questionnaire__category__in=catlist)
                qlist = qlist.filter(active=True).filter(category__in=catlist).filter(group__name__in=usergrouplist)
            #exclude completed
            for r in rlist:
                qlist = qlist.exclude(pk=r.questionnaire.id)
        #Reorder list by 'order' to allow manual override
        qlist = sorted(qlist, key=operator.attrgetter('order'))
        rlist = sorted(rlist, key=operator.attrgetter('date_stored'), reverse=True)
        # Reset session conditional data
        if self.request.session.get('conditionals'):
            #print("DEBUG: FORM_INITIAL: Reset conditionals")
            self.request.session['conditionals'] = {}


        context['questionnaire_list'] = qlist
        context['result_list']= rlist
        context['visit'] = cat1

        return context


# Questionnaire Intro page
class DetailView(LoginRequiredMixin, generic.DetailView):
    model = Questionnaire
    template_name = 'questionnaires/intro.html'

    def get_context_data(self, **kwargs):
        context = super(DetailView, self).get_context_data(**kwargs)
        context['now'] = timezone.now()
        return context

################REPORT TABLES ##########################################
##Reports
class SubjectReportView(LoginRequiredMixin, FilteredSingleTableView):
    template_name = 'questionnaires/results_summary.html'
    model = SubjectQuestionnaire
    table_class = SubjectQuestionnaireTable
    filter_class = SubjectQuestionnaireFilter
    raise_exception = True

    def get_queryset(self):
        return SubjectQuestionnaire.objects.order_by('questionnaire__code')

    def get_context_data(self, **kwargs):
        context = super(SubjectReportView, self).get_context_data(**kwargs)
        context['title'] = "Subject Reports"
        return context


##Results
class ResultFilterView(LoginRequiredMixin, FilteredSingleTableView):
    template_name = 'questionnaires/results_summary.html'
    model = TestResult
    table_class = TestResultTable
    filter_class = TestResultFilter
    raise_exception = True

    def get_queryset(self):
        return TestResult.objects.order_by('test_questionnaire__code')

    def get_context_data(self, **kwargs):
        context = super(ResultFilterView, self).get_context_data(**kwargs)
        context['title'] = "Questionnaire Results"
        return context

##Visits
class VisitView(LoginRequiredMixin, FilteredSingleTableView):
    template_name = 'questionnaires/results_summary.html'
    model = SubjectVisit
    table_class = SubjectVisitTable
    filter_class = SubjectVisitFilter
    raise_exception = True

    def get_context_data(self, **kwargs):
        context = super(VisitView, self).get_context_data(**kwargs)
        context['title'] = "Subject Visits"
        return context

## ACTIONS ##

def download_report(request, *args, **kwargs):

    # Get subject id
    sid = kwargs.get('subjectid')
    if (sid):
        qs = SubjectQuestionnaire.objects.get(pk=sid)
    else:
        raise Exception('E100: Download report cannot find id')
    filename = "QTAB_report_%s.csv" % sid
    # Create the HttpResponse object with the appropriate CSV header.
    import csv
    from django.utils.encoding import smart_str
    response = HttpResponse(content_type='text/csv')
    response['Content-Disposition'] = 'attachment; filename=%s' % filename
    response.write(u'\ufeff'.encode('utf8'))  # BOM (optional...Excel needs it to open UTF-8 file properly)
    writer = csv.writer(response, csv.excel)
    writer.writerow([
        smart_str(u"ID"),
        smart_str(u"Date"),
        smart_str(u"Questionnaire"),
        smart_str(u"Code"),
        smart_str(u"Description"),
        smart_str(u"Visit"),
        smart_str(u"Total Qns"),
    ])
    id = qs.subject.pk
    visit = "NA"
    if hasattr(qs.subject, 'subjectvisit'):
        if qs.subject.subjectvisit.xnatid:
            id = qs.subject.subjectvisit.xnatid
        visit = qs.subject.subjectvisit.category.name

    writer.writerow([
        smart_str(id),
        smart_str(qs.date_stored),
        smart_str(qs.questionnaire.title),
        smart_str(qs.questionnaire.code),
        smart_str(qs.questionnaire.description),
        smart_str(visit),
        smart_str(qs.questionnaire.question_set.count()),
    ])
    #Output question results
    writer.writerow([smart_str(u"**Results**")])
    writer.writerow([
            smart_str(u"Question"),
            smart_str(u"Options"),
            smart_str(u"Option values"),
            smart_str(u"Choice"),
            smart_str(u"Value"),
            smart_str(u"Text"),
            smart_str(u"Tested"),
     ])
    testresults = TestResult.objects.filter(test_token=qs.session_token)
    gender_re = re.compile(r'\[(FE)?MALE\sTwin\]')
    for qresult in testresults:
        testee = qresult.testee
        choicetext = ""
        choicevalue = ""
        freetext = ""
        choicetexts = ""
        choicevalues = ""
        if qresult.test_result_choice:
            choices = qresult.test_result_question.choice_set.all()
            choicetexts = [replaceTwinNames(qs.subject, choice.choice_text) for choice in choices]
            choicevalues = [choice.choice_value for choice in choices]
            choicetext = replaceTwinNames(qs.subject,qresult.test_result_choice.choice_text)
            choicevalue = qresult.test_result_choice.choice_value
        elif qresult.test_result_text:
            freetext = qresult.test_result_text
        elif qresult.test_result_date:
            freetext = qresult.test_result_date
        qtext = replaceTwinNames(qresult.testee,qresult.test_result_question.question_text)
        if bool(gender_re.search(qtext)):
            qtext = gender_re.sub(testee.first_name,qtext)
        writer.writerow([
            smart_str(qtext),
            smart_str(choicetexts),
            smart_str(choicevalues),
            smart_str(choicetext),
            smart_str(choicevalue),
            smart_str(freetext),
            smart_str(testee),
        ])
        #Output any custom data as table
        if qs.questionnaire.type =='custom':
            if qs.questionnaire.code == 'Wav1-P-08b':
                t = eval(qresult.test_result_text)
                if type(t) is dict:
                    for r,val in t.items():
                        writer.writerow(val)

    return response



class ResultsView(LoginRequiredMixin, PermissionRequiredMixin, generic.TemplateView):
    template_name = 'questionnaires/results.html'
    raise_exception = True
    permission_required = 'questionnaires.choice.can_add_choice'

    def get_queryset(self, **kwargs):
        qchoices = Choice.objects.annotate(choice_count=Count('testresult'))

        return qchoices.order_by('question')

    def get_context_data(self, **kwargs):
        context = super(ResultsView, self).get_context_data(**kwargs)
        table = self.get_queryset() #ResultsReportTable(self.get_queryset())
        #RequestConfig(self.request).configure(table)
        context['results_table'] = table

        return context


class TestResultDelete(LoginRequiredMixin, PermissionRequiredMixin, generic.FormView):
    """Delete a result set"""
    template_name = 'questionnaires/confirm_delete.html'
    form_class=TestResultDeleteForm
    raise_exception = True
    permission_required = 'questionnaires.delete_testresult'

    def post(self, request, *args, **kwargs):
        mylist = self.get_queryset()
        token = mylist[0].test_token
        sc = SubjectQuestionnaire.objects.get(session_token = token)
        try:
            r = mylist.delete()
            sc.delete()
            message = 'Successfully deleted questionnaire with ' + str(r[0]) + ' questions'
        except IntegrityError:
            message = 'Unable to delete - please refer to db administrator'
            #transaction.rollback()
            #print('DELETED: ', message, ' r=', r)
            message = message + ' ' + token
        logger.info(message)
        return render(request, self.template_name, {'msg': message})

    def get_context_data(self, **kwargs):
        context = super(TestResultDelete, self).get_context_data(**kwargs)
        resultlist = self.get_queryset()
        if resultlist.count() > 0:
            context.update({
                'qnaire': resultlist[0].test_questionnaire,
                'subject' : resultlist[0].testee,
                'date': resultlist[0].test_datetime
            })
        else:
            context['msg'] = 'There are NO results to delete'
        return context

    def get_queryset(self):
        fid = self.kwargs.get('token')
        try:
            sc = SubjectQuestionnaire.objects.get(pk=fid)
        except Exception as error:
            logger.error('Unable to delete test result: ' + repr(error))
        return TestResult.objects.filter(test_token=sc.session_token)

    def get_success_url(self):
        return reverse('questionnaires:reports')




################QUESTIONNAIRE FORMS ###################################
def parse_twin_question(subject,q, questions):
    """
    Some questions require replacement of male or female twin names
    This handles duplication where two males or two females and otherwise rejects other gender
    :param subject: parent subjectvisit
    :param q: question to be parsed
    :return: dict with twin as key and question as value with altered text or NONE if no replacements
    """
    rtn = {}
    male_re = re.compile(r'\[MALE\sTwin\]')
    female_re = re.compile(r'\[FEMALE\sTwin\]')
    twins = subject.get_twins()
    # has 2 males so duplicate this question
    if subject.has_mm() and bool(male_re.search(q.question_text)):
        q2 = copy.copy(q)
        q.question_text = male_re.sub(twins[0].subject.first_name,q.question_text)
        q2.question_text = male_re.sub(twins[1].subject.first_name, q2.question_text)
        q2.pk = str(q.pk) + '-1'
        # Pick up conditionals
        t1 = [q]
        t2 = [q2]
        if q.conditional:
            # test if order numbering starts at 0
            if questions[q.order] == q:
                start = q.order + 1
                end = start + int(q.condskip)
            else:  # assume order numbering starts at 1
                start = q.order
                end = start + int(q.condskip)

            for con in range(start,end):
                qsub = questions[con]
                qsub2 = copy.copy(qsub)
                qsub2.pk = str(qsub.pk) + '-1'
                qsub.question_text = male_re.sub(twins[0].subject.first_name, qsub.question_text)
                qsub2.question_text = male_re.sub(twins[1].subject.first_name, qsub2.question_text)
                t1.append(qsub)
                t2.append(qsub2)
        rtn = {twins[0]: t1, twins[1]: t2}

    elif subject.has_ff() and bool(female_re.search(q.question_text)):
        q2 = copy.copy(q)
        q.question_text = female_re.sub(twins[0].subject.first_name,q.question_text)
        q2.question_text = female_re.sub(twins[1].subject.first_name, q2.question_text)
        q2.pk = str(q.pk) + '-1'
        #Pick up conditionals
        t1 = [q]
        t2 = [q2]
        if q.conditional:
            #test if order numbering starts at 0
            if questions[q.order] == q:
                start = q.order + 1
                end = start + int(q.condskip)
            else: #assume order numbering starts at 1
                start = q.order
                end = start + int(q.condskip)

            for con in range(start, end):
                qsub = questions[con]
                qsub2 = copy.copy(qsub)
                qsub2.pk = str(qsub.pk) + '-1'
                qsub.question_text = female_re.sub(twins[0].subject.first_name, qsub.question_text)
                qsub2.question_text = female_re.sub(twins[1].subject.first_name, qsub2.question_text)
                t1.append(qsub)
                t2.append(qsub2)
        rtn = {twins[0]: t1, twins[1]: t2}

    elif subject.has_mf():
        for twin in twins:
            if twin.gender ==1 and bool(male_re.search( q.question_text)):
                q.question_text = male_re.sub(twin.subject.first_name, q.question_text)
                rtn = {twin: [q]}
            elif twin.gender ==2 and bool(female_re.search(q.question_text)):
                q.question_text = female_re.sub(twin.subject.first_name, q.question_text)
                rtn = {twin: [q]}

    return rtn


def get_conditional_string(q):
    conditional_actions = {0: True, 1: 'showif', 2: 'skipif', 3: 'skipmore', 4: 'skipless', 5: 'showchecked'}
    return '%s_%d_%d' % (conditional_actions[q.conditional], q.condval, q.condskip)

@login_required
def load_questionnaire(request, *args, **kwargs):
    """ Prepare questionnaire wizard with required questions
        Note: initial values are reloaded every step - so need process_step with session vars to maintain conditionals
    """
    raise_exception = True
    qid = kwargs.get('pk')
    if qid is not None:
        qnaire = Questionnaire.objects.get(pk=qid)
        try:
            subject = SubjectVisit.objects.get(subject=request.user)
        except ObjectDoesNotExist:
            subject = None
            pass
        gender_re = re.compile(r'\[(FE)?MALE\sTwin\]')
        usergrouplist = request.user.groups.all()
        questions = qnaire.question_set.filter(
            Q(group__isnull=True)|Q(group__in=usergrouplist)).order_by('order')

        # Detect question for MALE or FEMALE twin and duplicate if necessary
        twinlist = {}
        if subject is not None and subject.is_parent:
            temp = list()
            tempids = []
            for q in questions:
                if bool(gender_re.search(q.question_text)):
                    qrtn = parse_twin_question(subject, q, questions)
                    if not qrtn:
                        continue
                    else:
                        for twin,question in qrtn.items():
                            for qs in question:
                                temp.append(qs)
                                tempids.append(qs.id)
                                twinlist[qs.pk] = twin.subject
                elif q.id not in tempids:
                    temp.append(q)
                    tempids.append(q.id)
            questions = temp


        #Set initial data
        linkdata = {}
        condata = {}

        #Setup empty forms
        if qnaire.type == 'single':
            return singlepage_questionnaire(request, qnaire, questions)
        elif qnaire.type =='custom':
            code = qnaire.code.replace('-','')
            customurl = 'questionnaires:%s' % code
            return HttpResponseRedirect(reverse(customurl, kwargs={'code': qnaire.code}))
        else:
            num = 0
            for q in questions:
                if twinlist and (q.pk in twinlist.keys()):
                    twin = twinlist.get(q.pk)
                    if isinstance(q.pk,str):
                        #reset back to int
                        q.pk = int(q.pk.split('-')[0])
                else:
                    twin = ''
                linkdata[str(num)] = {'qid': q, 'twin':twin}
                if q.conditional:
                    condata[str(num)] = get_conditional_string(q)
                num += 1
            formlist = [AnswerForm] * len(questions) #questions.count()
            initdata = OrderedDict(linkdata)
            form = QuestionnaireWizard.as_view(form_list=formlist, initial_dict=initdata, condition_dict =condata)
    return form(context=RequestContext(request), request=request)

def is_number(s):
    try:
        float(s)
        return True
    except ValueError:
        return False

class QuestionnaireWizard(LoginRequiredMixin, SessionWizardView):
    template_name = 'questionnaires/qpage.html'
    file_storage = FileSystemStorage(location=os.path.join(settings.MEDIA_ROOT, 'photos'))

    def get_context_data(self, form, **kwargs):
        context = super(QuestionnaireWizard, self).get_context_data(form=form, **kwargs)
        #print("DEBUG: Current=", self.steps.current)
        if self.steps.current == '0':
            self.request.session['start'] = str(time.time())
        return context

    def dispatch(self, request, *args, **kwargs):
        return super(QuestionnaireWizard, self).dispatch(request, *args, **kwargs)

    def process_step(self, form):
        currentstep = self.steps.current
        nextstep = int(currentstep) + 1
        fdata = self.get_form_step_data(form)
        # Modify conditional dict - true/false: this is used when generating form_list
        condition = self.condition_dict.get(currentstep)
        if (condition is not None and not isinstance(condition,bool)):
            field = '%d-question' % int(currentstep)
            cond_type = condition.split('_')[0]
            check_val = condition.split('_')[1]
            skip_val = condition.split('_')[2]
            if cond_type =='showif':
                self.condition_dict[str(nextstep)] = (check_val == fdata[field])
                #If back step - need to regenerate conditional
                if self.condition_dict[str(nextstep)] and self.initial_dict[str(nextstep)]['qid'].conditional:
                    self.condition_dict[str(nextstep)] = get_conditional_string(self.initial_dict[str(nextstep)]['qid'])
            elif cond_type =='skipif':
                for i in range(nextstep, int(skip_val) + nextstep):
                    self.condition_dict[str(i)] = (check_val != fdata[field])
                    if self.condition_dict[str(i)] and self.initial_dict[str(i)]['qid'].conditional:
                        self.condition_dict[str(i)] = get_conditional_string(self.initial_dict[str(i)]['qid'])
            elif cond_type =='skipmore':
                if (is_number(fdata[field])):
                    for i in range(nextstep, int(skip_val) + nextstep):
                        self.condition_dict[str(i)] = (int(fdata[field]) <= int(check_val))
                        if self.condition_dict[str(i)] and self.initial_dict[str(i)]['qid'].conditional:
                            self.condition_dict[str(i)] = get_conditional_string(self.initial_dict[str(i)]['qid'])
            elif cond_type == 'skipless':
                if (is_number(fdata[field])):
                    for i in range(nextstep, int(skip_val) + nextstep):
                        self.condition_dict[str(i)] = (int(fdata[field]) >= int(check_val))
                        if self.condition_dict[str(i)] and self.initial_dict[str(i)]['qid'].conditional:
                            self.condition_dict[str(i)] = get_conditional_string(self.initial_dict[str(i)]['qid'])
            elif cond_type == 'showchecked':
                # Questions must be ordered from zero to work properly
                # Set every option to false then overwrite
                choices = [k for k,v in form.fields['question'].choices]

                for k in choices:
                    if k not in fdata.getlist(field):
                        nextcondition = self.condition_dict.get(k)
                        if (nextcondition is not None and not isinstance(nextcondition, bool)):
                            nextskip_val = nextcondition.split('_')[2]
                            for nextk in range(int(k), int(nextskip_val)+int(k)+1):
                                self.condition_dict[str(nextk)] = False
                        else:
                            self.condition_dict[k] = False

            #Update dict
            self.request.session['conditionals'] = self.condition_dict

        return self.get_form_step_data(form)

    def get_form_initial(self, step):
        initial = self.initial_dict.get(step)
        initial.update({'myuser': self.request.user})
        #Sesion conditionals - do not allow overwrite but need to detect new questionnaire
        if self.request.session.get('conditionals'):
            self.condition_dict = self.request.session.get('conditionals')

        return initial

    # In addition to form_list, the done() method is passed a form_dict, which allows you to access the wizard’s forms based on their step names.
    def done(self, form_list, form_dict, **kwargs):
        # Find question and questionnaire
        qn = self.initial_dict.get('0')['qid']
        qnaire = qn.qid
        store_token = self.request.POST['csrfmiddlewaretoken'] + str(time.time())
        idx = [key for key in form_dict]
        formuser =self.request.user
        start = self.request.session.get('start')
        if start:
            start = datetime.fromtimestamp(float(start))
        else:
            start = timezone.now()

        for key in form_dict:
            f = form_dict.get(key)
            i = idx.index(key)
            qn = self.initial_dict.get(key)['qid']
            twin = self.initial_dict.get(key)['twin']

            #Get response
            response = list(form_list)[i].cleaned_data

            if qn.duplicate:
                questions = ['question','question2']
                if (hasattr(f,'t1') and isinstance(f.t1,User)):
                    users = [f.t2,f.t1]
                else:
                    users = [formuser, formuser]
            else:
                questions = ['question']
                if isinstance(twin,User):
                    users = [twin]
                else:
                    users = [formuser]

            for q in questions:
                choiceidx = response[q]
                if qn.question_type in [3,5]:
                    # Not choice but free text
                    answers = [choiceidx]
                elif qn.question_type == 2:
                    answers = qn.choice_set.filter(choice_value__in=choiceidx)
                else:
                    answers = qn.choice_set.filter(choice_value=choiceidx)
                testee = users.pop()
                # Load save for each choice
                for answer in answers:
                    tresult = TestResult()
                    tresult.testee = testee
                    tresult.test_questionnaire = qnaire
                    tresult.test_result_question = qn
                    if qn.question_type == 5:
                        tresult.test_result_date = answer
                    elif qn.question_type == 3:
                        tresult.test_result_text = answer
                    else:
                        tresult.test_result_choice = answer

                    tresult.test_token = store_token
                    tresult.save()

        # Store category for user
        subjectcat = SubjectQuestionnaire(subject=self.request.user,
                                          questionnaire=qnaire,
                                          start=start,
                                          session_token=store_token)
        subjectcat.save()
        return render(self.request, 'questionnaires/done.html', {
            'form_data': [form.cleaned_data for form in form_list],
            'qnaire_title' : qnaire.title,
        })


###################################
# ############# SINGLE PAGE ##################

def singlepage_questionnaire(request,qnaire, questions):
    template = 'questionnaires/single.html'
    """
    Detect user
    """
    user = request.user
    messages = ''
    qbgcolor = qnaire.bgcolor
    qtextcolor = qnaire.textcolor
    # Create the formset, specifying the form and formset we want to use.
    LinkFormSet = formset_factory(AnswerForm, formset=BaseQuestionFormSet, validate_max=False)
    link_data = [{'qid': q, 'myuser': request.user} for q in questions]

    if request.method == 'POST':
        link_formset = LinkFormSet(request.POST) #cannot reload as dynamic
        token = request.POST['csrfmiddlewaretoken'] + str(time.time())
        start = request.session.get('start')
        if start:
            start = datetime.fromtimestamp(float(start))
        else:
            start = timezone.now()
        if link_formset.is_valid():
            # Now save the data for each form in the formset
            new_data = []
            for i in range(0, len(link_data)):
                formid = 'form-%d-question' % i
                val = request.POST[formid]  # TODO Validate data input
                qn = Question.objects.get(pk=link_data[i]['qid'].pk)
                if qn.question_type in [3,5]:
                    choices = [val]
                elif qn.question_type == 2:
                    choices = qn.choice_set.filter(choice_value__in=val)
                else:
                    choices = qn.choice_set.filter(choice_value=val)

                # Load save for each choice
                for answer in choices:
                    tresult = TestResult()
                    tresult.testee = user
                    tresult.test_questionnaire = qnaire
                    tresult.test_result_question = qn
                    if qn.question_type == 5:
                        tresult.test_result_date = answer
                    elif qn.question_type == 3:
                        tresult.test_result_text = answer
                    else:
                        tresult.test_result_choice = answer

                    tresult.test_token = token
                    tresult.save()

            # Save user info with category
            template='questionnaires/done.html'
            try:
                subjectcat = SubjectQuestionnaire(subject=user, questionnaire=qnaire, start=start, session_token=token)
                subjectcat.save()
                messages='Congratulations, %s!  You have completed the questionnaire.' % user
            except IntegrityError:
                messages.error(request, 'There was an error saving your result.')




    else:
        link_formset = LinkFormSet(initial=link_data)
        request.session['start'] = str(time.time())

    context = {
        'formset': link_formset,
        'qtitle' : qnaire.title,
        'messages': messages,
        'qbgcolor' : qbgcolor,
        'qtextcolor': qtextcolor,
    }

    return render(request, template, context)


# python3 support # import the logging library # Get an instance of a logger ###Local classes ## Login Provides the ability to login as a user with a username and password # Sets a test cookie to make sure the user has cookies enabled # Return a 'disabled account' error message # Return an 'invalid login' error message. # If the test cookie worked, go ahead and # delete it since its no longer needed The user has provided invalid credentials (this was checked in AuthenticationForm.is_valid()). So now we         set the test cookie again and re-render the form with errors. # Security check -- don't allow redirection to a different host. #return reverse(redirect_to) Provides users the ability to logout #template_name = 'questionnaires/index.html' # Do something with `template_response` ########################################################################################### # set defaults # Wave 1 # include ALL #user_results = user.subjectquestionnaire_set.all() #SubjectQuestionnaire.objects.filter(subject=user) #If missing, create visit and set to first category #exclude completed #Reorder list by 'order' to allow manual override # Reset session conditional data #print("DEBUG: FORM_INITIAL: Reset conditionals") # Questionnaire Intro page ################REPORT TABLES ########################################## ##Reports ##Results ##Visits ## ACTIONS ## # Get subject id # Create the HttpResponse object with the appropriate CSV header. # BOM (optional...Excel needs it to open UTF-8 file properly) #Output question results #Output any custom data as table #ResultsReportTable(self.get_queryset()) #RequestConfig(self.request).configure(table) Delete a result set #transaction.rollback() #print('DELETED: ', message, ' r=', r) ################QUESTIONNAIRE FORMS ################################### Some questions require replacement of male or female twin names     This handles duplication where two males or two females and otherwise rejects other gender     :param subject: parent subjectvisit     :param q: question to be parsed     :return: dict with twin as key and question as value with altered text or NONE if no replacements # has 2 males so duplicate this question # Pick up conditionals # test if order numbering starts at 0 # assume order numbering starts at 1 #Pick up conditionals #test if order numbering starts at 0 #assume order numbering starts at 1 Prepare questionnaire wizard with required questions         Note: initial values are reloaded every step - so need process_step with session vars to maintain conditionals # Detect question for MALE or FEMALE twin and duplicate if necessary #Set initial data #Setup empty forms #reset back to int #questions.count() #print("DEBUG: Current=", self.steps.current) # Modify conditional dict - true/false: this is used when generating form_list #If back step - need to regenerate conditional # Questions must be ordered from zero to work properly # Set every option to false then overwrite #Update dict #Sesion conditionals - do not allow overwrite but need to detect new questionnaire # In addition to form_list, the done() method is passed a form_dict, which allows you to access the wizard’s forms based on their step names. # Find question and questionnaire #Get response # Not choice but free text # Load save for each choice # Store category for user ################################### # ############# SINGLE PAGE ################## Detect user # Create the formset, specifying the form and formset we want to use. #cannot reload as dynamic # Now save the data for each form in the formset # TODO Validate data input # Load save for each choice # Save user info with category