# 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 utils import myqueue,calculate_age,totaltime_sec_to_string

import django_rq
queue = django_rq.get_queue('default')
queuelow = django_rq.get_queue('low')
queuehigh = django_rq.get_queue('low')

from rowers.models import (
    Rower, Workout,Team,
    GeoCourse, TrainingMicroCycle,TrainingMesoCycle,TrainingMacroCycle,
    TrainingPlan,PlannedSession,VirtualRaceResult,CourseTestResult
    )

import metrics
import numpy as np
import dataprep
import courses

from rowers.tasks import handle_check_race_course

# 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 = courses.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 len(ws) != 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

def is_session_complete_ws(ws,ps):
    ws = ws.order_by("date")
    if len(ws)==0:
        today = date.today()
        if today > ps.enddate:
            status = 'missed'
            ratio = 0
            return ratio,status,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
    if ps.criterium == 'none':
        if ps.sessiontype == 'session':
            cratiomin = 0.8
            cratiomax = 1.2
        else:
            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

    ratio = score/float(value)

    status = 'partial'

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

                return ratio,'partial',completiondate
        else:
            if ratio>cratiomin and ratio<cratiomax:
                return ratio,'completed',completiondate
            else:
                completiondate = ws.reverse()[0].date
                return ratio,'partial',completiondate
    elif ps.sessiontype == 'test':
        if ratio==1.0:
            return ratio,'completed',completiondate
        else:
            return ratio,'partial',completiondate
    elif ps.sessiontype == 'challenge':
        if ps.criterium == 'exact':
            if ratio == 1.0:
                return ratio,'completed',completiondate
            else:
                return ratio,'partial',completiondate
        elif ps.criterium == 'minimum':
            if ratio > 1.0:
                return ratio,'completed',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,'completed',completiondate
                    
        if ps.course:
            (
                coursetime,
                coursemeters,
                coursecompleted
            ) = courses.get_time_course(ws,ps.course)
            if coursecompleted:
                return 1.0,'completed',completiondate
            else:
                return ratio,'partial',completiondate
        else:
            if ps.criterium == 'exact':
                if ratio == 1.0:
                    return ratio,'completed',completiondate
                else:
                    if not completiondate:
                        completiondate = ws.reverse()[0].date
                    return ratio,'partial',completiondate
            elif ps.criterium == 'minimum':
                if ratio >= 1.0:
                    return ratio,'completed',completiondate
                else:
                    if not completiondate:
                        completiondate = ws.reverse()[0].date

                    return ratio,'partial',completiondate
            else:
                if ratio>cratiomin and ratio<cratiomax:
                    return ratio,'completed',completiondate
                else:
                    return ratio,'partial',completiondate
    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/ps.sessionvalue
                    return ratio,'completed',completiondate

        # we're still here - no record, need to create one
        if ws:
            record = CourseTestResult(
                userid=ws[0].user.id,
                plannedsession=ps,
                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')
                    
        if ps.course:
            (
                coursetime,
                coursemeters,
                coursecompleted
            ) = courses.get_time_course(ws,ps.course)
            if coursecompleted:
                return 1.0,'completed',completiondate
            else:
                return ratio,'partial',completiondate
        else:
            if ps.criterium == 'exact':
                if ratio == 1.0:
                    return ratio,'completed',completiondate
                else:
                    if not completiondate:
                        completiondate = ws.reverse()[0].date
                    return ratio,'partial',completiondate
            elif ps.criterium == 'minimum':
                if ratio >= 1.0:
                    return ratio,'completed',completiondate
                else:
                    if not completiondate:
                        completiondate = ws.reverse()[0].date

                    return ratio,'partial',completiondate
            else:
                if ratio>cratiomin and ratio<cratiomax:
                    return ratio,'completed',completiondate
                else:
                    return ratio,'partial',completiondate

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


def is_session_complete(r,ps):
    status = '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):
    ps.rower.add(r)
    ps.save()

    return 1

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_dates_timeperiod(timeperiod):
    # set start end date according timeperiod
    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)
    else:
        startdate = date.today()
        enddate = date.today()

    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,
            ).order_by("preferreddate","startdate","enddate")
    else:
        sps = PlannedSession.objects.filter(
            manager=m,
            startdate__lte=enddate,
            enddate__gte=startdate,
            ).order_by("preferreddate","startdate","enddate")

    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,
    ).order_by("preferreddate","startdate","enddate")

    return sps

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

    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_virtualrace(ps,cd):
    for attr, value in cd.items():
        if attr == 'comment':
            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 = courses.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 = 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
    
    vs = VirtualRaceResult.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 False
    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):
    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):
    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
        )

    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 race_can_register(r,race):
    records = VirtualRaceResult.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 recordid:
        records = VirtualRaceResult.objects.filter(userid=r.id,
                                                   workoutid__isnull=True,
                                                   race=race,
                                                   id=recordid)
    else:
        records = VirtualRaceResult.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_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].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

    # 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:
            print otherrecord
            otherrecord.workoutid = None
            otherrecord.coursecompleted = False
            otherrecord.save()
        
        job = myqueue(queue,handle_check_race_course,ws[0].csvfilename,
                      ws[0].id,race.course.id,record.id,splitsecond=splitsecond)
            
        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()

        
