from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals

# Python
from django.utils import timezone
from datetime import datetime
import datetime as dt
from datetime import timedelta
from datetime import date
import time
from django.db import IntegrityError
import uuid
from django.conf import settings
import pytz
from dateutil import parser
from rowers.utils import myqueue,calculate_age,totaltime_sec_to_string
import re
import django_rq
queue = django_rq.get_queue('default')
queuelow = django_rq.get_queue('low')
queuehigh = django_rq.get_queue('low')

import pandas as pd

from rowers.models import (
    Rower, Workout,Team,
    GeoCourse, TrainingMicroCycle,TrainingMesoCycle,TrainingMacroCycle,
    TrainingPlan,PlannedSession,VirtualRaceResult,CourseTestResult,
    get_course_timezone, IndoorVirtualRaceResult,VirtualRace,createmacrofillers,
    createmesofillers,createmicrofillers,
    )

from rowers.courses import get_time_course

from rowers.emails import htmlstrip,htmlstripnobr

import rowers.mytypes as mytypes

import rowers.metrics as metrics
import numpy as np
import rowers.dataprep as dataprep
import rowers.courses as courses
import iso8601
from iso8601 import ParseError
from rowers.tasks import handle_check_race_course
from rowers.tasks import (
    handle_sendemail_raceregistration,handle_sendemail_racesubmission
    )
from rowers.utils import totaltime_sec_to_string

def checkscores(r,macrocycles):
    for m in macrocycles:
        createmesofillers(m)
        m.plantime = 0
        m.actualtime = 0
        m.plandistance = 0
        m.actualdistance = 0
        m.planrscore = 0
        m.actualrscore = 0
        m.plantrimp = 0
        m.actualtrimp = 0


        mesocycles = TrainingMesoCycle.objects.filter(
            plan=m,
            type='userdefined').order_by("startdate")

        for me in mesocycles:
            me.plantime = 0
            me.actualtime = 0
            me.plandistance = 0
            me.actualdistance = 0
            me.planrscore = 0
            me.actualrscore = 0
            me.plantrimp = 0
            me.actualtrimp = 0

            microcycles = TrainingMicroCycle.objects.filter(
                plan=me,
                type='userdefined').order_by("startdate")

            for mm in microcycles:
                sps = get_sessions(r,startdate=mm.startdate,enddate=mm.enddate)

                #                sps = PlannedSession.objects.filter(
                #                    rower = r,
                #                    startdate__lte=mm.enddate,
                #                    enddate__gte=mm.startdate)


                mm.plantime = 0
                mm.actualtime = 0
                mm.plandistance = 0
                mm.actualdistance = 0
                mm.planrscore = 0
                mm.actualrscore = 0
                mm.plantrimp = 0
                mm.actualtrimp = 0


                if mm.type == 'userdefined':
                    for ps in sps:
                        ratio, status, cdate = is_session_complete(r,ps)
                        if ps.sessionmode == 'time':
                            mm.plantime += ps.sessionvalue
                            mm.actualtime += int(ps.sessionvalue*ratio)
                        elif ps.sessionmode == 'distance' and ps.sessiontype != 'race':
                            mm.plandistance += ps.sessionvalue
                            mm.actualdistance += int(ps.sessionvalue*ratio)
                        elif ps.sessionmode == 'rScore':
                            mm.planrscore += ps.sessionvalue
                            mm.actualrscore += int(ps.sessionvalue*ratio)
                        elif ps.sessionmode == 'TRIMP':
                            mm.plantrimp += ps.sessionvalue
                            mm.actualtrimp += int(ps.sessionvalue*ratio)

                    mm.save()

                me.plantime += mm.plantime
                me.actualtime += mm.actualtime
                me.plandistance += mm.plandistance
                me.actualdistance += mm.actualdistance
                me.planrscore += mm.planrscore
                me.actualrscore += mm.actualrscore
                me.plantrimp += mm.plantrimp
                me.actualtrimp += mm.actualtrimp

            if me.type == 'userdefined':
                me.save()

            m.plantime += me.plantime
            m.actualtime += me.actualtime
            m.plandistance += me.plandistance
            m.actualdistance += me.actualdistance
            m.planrscore += me.planrscore
            m.actualrscore += me.actualrscore
            m.plantrimp += me.plantrimp
            m.actualtrimp += me.actualtrimp



        if m.type == 'userdefined':
            m.save()


def get_execution_report(rower,startdate,enddate,plan=None):
    if plan:
        macros = TrainingMacroCycle.objects.filter(plan=plan).order_by("startdate")
        checkscores(rower,macros)
        mesos = TrainingMesoCycle.objects.filter(plan__in=macros).order_by("startdate")
        micros = TrainingMicroCycle.objects.filter(plan__in=mesos).order_by("startdate")
        micros = micros.exclude(enddate__lte=startdate).exclude(startdate__gte=enddate)
    else:
        plans = TrainingPlan.objects.filter(startdate__lte=startdate,enddate__gte=startdate)
        plans2 = TrainingPlan.objects.filter(enddate__lte=enddate,startdate__lte=enddate)
        plans = plans | plans2

        plans = plans.exclude(status=False).order_by("-enddate")

        if not plans:
            # make week cycles here
            # get monday before startdate
            startdate += timedelta(days=1-startdate.isoweekday())
            startdate = startdate-timedelta(days=7)
            micros = []
            while startdate <= enddate:
                micro = type('micros',(object,),
                             {
                                 'startdate':startdate,
                                 'enddate':startdate+timedelta(days=7)
                             })
                micros.append(micro)
                startdate += timedelta(days=7)
        else:
            plan = plans[0]
            macros = TrainingMacroCycle.objects.filter(plan=plan).order_by("startdate")
            checkscores(rower,macros)
            mesos = TrainingMesoCycle.objects.filter(plan__in=macros).order_by("startdate")
            micros = TrainingMicroCycle.objects.filter(
                plan__in=mesos,
                enddate__gte=startdate,
                startdate__lte=enddate,
            ).order_by("startdate")

    # we've got micros, now get sessions
    startdates = []
    planned = []
    executed = []

    for mm in micros:
        plannedscore = 0
        actualscore = 0
        sps = get_sessions(rower,startdate=mm.startdate,enddate=mm.enddate)
        unmatchedworkouts = Workout.objects.filter(
            user=rower,
            plannedsession=None,
            date__gte=mm.startdate,date__lte=mm.enddate).exclude(duplicate=True)
        for w in unmatchedworkouts:
            if w.rscore != 0:
                actualscore += w.rscore
            elif w.hrtss != 0:
                actualscore += w.hrtss
            else:
                minutes = w.duration.hour*60+w.duration.minute
                actualscore += minutes
        for ps in sps:
            ratio, status, cdate = is_session_complete(rower,ps)
            if ps.sessionmode == 'rScore':
                plannedscore += ps.sessionvalue
                actualscore += ratio*ps.sessionvalue
            else:
                ws = Workout.objects.filter(user=rower,plannedsession=ps)
                if not ws:
                    if ps.sessionmode == 'time':
                        plannedscore += ps.sessionvalue
                    elif ps.sessionmode == 'distance':
                        plannedscore += 60.
                    elif ps.sessionmode == 'TRIMP':
                        plannedscore += ps.sessionvalue/2.

                for w in ws:
                    if w.rscore != 0:
                        if ratio > 0:
                            plannedscore += w.rscore/ratio
                            actualscore += w.rscore
                        else:
                            plannedscore += 60
                            actualscore += 0
                    elif w.hrtss != 0:
                        if ratio > 0:
                            plannedscore += w.hrtss/ratio
                            actualscore += w.hrtss
                        else:
                            plannedscore += 60
                            actualscore += 0
                    else:
                        minutes = w.duration.hour*60+w.duration.minute
                        if ratio > 0:
                            plannedscore += minutes/ratio
                        else:
                            plannedscore += 60

                        actualscore += minutes

        actualscore = int(actualscore)
        plannedscore = int(plannedscore)

        startdates += [mm.startdate]
        planned += [plannedscore]
        executed += [actualscore]


    data = pd.DataFrame({
        'startdate':startdates,
        'planned':planned,
        'executed':executed,
    })

    return(data,'ok')

def get_indoorraces(workout):
    races1 = VirtualRace.objects.filter(
        sessiontype='indoorrace',
        startdate__lte=workout.date,
        enddate__gte=workout.date,
        sessionmode='distance',
        sessionvalue=workout.distance)


    if workout.duration.second == 0 and workout.duration.microsecond == 0:
        duration = 60*workout.duration.hour+workout.duration.minute


        races2 = VirtualRace.objects.filter(
            sessiontype='indoorrace',
            startdate__lte=workout.date,
            enddate__gte=workout.date,
            sessionmode='time',
            sessionvalue=duration)

        races = races1 | races2
    else:
        races = races1

    registrations = IndoorVirtualRaceResult.objects.filter(
        race__in = races,
        boatclass = workout.workouttype,
        userid=workout.user.id)

    races = [r.race for r in registrations]


    return races

def get_todays_micro(plan,thedate=date.today()):
    thismicro = None

    thismacro = TrainingMacroCycle.objects.filter(
        plan=plan,
        startdate__lte = thedate,
        enddate__gte = thedate
        )

    if thismacro:
        thismeso = TrainingMesoCycle.objects.filter(
            plan=thismacro[0],
            startdate__lte = thedate,
            enddate__gte = thedate
        )

        if thismeso:
            thismicro = TrainingMicroCycle.objects.filter(
                plan=thismeso[0],
                startdate__lte = thedate,
                enddate__gte = thedate
            )

            if thismicro:
                thismicro = thismicro[0]

    return thismicro

# Low Level functions - to be called by higher level methods
def add_workouts_plannedsession(ws,ps,r):
    result = 0
    comments = []
    errors = []

    # check if all sessions have same date
    dates = [w.date for w in ws]
    if (not all(d == dates[0] for d in dates)) and ps.sessiontype not in ['challenge','cycletarget']:
        errors.append('For tests and training sessions, selected workouts must all be done on the same date')
        return result,comments,errors

    if len(ws)>1 and ps.sessiontype == 'test':
        errors.append('For tests, you can only attach one workout')
        return result,comments,errors



    wold = Workout.objects.filter(plannedsession=ps,user=r)
    ids = [w.id for w in wold] + [w.id for w in ws]
    ids = list(set(ids))

    if len(ids)>1 and ps.sessiontype in ['test','coursetest','race']:
        errors.append('For tests, you can only attach one workout')
        return result,comments,errors

    # start adding sessions
    for w in ws:
        if w.date>=ps.startdate and w.date<=ps.enddate:
            w.plannedsession = ps
            w.save()
            result += 1
            comments.append('Attached workout %i to session' % w.id)
            if ps.sessiontype == 'coursetest':
                record = CourseTestResult(
                    userid=w.user.id,
                    plannedsession=ps,
                    duration=dt.time(0,0),
                    coursecompleted=False,
                    )
                record.save()
                job = myqueue(queue,handle_check_race_course,w.csvfilename,
                              w.id,ps.course.id,record.id,
                              mode='coursetest')
        else:
            errors.append('Workout %i did not match session dates' % w.id)

    return result,comments,errors


def remove_workout_plannedsession(w,ps):
    if w.plannedsession == ps:
        w.plannedsession = None
        w.save()
        return 1

    return 0

def clone_planned_session(ps):
    ps.save()
    ps.pk = None # creates new instance
    ps.save()

def timefield_to_seconds_duration(t):
    duration = t.hour*3600.
    duration += t.minute * 60.
    duration += t.second
    duration += t.microsecond/1.e6

    return duration


def get_virtualrace_times(virtualrace):
    geocourse = GeoCourse.objects.get(id = virtualrace.course.id)
    timezone_str = get_course_timezone(geocourse)

    startdatetime = datetime.datetime.combine(
        virtualrace.startdate,virtualrace.start_time)
    enddatetime = datetime.datetime.combine(
        virtualrace.enddate,virtualrace.end_time)

    startdatetime = pytz.timezone(timezone_str).localize(
        startdatetime
    )
    enddatetime = pytz.timezone(timezone_str).localize(
        enddatetime
    )

    return {
        'startdatetime':startdatetime,
        'enddatetime':enddatetime,
        'evaluation_closure':virtualrace.evaluation_closure,
        'registration_closure':virtualrace.registration_closure,
        }

def get_session_metrics(ps):
    rowers = ps.rower.all()
    rscore = []
    trimp = []
    duration = []
    distance = []
    firstname = []
    lastname = []
    completedate = []
    status = []

    for r in rowers:
        rscorev = 0
        trimpv = 0
        durationv = 0
        distancev = 0
        completedatev = ''
        statusv = 0

        ws = Workout.objects.filter(user=r,plannedsession=ps).order_by("date")

        if ws.count() != 0:
            for w in ws:
                distancev += w.distance
                durationv += timefield_to_seconds_duration(w.duration)
                thetrimp,hrtss = dataprep.workout_trimp(w)
                trimpv += thetrimp
                tss = dataprep.workout_rscore(w)[0]
                if not np.isnan(tss) and tss != 0:
                    rscorev += tss
                elif tss == 0:
                    rscorev += hrtss

            ratio,statusv,completiondate = is_session_complete_ws(ws,ps)
            try:
                completedatev = completiondate.strftime('%Y-%m-%d')
            except AttributeError:
                completedatev = ''
            durationv /= 60.


        trimp.append(int(trimpv))
        duration.append(int(durationv))
        distance.append(int(distancev))
        rscore.append(int(rscorev))
        firstname.append(r.user.first_name)
        lastname.append(r.user.last_name)
        status.append(statusv)
        completedate.append(completedatev)

    thedict = {
        'first_name':firstname,
        'last_name':lastname,
        'duration':duration,
        'distance':distance,
        'rscore':rscore,
        'trimp':trimp,
        'completedate':completedate,
        'status':status,
    }

    return thedict

cratiocolors = {
    'better than nothing': "lightgreen",
    'partial': "mediumgreen",
    'on target': "green",
    'over target': "purple",
    'way over target': "violet",
    'missed': "black",
    'not assigned': "",
    'not done': "white",
}

def is_session_complete_ws(ws,ps):
    ws = ws.order_by("date")
    if ws.count()==0:
        today = date.today()
        if today > ps.enddate:
            verdict = 'missed'
            ratio = 0
            return ratio,verdict,None
        else:
            return 0,'not done',None

    value = ps.sessionvalue
    if ps.sessionunit == 'min':
        value *= 60.
    elif ps.sessionunit == 'km':
        value *= 1000.

    cratiomin = 1
    cratiomax = 1

    cratios = {
        'better than nothing':0,
        'partial':0.6,
        'on target':0.8,
        'over target':1.2,
        'way over target': 1.5
        }


    if ps.criterium == 'none':
        if ps.sessiontype == 'session':
            cratiomin = 0.8
            cratiomax = 1.2
        else:
            cratios['on target'] = 0.9167
            cratios['over target'] = 1.0833
            cratiomin = 0.9167
            cratiomax = 1.0833


    score = 0
    completiondate = None
    for w in ws:
        if ps.sessionmode == 'distance':
            score += w.distance
        elif ps.sessionmode == 'time':
            durationseconds = timefield_to_seconds_duration(w.duration)
            score += durationseconds
        elif ps.sessionmode == 'TRIMP':
            trimp,hrtss = dataprep.workout_trimp(w)
            score += trimp
        elif ps.sessionmode == 'rScore':
            rscore = dataprep.workout_rscore(w)[0]
            if not np.isnan(rscore) and rscore != 0:
                score += rscore
            elif rscore == 0:
                trimp,hrtss = dataprep.workout_trimp(w)
                score += hrtss

        if not completiondate and score>=cratiomin*value:
            completiondate = w.date

    try:
        ratio = score/float(int(value))
    except ZeroDivisionError:
        ratio = 0

    verdict = 'better than nothing'

    if ps.sessiontype in ['session','cycletarget']:
        if ps.criterium == 'exact':
            if ratio == 1.0:
                return ratio,'on target',completiondate
            else:
                if not completiondate:
                    completiondate = ws.reverse()[0].date
                return ratio,'partial',completiondate
        elif ps.criterium == 'minimum':
            if ratio >= 1.0:
                return ratio,'on target',completiondate
            else:
                if not completiondate:
                    completiondate = ws.reverse()[0].date

                return ratio,'partial',completiondate
        else:
            thevalue = 0
            for key,value in cratios.items():
                if ratio>value and value>thevalue:
                    verdict = key
                    thevalue = value

            completiondate = ws.reverse()[0].date
            return ratio,verdict,completiondate
    elif ps.sessiontype == 'test':
        if ratio==1.0:
            return ratio,'on target',completiondate
        else:
            return ratio,'partial',completiondate
    elif ps.sessiontype == 'challenge':
        if ps.criterium == 'exact':
            if ratio == 1.0:
                return ratio,'on target',completiondate
            else:
                return ratio,'partial',completiondate
        elif ps.criterium == 'minimum':
            if ratio > 1.0:
                return ratio,'on target',completiondate
            else:
                if not completiondate:
                    completiondate = ws.reverse()[0].date
                return ratio,'partial',completiondate
        else:
            if not completiondate:
                completiondate = ws.reverse()[0].date
            return ratio,'partial',completiondate
    elif ps.sessiontype == 'race':
        vs = VirtualRaceResult.objects.filter(race=ps)
        wids = [w.id for w in ws]
        for record in vs:
            if record.workoutid in wids:
                if record.coursecompleted:
                    ratio = record.distance/ps.sessionvalue
                    return ratio,'on target',completiondate
                else:
                    ratio = record.distance/ps.sessionvalue
                    return ratio,'partial',completiondate
        return (0,'partial',None)
    elif ps.sessiontype == 'coursetest':
        vs = CourseTestResult.objects.filter(plannedsession=ps)
        wids = [w.id for w in ws]
        for record in vs:
            if record.workoutid in wids:
                if record.coursecompleted:
                    ratio = record.distance/float(ps.sessionvalue)
                    return ratio,'on target',completiondate
                else:
                    ratio = record.distance/float(ps.sessionvalue)
                    return ratio,'partial',completiondate

        # we're still here - no record, need to create one
        if ws:
            record = CourseTestResult(
                userid=ws[0].user.id,
                plannedsession=ps,
                workoutid=ws[0].id,
                duration=dt.time(0,0),
                coursecompleted=False,
            )
            record.save()
            job = myqueue(queue,handle_check_race_course,ws[0].csvfilename,
                          ws[0].id,ps.course.id,record.id,
                          mode='coursetest')

            return (0,'not done',None)


    else:
        if not completiondate:
            completiondate = ws.reverse()[0].date
        return ratio,verdict,completiondate


def is_session_complete(r,ps):
    verdict = 'not done'


    if r not in ps.rower.all():
        return 0,'not assigned',None

    ws = Workout.objects.filter(user=r,plannedsession=ps)

    return is_session_complete_ws(ws,ps)


def rank_results(ps):
    return 1

def add_team_session(t,ps):
    ps.team.add(t)
    ps.save()

    return 1

def add_rower_session(r,ps):
    teams = Team.objects.filter(manager=ps.manager)
    members = Rower.objects.filter(team__in=teams).distinct()
    if r in members and r.rowerplan != 'freecoach':
        ps.rower.add(r)
        ps.save()

        return 1
    elif ps.manager.rower == r and r.rowerplan != 'freecoach':
        ps.rower.add(r)
        ps.save()

    return 0

def remove_team_session(t,ps):
    ps.team.remove(t)

    return 1

def remove_rower_session(r,ps):
    ps.rower.remove(r)

    return 1

def get_team(request):
    teamid = request.GET.get('team')
    return teamid

def get_dates_timeperiod(request,startdatestring='',enddatestring='',
                         defaulttimeperiod='thisweek'):
    # set start end date according timeperiod
    # should always return datetime.date

    timeperiod = request.GET.get('when')

    if not timeperiod:
        timeperiod = defaulttimeperiod

    if startdatestring == '':
        startdatestring = request.GET.get('startdate')
    if enddatestring == '':
        enddatestring = request.GET.get('enddate')

    if startdatestring and enddatestring:
        try:
            startdate = dt.datetime.strptime(startdatestring,'%Y-%m-%d').date()
            enddate = dt.datetime.strptime(enddatestring,'%Y-%m-%d').date()
        except ValueError:
            try:
                startdate = parser.parse(startdatestring,fuzzy=True).date()
                enddate = parser.parse(enddatestring, fuzzy=True).date()
            except ValueError:
                startdate = timezone.now()-timezone.timedelta(days=5)
                startdate = startdate.date()
                enddate = timezone.now().date()

        if startdate>enddate:
            e = startdate
            startdate = enddate
            enddate = e

        return startdate,enddate

    daterangetester = re.compile('^(\d+-\d+-\d+)\/(\d+-\d+-\d+)')

    if timeperiod=='today':
        startdate=date.today()
        enddate=date.today()
    elif timeperiod=='tomorrow':
        startdate=date.today()+timezone.timedelta(days=1)
        enddate=date.today()+timezone.timedelta(days=1)
    elif timeperiod=='thisweek':
        today = date.today()
        startdate = date.today()-timezone.timedelta(days=today.weekday())
        enddate = startdate+timezone.timedelta(days=6)
    elif timeperiod=='thismonth':
        today = date.today()
        startdate = today.replace(day=1)
        enddate = startdate+timezone.timedelta(days=32)
        enddate = enddate.replace(day=1)
        enddate = enddate-timezone.timedelta(days=1)
    elif timeperiod=='lastweek':
        today = date.today()
        enddate = today-timezone.timedelta(days=today.weekday())-timezone.timedelta(days=1)
        startdate = enddate-timezone.timedelta(days=6)
    elif timeperiod=='nextweek':
        today = date.today()
        startdate = today-timezone.timedelta(days=today.weekday())+timezone.timedelta(days=7)
        enddate = startdate+timezone.timedelta(days=6)
    elif timeperiod=='lastmonth':
        today = date.today()
        startdate = today.replace(day=1)
        startdate = startdate-timezone.timedelta(days=3)
        startdate = startdate.replace(day=1)
        enddate = startdate+timezone.timedelta(days=32)
        enddate = enddate.replace(day=1)
        enddate = enddate-timezone.timedelta(days=1)
    elif timeperiod=='nextmonth':
        today = date.today()
        startdate = today.replace(day=1)
        startdate = startdate+timezone.timedelta(days=32)
        startdate = startdate.replace(day=1)
        enddate = startdate+timezone.timedelta(days=32)
        enddate = enddate.replace(day=1)
        enddate = enddate-timezone.timedelta(days=1)
    elif timeperiod=='lastyear':
        today = date.today()
        startdate = today-timezone.timedelta(days=365)
        enddate = today+timezone.timedelta(days=1)
    elif daterangetester.match(timeperiod):
        tstartdatestring = daterangetester.match(timeperiod).group(1)
        tenddatestring = daterangetester.match(timeperiod).group(2)
        try:
            startdate = dt.datetime.strptime(tstartdatestring,'%Y-%m-%d').date()
            enddate = dt.datetime.strptime(tenddatestring,'%Y-%m-%d').date()
            if startdate > enddate:
                startdate2 = enddate
                enddate = startdate
                startdate = startdate2
        except ValueError:
            startdate = date.today()
            enddate = date.today()
    else:
        startdate = date.today()
        enddate = date.today()


    if startdatestring != '':
        try:
            startdate = iso8601.parse_date(startdatestring)
        except ParseError:
            pass

    if enddatestring != '':
        try:
            enddate = iso8601.parse_date(enddatestring)
        except ParseError:
            pass

    return startdate,enddate

def get_sessions_manager(m,teamid=0,startdate=date.today(),
                         enddate=date.today()+timezone.timedelta(+1000)):
    if teamid:
        t = Team.objects.get(id=teamid)
        rs = Rower.objects.filter(team__in=[t]).distinct()
        sps = PlannedSession.objects.filter(
            rower__in=rs,
            manager=m,
            startdate__lte=enddate,
            enddate__gte=startdate,
            is_template=False,
            ).order_by("preferreddate","startdate","enddate").exclude(
                sessiontype='race').exclude(sessiontype='indoorrace')
    else:
        sps = PlannedSession.objects.filter(
            manager=m,
            startdate__lte=enddate,
            enddate__gte=startdate,
            is_template=False,
        ).order_by("preferreddate","startdate","enddate").exclude(
                sessiontype='race').exclude(sessiontype='indoorrace')

    return sps

def get_sessions(r,startdate=date.today(),
                 enddate=date.today()+timezone.timedelta(+1000)):

    sps = PlannedSession.objects.filter(
        rower__in=[r],
        startdate__lte=enddate,
        enddate__gte=startdate,
        is_template=False,
    ).order_by("preferreddate","startdate","enddate").exclude(
                sessiontype='race').exclude(sessiontype='indoorrace')

    return sps

def get_my_session_ids(r):
    sps = PlannedSession.objects.filter(
        rower__in=[r]
        ).order_by("preferreddate","startdate","enddate").exclude(
                sessiontype='race')

    return [ps.id for ps in sps]

def get_workouts_session(r,ps):
    ws = Workout.objects.filter(user=r,plannedsession=ps)

    return ws

def update_plannedsession(ps,cd):
    for attr, value in cd.items():
        if attr == 'comment':
            value.replace("\r\n", "&#10");
            value.replace("\n", "&#10");
        setattr(ps, attr, value)

    ps.save()

    return 1,'Planned Session Updated'

def update_indoorvirtualrace(ps,cd):
    for attr, value in cd.items():
        if attr == 'comment':
            value = htmlstripnobr(value)
            value.replace("\r\n", "&#10");
            value.replace("\n", "&#10");
        setattr(ps, attr, value)

    timezone_str = cd['timezone']

    # correct times

    startdatetime = datetime.combine(cd['startdate'],cd['start_time'])
    enddatetime = datetime.combine(cd['enddate'],cd['end_time'])

    startdatetime = pytz.timezone(timezone_str).localize(
        startdatetime
    )
    enddatetime = pytz.timezone(timezone_str).localize(
        enddatetime
    )
    ps.evaluation_closure = pytz.timezone(timezone_str).localize(
        ps.evaluation_closure.replace(tzinfo=None)
    )

    registration_form = cd['registration_form']
    registration_closure = cd['registration_closure']
    if registration_form == 'manual':
        try:
            registration_closure = pytz.timezone(
                timezone_str
            ).localize(
                registration_closure.replace(tzinfo=None)
            )
        except AttributeError:
            registration_closure = startdatetime
    elif registration_form == 'windowstart':
        registration_closure = startdatetime
    elif registration_form == 'windowend':
        registration_closure = enddatetime
    else:
        registration_closure = ps.evaluation_closure

    ps.registration_closure = registration_closure

    ps.timezone = timezone_str

    ps.save()

    return 1,'Virtual Race Updated'

def update_virtualrace(ps,cd):
    for attr, value in cd.items():
        if attr == 'comment':
            value = htmlstripnobr(value)
            value.replace("\r\n", "&#10");
            value.replace("\n", "&#10");
        setattr(ps, attr, value)

    # correct times

    course = cd['course']
    geocourse = GeoCourse.objects.get(id= course.id)
    timezone_str = get_course_timezone(geocourse)

    startdatetime = datetime.combine(cd['startdate'],cd['start_time'])
    enddatetime = datetime.combine(cd['enddate'],cd['end_time'])

    startdatetime = pytz.timezone(timezone_str).localize(
        startdatetime
    )
    enddatetime = pytz.timezone(timezone_str).localize(
        enddatetime
    )
    ps.evaluation_closure = pytz.timezone(timezone_str).localize(
        ps.evaluation_closure.replace(tzinfo=None)
    )

    registration_form = cd['registration_form']
    registration_closure = cd['registration_closure']
    if registration_form == 'manual':
        try:
            registration_closure = pytz.timezone(
                timezone_str
            ).localize(
                registration_closure.replace(tzinfo=None)
            )
        except AttributeError:
            registration_closure = startdatetime
    elif registration_form == 'windowstart':
        registration_closure = startdatetime
    elif registration_form == 'windowend':
        registration_closure = enddatetime
    else:
        registration_closure = ps.evaluation_closure

    ps.registration_closure = registration_closure

    ps.timezone = timezone_str

    ps.save()

    return 1,'Virtual Race Updated'

def race_rower_status(r,race):

    has_registered = False
    is_complete = False

    if race.sessiontype == 'race':
        resultobj = VirtualRaceResult
    else:
        resultobj = IndoorVirtualRaceResult

    vs = resultobj.objects.filter(userid=r.id,race=race)
    if vs:
        has_registered = True
        is_complete = vs[0].coursecompleted

    return is_complete,has_registered

def race_can_edit(r,race):
    if r.user != race.manager:
        return False
    else:
        start_time = race.start_time
        start_date = race.startdate
        startdatetime = datetime.combine(start_date,start_time)
        startdatetime = pytz.timezone(race.timezone).localize(
            startdatetime
        )
        if timezone.now()<startdatetime:
            return True
        else:
            return False


    return False

def race_can_submit(r,race):
    if r not in race.rower.all():
        return False

    start_time = race.start_time
    start_date = race.startdate
    startdatetime = datetime.combine(start_date,start_time)
    startdatetime = pytz.timezone(race.timezone).localize(
        startdatetime
        )
    evaluation_closure = race.evaluation_closure


    if timezone.now() > startdatetime and timezone.now() < evaluation_closure:
        is_complete,has_registered = race_rower_status(r,race)
        if is_complete == False:
            return True
        else:
            return True
    else:
        return False

    return False

def race_can_resubmit(r,race):
    records = VirtualRaceResult.objects.filter(
        userid=r.id,
        race=race)


    if not records:
        return False

    start_time = race.start_time
    start_date = race.startdate
    startdatetime = datetime.combine(start_date,start_time)
    startdatetime = pytz.timezone(race.timezone).localize(
        startdatetime
        )
    evaluation_closure = race.evaluation_closure



    if timezone.now() > startdatetime and timezone.now() < evaluation_closure:
        is_complete,has_registered = race_rower_status(r,race)
        return is_complete
    else:
        return False

    return False

def race_can_adddiscipline(r,race):

    if race.sessiontype != 'race':
        return False

    records = VirtualRaceResult.objects.filter(
        userid=r.id,
        race=race)

    if not records:
        return False


    start_time = race.start_time
    start_date = race.startdate
    startdatetime = datetime.combine(start_date,start_time)
    startdatetime = pytz.timezone(race.timezone).localize(
        startdatetime
        )
    evaluation_closure = race.evaluation_closure


    if timezone.now() < evaluation_closure:
        is_complete,has_registered = race_rower_status(r,race)
        if has_registered:
            return True
        else:
            return False
    else:
        return False

    return False


def race_can_withdraw(r,race):
    if race.sessiontype == 'race':
        recordobj = VirtualRaceResult
    else:
        recordobj = IndoorVirtualRaceResult

    records = recordobj.objects.filter(
        userid=r.id,
        race=race
        )



    if not records:
        return False

    start_time = race.start_time
    start_date = race.startdate
    startdatetime = datetime.combine(start_date,start_time)
    startdatetime = pytz.timezone(race.timezone).localize(
        startdatetime
        )

    registration_closure = race.registration_closure
    if registration_closure is None or registration_closure == '':
        registration_closure  = startdatetime

    if timezone.now() > registration_closure:
        return False
    elif timezone.now() > startdatetime:
        return False


    return True

def email_submit_race(r,race,workoutid):
    try:
        w = Workout.objects.get(id=workoutid)
    except Workout.DoesNotExist:
        return 0

    if race.sessionmode == 'time':
        wduration = timefield_to_seconds_duration(w.duration)
        delta = wduration - (60.*race.sessionvalue)

        if delta > -2 and delta < 2:
            w.duration = totaltime_sec_to_string(60.*race.sessionvalue)
            w.save()


    elif race.sessionmode == 'distance':
        delta = w.distance - race.sessionvalue

        if delta > -5 and delta < 5:
            w.distance = race.sessionvalue
            w.save()


    if race_can_register(r,race):
        teamname = ''
        weightcategory = w.weightcategory
        sex = r.sex
        if sex == 'not specified':
            sex = 'male'

        if not r.birthdate:
            return 0

        age = calculate_age(r.birthdate)

        adaptiveclass = r.adaptiveclass
        boatclass = w.workouttype

        record = IndoorVirtualRaceResult(
            userid = r.id,
            teamname=teamname,
            race=race,
            username = u'{f} {l}'.format(
                    f = r.user.first_name,
                    l = r.user.last_name
                ),
                weightcategory=weightcategory,
                adaptiveclass=adaptiveclass,
                duration=dt.time(0,0),
                boatclass=boatclass,
                coursecompleted=False,
                sex=sex,
                age=age
                )

        record.save()

        result = add_rower_race(r,race)

        otherrecords = IndoorVirtualRaceResult.objects.filter(
            race = race)


        for otherrecord in otherrecords:
            otheruser = Rower.objects.get(id=otherrecord.userid)
            othername = otheruser.user.first_name+' '+otheruser.user.last_name
            registeredname = r.user.first_name+' '+r.user.last_name
            if otherrecord.emailnotifications:
                job = myqueue(
                    queue,
                    handle_sendemail_raceregistration,
                    otheruser.user.email, othername,
                    registeredname,
                    race.name,
                    race.id
                )


    if race_can_submit(r,race):
        records = IndoorVirtualRaceResult.objects.filter(
            userid = r.id,
            race=race
            )

        if not records:
            return 0

        record = records[0]

        workouts = Workout.objects.filter(id=w.id)

        result,comments,errors,jobid = add_workout_indoorrace(
            workouts,race,r,recordid=record.id
            )


        if result:
            otherrecords = IndoorVirtualRaceResult.objects.filter(
                race = race)

            for otherrecord in otherrecords:
                otheruser = Rower.objects.get(id=otherrecord.userid)
                othername = otheruser.user.first_name+' '+otheruser.user.last_name
                registeredname = r.user.first_name+' '+r.user.last_name
                if otherrecord.emailnotifications:
                    job = myqueue(
                        queue,
                        handle_sendemail_racesubmission,
                        otheruser.user.email, othername,
                        registeredname,
                        race.name,
                        race.id
                    )

            return 1
        else:
            return 0
    else:

        return 0

    return 0


def race_can_register(r,race):
    if race.sessiontype == 'race':
        recordobj = VirtualRaceResult
    else:
        recordobj = IndoorVirtualRaceResult

    records = recordobj.objects.filter(
        userid=r.id,
        race=race)

    if records:
        return False

    start_time = race.start_time
    start_date = race.startdate
    startdatetime = datetime.combine(start_date,start_time)
    startdatetime = pytz.timezone(race.timezone).localize(
        startdatetime
        )

    registration_closure = race.registration_closure
    if registration_closure is None or registration_closure == '':
        registration_closure  = startdatetime

    if timezone.now() > registration_closure:
        return False

    return True

def add_rower_race(r,race):
    race.rower.add(r)
    race.save()

    return 1

def remove_rower_race(r,race,recordid=None):
    race.rower.remove(r)

    if race.sessiontype == 'race':
        recordobj = VirtualRaceResult
    else:
        recordobj = IndoorVirtualRaceResult

    if recordid:
        records = recordobj.objects.filter(userid=r.id,
                                                   workoutid__isnull=True,
                                                   race=race,
                                                   id=recordid)
    else:
        records = recordobj.objects.filter(userid=r.id,
                                                   workoutid__isnull=True,
                                                   race=race,)
    for r in records:
        r.delete()

    return 1

# Low Level functions - to be called by higher level methods
def add_workout_indoorrace(ws,race,r,recordid=0):
    result = 0
    comments = []
    errors = []

    start_time = race.start_time
    start_date = race.startdate
    startdatetime = datetime.combine(start_date,start_time)
    startdatetime = pytz.timezone(race.timezone).localize(
        startdatetime
        )

    end_time = race.end_time
    end_date = race.enddate
    enddatetime = datetime.combine(end_date,end_time)
    enddatetime = pytz.timezone(race.timezone).localize(
        enddatetime
        )

    # check if all sessions have same date
    dates = [w.date for w in ws]
    if (not all(d == dates[0] for d in dates)) and race.sessiontype not in ['challenge','cycletarget']:
        errors.append('For tests and training sessions, selected workouts must all be done on the same date')
        return result,comments,errors,0


    if len(ws)>1 and race.sessiontype == 'test':
        errors.append('For tests, you can only attach one workout')
        return result,comments,errors,0



    ids = [w.id for w in ws]
    ids = list(set(ids))

    if len(ids)>1 and race.sessiontype in ['test','coursetest','race','indoorrace']:
        errors.append('For tests, you can only attach one workout')
        return result,comments,errors,0



    username = r.user.first_name+' '+r.user.last_name
    if r.birthdate:
        age = calculate_age(r.birthdate)
    else:
        age =  None

    record = IndoorVirtualRaceResult.objects.get(
        userid=r.id,
        race=race,
        id=recordid
    )


    records = IndoorVirtualRaceResult.objects.filter(
        userid=r.id,
        race=race,
        workoutid = ws[0].id
        )

    if not record:
        errors.append("Couldn't find this entry")
        return result,comments,errors,0

    if race.sessionmode == 'distance':
        if ws[0].distance != race.sessionvalue:
            errors.append('Your workout did not have the correct distance')
            return 0,comments, errors, 0
        else:
            record.distance = ws[0].distance
            record.duration = ws[0].duration
    else:
        t = ws[0].duration
        seconds = t.second+t.minute*60.+t.hour*3600.+t.microsecond/1.e6
        if seconds != race.sessionvalue*60.:
            errors.append('Your workout did not have the correct duration')
            return 0, comments, errors, 0
        else:
            record.distance = ws[0].distance
            record.duration = ws[0].duration

    if ws[0].workouttype != record.boatclass:
        errors.append('Your workout boat class is different than on your race registration')
        return 0,comments,errors,0

    if ws[0].workouttype not in mytypes.otetypes:
        errors.append('You must submit a indoor rowing workout')
        return 0,comments, errors, 0

    if ws[0].weightcategory != record.weightcategory:
        errors.append('Your workout weight category did not match the weight category you registered')
        return 0,comments, errors,0

    if ws[0].adaptiveclass != record.adaptiveclass:
        errors.append('Your adaptive classification did not match the registration')
        return 0,comments, errors, 0

    # start adding sessions
    if ws[0].startdatetime>=startdatetime and ws[0].startdatetime<=enddatetime:
        ws[0].plannedsession = race
        ws[0].save()
        result += 1

    else:
        errors.append('Workout %i did not match the race window' % ws[0].id)
        return result,comments,errors,0

    if result>0:
        for otherrecord in records:
            otherrecord.workoutid = None
            otherrecord.coursecompleted = False
            otherrecord.save()

        record.coursecompleted = True
        record.workoutid = ws[0].id

        if ws[0].privacy == 'private':
            ws[0].privacy = 'visible'
            ws[0].save()
            comments.append('Workouts submitted to virtual events have to be public. We have changed the workout to a public workout.')

        record.save()

        add_workouts_plannedsession(ws,race,r)


    return result,comments,errors,0


def add_workout_race(ws,race,r,splitsecond=0,recordid=0):
    result = 0
    comments = []
    errors = []

    start_time = race.start_time
    start_date = race.startdate
    startdatetime = datetime.combine(start_date,start_time)
    startdatetime = pytz.timezone(race.timezone).localize(
        startdatetime
        )

    end_time = race.end_time
    end_date = race.enddate
    enddatetime = datetime.combine(end_date,end_time)
    enddatetime = pytz.timezone(race.timezone).localize(
        enddatetime
        )

    # check if all sessions have same date
    dates = [w.date for w in ws]
    if (not all(d == dates[0] for d in dates)) and race.sessiontype not in ['challenge','cycletarget']:
        errors.append('For tests and training sessions, selected workouts must all be done on the same date')
        return result,comments,errors,0

    if len(ws)>1 and race.sessiontype == 'test':
        errors.append('For tests, you can only attach one workout')
        return result,comments,errors,0



    ids = [w.id for w in ws]
    ids = list(set(ids))

    if len(ids)>1 and race.sessiontype in ['test','coursetest','race']:
        errors.append('For tests, you can only attach one workout')
        return result,comments,errors,0



    username = r.user.first_name+' '+r.user.last_name
    if r.birthdate:
        age = calculate_age(r.birthdate)
    else:
        age =  None

    record = VirtualRaceResult.objects.get(
        userid=r.id,
        race=race,
        id=recordid
    )

    records = VirtualRaceResult.objects.filter(
        userid=r.id,
        race=race,
        workoutid = ws[0].id
        )

    if not record:
        errors.append("Couldn't find this entry")
        return result,comments,errors,0

    if ws[0].workouttype not in mytypes.otwtypes:
        errors.append('You have to submit a rowing on water workout')
        return 0,comments,errors,0

    if ws[0].workouttype != record.boatclass:
        errors.append('Your workout boat class is different than on your race registration')
        return 0,comments,errors,0

    if ws[0].boattype != record.boattype:
        errors.append('Your workout boat type did not match the boat type you registered')
        return 0,comments,errors,0

    if ws[0].weightcategory != record.weightcategory:
        errors.append('Your workout weight category did not match the weight category you registered')
        return 0,comments, errors,0

    if ws[0].adaptiveclass != record.adaptiveclass:
        errors.append('Your workout adaptive classification did not match the registration')
        return 0,comments, errors,0

    # start adding sessions
    if ws[0].startdatetime>=startdatetime and ws[0].startdatetime<=enddatetime:
        ws[0].plannedsession = race
        ws[0].save()
        result += 1

    else:
        errors.append('Workout %i did not match the race window' % ws[0].id)
        return result,comments,errors,0

    if result>0:
        for otherrecord in records:
            otherrecord.workoutid = None
            otherrecord.coursecompleted = False
            otherrecord.save()

        if ws[0].privacy == 'private':
            ws[0].privacy = 'visible'
            ws[0].save()
            comments.append('Workouts submitted to virtual events have to be public. We have changed the workout to a public workout.')

        job = myqueue(queue,handle_check_race_course,ws[0].csvfilename,
                      ws[0].id,race.course.id,record.id,splitsecond=splitsecond,
                      referencespeed=record.referencespeed,coursedistance=race.course.distance
                      )



        add_workouts_plannedsession(ws,race,r)


    return result,comments,errors,job.id

def delete_race_result(workout,race):
    results = VirtualRaceResult.objects.filter(workoutid=workout.id,race=race)
    for r in results:
        r.workoutid = None
        r.save()
