from __future__ import unicode_literals

from django.db import models
from django.contrib.auth.models import User
from django.core.validators import validate_email
from django.core.exceptions import ValidationError
from django import forms
from django.forms import ModelForm
from django.dispatch import receiver
from django.forms.widgets import SplitDateTimeWidget
from django.forms.extras.widgets import SelectDateWidget
from django.forms.formsets import BaseFormSet
from django.contrib.admin.widgets import AdminDateWidget,AdminTimeWidget,AdminSplitDateTime
from datetimewidget.widgets import DateTimeWidget
from django.core.validators import validate_email
import os
import twitter
import re
import pytz

from scipy.interpolate import splprep, splev, CubicSpline
import numpy as np

from django.conf import settings
from sqlalchemy import create_engine
import sqlalchemy as sa
from sqlite3 import OperationalError
from django.utils import timezone
import pandas as pd
from dateutil import parser
import datetime
from django.core.exceptions import ValidationError
from rowers.rows import validate_file_extension
from collections import OrderedDict
from timezonefinder import TimezoneFinder

import types
from matplotlib import path

from rowsandall_app.settings import (
    TWEET_ACCESS_TOKEN_KEY,
    TWEET_ACCESS_TOKEN_SECRET,
    TWEET_CONSUMER_KEY,
    TWEET_CONSUMER_SECRET,
    )

tweetapi = twitter.Api(consumer_key=TWEET_CONSUMER_KEY,
                       consumer_secret=TWEET_CONSUMER_SECRET,
                       access_token_key=TWEET_ACCESS_TOKEN_KEY,
                       access_token_secret=TWEET_ACCESS_TOKEN_SECRET)

user = settings.DATABASES['default']['USER']
password = settings.DATABASES['default']['PASSWORD']
database_name = settings.DATABASES['default']['NAME']
host = settings.DATABASES['default']['HOST']
port = settings.DATABASES['default']['PORT']

database_url = 'mysql://{user}:{password}@{host}:{port}/{database_name}'.format(
    user=user,
    password=password,
    database_name=database_name,
    host=host,
    port=port,
    )

if settings.DEBUG or user=='':	    
    database_url = 'sqlite:///db.sqlite3'

timezones = (
    (x,x) for x in pytz.common_timezones
)

class UserFullnameChoiceField(forms.ModelChoiceField):
    def label_from_instance(self,obj):
        return obj.get_full_name()

# model for configurable template field
class TemplateListField(models.TextField):
    def __init__(self, *args, **kwargs):
        self.token = kwargs.pop('token',',')
        super(TemplateListField, self).__init__(*args, **kwargs)

    def to_python(self, value):
        if not value: return
        if isinstance(value, list):
            return value
        # remove double quotes and brackets
        value = re.sub(r'u\"','',value)
        value = re.sub(r'u\'','',value)
        value = re.sub(r'\\','',value)
        value = re.sub(r'\"','',value)
        value = re.sub(r'\'','',value)
        value = re.sub(r'\[','',value)
        value = re.sub(r'\]','',value)
        value = re.sub(r'\[\[','[',value)
        value = re.sub(r'\]\]',']',value)
        value = re.sub(r'\ \ ',' ',value)
        value = re.sub(r', ',',',value)
        
        return value.split(self.token)
        
    def from_db_value(self,value, expression, connection, context):
        if value is None:
            return value
        if isinstance(value, list):
            return value
        return value.split(self.token)
    
    def get_db_prep_value(self, value, connection, prepared=False):
        if not value: return
        assert(isinstance(value, list) or isinstance(value, tuple))
        return self.token.join([unicode(s) for s in value])

    def value_to_string(self, obj):
        value = self._get_val_from_obj(obj)
        return self.get_deb_prep_value(value)
    
# model for Power Zone names
class PowerZonesField(models.TextField):
#    __metaclass__ = models.SubfieldBase

    def __init__(self, *args, **kwargs):
        self.token = kwargs.pop('token',',')
        super(PowerZonesField, self).__init__(*args, **kwargs)

    def to_python(self, value):
        if not value: return
        if isinstance(value, list):
            return value
        # remove double quotes and brackets
        value = re.sub(r'u\"','',value)
        value = re.sub(r'u\'','',value)
        value = re.sub(r'\\','',value)
        value = re.sub(r'\"','',value)
        value = re.sub(r'\'','',value)
        value = re.sub(r'\[','',value)
        value = re.sub(r'\]','',value)
        value = re.sub(r'\[\[','[',value)
        value = re.sub(r'\]\]',']',value)
        value = re.sub(r'\ \ ',' ',value)
        value = re.sub(r', ',',',value)
        
        return value.split(self.token)

    def from_db_value(self,value, expression, connection, context):
        if value is None:
            return value
        if isinstance(value, list):
            return value
        return value.split(self.token)
    
    def get_db_prep_value(self, value, connection, prepared=False):
        if not value: return
        assert(isinstance(value, list) or isinstance(value, tuple))
        return self.token.join([unicode(s) for s in value])

    def value_to_string(self, obj):
        value = self._get_val_from_obj(obj)
        return self.get_deb_prep_value(value)


c2url = 'http://www.concept2.com/indoor-rowers/racing/records/world?machine=1&event=All&gender=All&age=All&weight=All'
    
def update_records(url=c2url):
    try:
        dfs = pd.read_html(url,attrs={'class':'views-table'})
        df = dfs[0]
        df.columns = df.columns.str.strip()
        success = 1
    except:
        df = pd.DataFrame()

    if not df.empty:
        C2WorldClassAgePerformance.objects.all().delete()

    df.Gender = df.Gender.apply(lambda x: 'male' if x=='M' else 'female')
    df['Distance'] = df['Event']
    df['Duration'] = 0

    for nr,row in df.iterrows():
        if 'm' in row['Record']:
            df.ix[nr,'Distance'] = row['Record'][:-1]
            df.ix[nr,'Duration'] = 60*row['Event']
        else:
            df.ix[nr,'Distance'] = row['Event']
            try:
                tobj = datetime.datetime.strptime(row['Record'],'%M:%S.%f')
            except ValueError:
                tobj = datetime.datetime.strptime(row['Record'],'%H:%M:%S.%f')
            df.ix[nr,'Duration'] = 3600.*tobj.hour+60.*tobj.minute+tobj.second+tobj.microsecond/1.e6

    for nr,row in df.iterrows():
        try:
            weightcategory = row.Weight.lower()
        except AttributeError:
            weightcategory = 'hwt'
            
        sex = row.Gender
        name = row.Name
        age = int(row.Age)
        distance = int(row.Distance)
        duration = float(row.Duration)
        season = int(row.Season)

        velo = distance/duration
        power = int(2.8*velo**3)

        record = C2WorldClassAgePerformance(
            age = age,
            weightcategory = weightcategory,
            sex=sex,
            distance = distance,
            duration = duration,
            power = power,
            season = season,
            name = name,
        )
        try:
            record.save()
        except:
            print record
            

class CalcAgePerformance(models.Model):
    weightcategories = (
	('hwt','heavy-weight'),
	('lwt','light-weight'),
	)

    sexcategories = (
        ('male','male'),
        ('female','female'),
        )

    weightcategory = models.CharField(default="hwt",
                                      max_length=30,
                                      choices=weightcategories)

    sex = models.CharField(default="female",
                           max_length=30,
                           choices=sexcategories)

    age = models.IntegerField(default=19,verbose_name="Age")

    duration = models.FloatField(default=1,blank=True)
    power = models.IntegerField(default=200)

    class Meta:
        db_table = 'calcagegrouprecords'

class PowerTimeFitnessMetric(models.Model):
    modechoices = (
        ('rower','Rower'),
        ('water','On the water')
        )
    
    date = models.DateField(default=timezone.now)
    last_workout = models.IntegerField(default=0)
    user = models.ForeignKey(User)
    PowerFourMin = models.FloatField(default=0)
    PowerTwoK = models.FloatField(default=0)
    PowerOneHour = models.FloatField(default=0)
    workoutmode = models.CharField(default='rower',choices=modechoices,
                                   max_length=40)
    class Meta:
        db_table = 'powertimefitnessmetric'
        
class C2WorldClassAgePerformance(models.Model):
    weightcategories = (
	('hwt','heavy-weight'),
	('lwt','light-weight'),
	)

    sexcategories = (
        ('male','male'),
        ('female','female'),
        )

    weightcategory = models.CharField(default="hwt",
                                      max_length=30,
                                      choices=weightcategories)

    sex = models.CharField(default="female",
                           max_length=30,
                           choices=sexcategories)

    age = models.IntegerField(default=19,verbose_name="Age")

    distance = models.IntegerField(default=2000)
    name = models.CharField(max_length=200,blank=True)
    duration = models.FloatField(default=1,blank=True)
    season = models.IntegerField(default=2013)
    power = models.IntegerField(default=200)

    class Meta:
        unique_together = ('age','sex','weightcategory','distance')
    
    def __unicode__(self):
        return self.sex+' '+self.weightcategory+' '+self.name+':'+str(self.age)+' ('+str(self.season)+')'

# For future Team functionality
class Team(models.Model):
    choices = (
        ('private','private'),
        ('open','open'),
        )

    viewchoices = (
        ('coachonly','Coach Only'),
        ('allmembers','All Members')
        )
    
    name = models.CharField(max_length=150,unique=True,verbose_name='Team Name')
    notes = models.CharField(blank=True,max_length=200,verbose_name='Team Purpose')
    manager = models.ForeignKey(User, null=True)
    private = models.CharField(max_length=30,choices=choices,default='open',
                               verbose_name='Team Type')

    viewing = models.CharField(max_length=30,choices=viewchoices,default='allmembers',verbose_name='Sharing Behavior')

    def __unicode__(self):
        return self.name

class TeamForm(ModelForm):
    class Meta:
        model = Team
        fields = ['name','notes','private','viewing']
	widgets = {
	    'notes': forms.Textarea,
	    }

class TeamInvite(models.Model):
    team = models.ForeignKey(Team)
    user = models.ForeignKey(User,null=True)
    issuedate = models.DateField(default=timezone.now)
    code = models.CharField(max_length=150,unique=True)
    email = models.CharField(max_length=150,null=True,blank=True)

class TeamInviteForm(ModelForm):
    user = UserFullnameChoiceField(queryset=User.objects.all(),required=False)
    class Meta:
        model = TeamInvite
        fields = ['user','email']

class TeamRequest(models.Model):
    team = models.ForeignKey(Team)
    user = models.ForeignKey(User,null=True)
    issuedate = models.DateField(default=timezone.now)
    code = models.CharField(max_length=150,unique=True)

from utils import (
    workflowleftpanel,workflowmiddlepanel,
    defaultleft,defaultmiddle,landingpages
    )

from utils import geo_distance

def polygon_coord_center(polygon):

    points = GeoPoint.objects.filter(polygon=polygon).order_by("order_in_poly")

    latitudes = pd.Series([p.latitude for p in points])
    longitudes = pd.Series([p.longitude for p in points])
    
    return latitudes.mean(), longitudes.mean()


    
def polygon_to_path(polygon):
    points = GeoPoint.objects.filter(polygon=polygon).order_by("order_in_poly")
    s = []
    for point in points:
        s.append([point.latitude,point.longitude])

    p = path.Path(s[:-1])

    return p

from rowers.courseutils import coordinate_in_path

def course_spline(coordinates):
    latitudes = coordinates['latitude'].values
    longitudes = coordinates['longitude'].values
    
    # spline parameters
    s = 1.0
    k = min([5,len(latitudes)-1])
    nest = -1

    t = np.linspace(0,1,len(latitudes))
    tnew = np.linspace(0,1,100)
    
    latnew = CubicSpline(t,latitudes,bc_type='clamped')(tnew)
    lonnew = CubicSpline(t,longitudes,bc_type='clamped')(tnew)
#    latnew = CubicSpline(t,latitudes,bc_type='natural')(tnew)
#    lonnew = CubicSpline(t,longitudes,bc_type='natural')(tnew)

#    tckp,u = splprep([t,latitudes,longitudes],s=s,k=k,nest=nest)
#    tnew,latnew,lonnew = splev(np.linspace(0,1,100),tckp)

    newcoordinates = pd.DataFrame({
        'latitude':latnew,
        'longitude':lonnew,
        })

    return newcoordinates
        
def course_coord_center(course):

    polygons = GeoPolygon.objects.filter(course=course).order_by("order_in_course")

    latitudes = []
    longitudes = []

    for p in polygons:
        latitude,longitude = polygon_coord_center(p)
        latitudes.append(latitude)
        longitudes.append(longitude)

    latitude = pd.Series(latitudes).median()
    longitude = pd.Series(longitudes).median()

    
    coordinates = pd.DataFrame({
        'latitude':latitudes,
        'longitude':longitudes,
        })

    
    return latitude,longitude,coordinates

def course_coord_maxmin(course):

    polygons = GeoPolygon.objects.filter(course=course).order_by("order_in_course")

    latitudes = []
    longitudes = []

    for p in polygons:
        latitude,longitude = polygon_coord_center(p)
        latitudes.append(latitude)
        longitudes.append(longitude)

    lat_min = pd.Series(latitudes).min()
    lat_max = pd.Series(latitudes).max()
    long_min = pd.Series(longitudes).min()
    long_max = pd.Series(longitudes).max()


    return lat_min,lat_max,long_min,long_max

def get_dir_vector(polygon1,polygon2):
    lat1,lon1 = polygon_coord_center(polygon1)
    lat2,lon2 = polygon_coord_center(polygon2)

    return [lat2-lat1,lon2-lon1]

def get_delta(vector,polygon):
    x = pd.Series(range(10000))/9999.
    vlat = vector[0]
    vlon = vector[1]

    lat1,lon1 = polygon_coord_center(polygon)
    
    lat = x.apply(lambda x:lat1+x*vlat)
    lon = x.apply(lambda x:lon1+x*vlon)

    totdist,bearing = geo_distance(lat1,lon1,lat1+vlat,lon1+vlon)

    dist = x*totdist
    
    p = polygon_to_path(polygon)

    f = lambda x: coordinate_in_path(x['lat'],x['lon'],p)

    df = pd.DataFrame({'x':x,
                      'lat':lat,
                      'lon':lon,
                       'dist':dist,
                       })
    
    df['inpolygon'] = df.apply(f,axis=1)


    b = (~df['inpolygon']).shift(-1)+df['inpolygon']


    if len(df[b==2]):
        return 1.0e3*df[b==2]['dist'].min()

    else:
        return 0
    
        
def get_delta_start(course):
    polygons = GeoPolygon.objects.filter(course=course).order_by("order_in_course")
    vector = get_dir_vector(polygons[0],polygons[1])
    delta = get_delta(vector,polygons[0])

    return delta

def get_delta_finish(course):
    polygons = GeoPolygon.objects.filter(course=course).order_by("-order_in_course")
    vector = get_dir_vector(polygons[0],polygons[1])
    delta = get_delta(vector,polygons[0])

    return delta

def course_length(course):
    polygons = GeoPolygon.objects.filter(course=course).order_by("order_in_course")

    totaldist = 0
    for i in range(len(polygons)-1):
        latitude1,longitude1 = polygon_coord_center(polygons[i])
        latitude2,longitude2 = polygon_coord_center(polygons[i+1])

        dist = geo_distance(latitude1,longitude1,
                            latitude2,longitude2,)

        totaldist += 1000.*dist[0]

    vector = get_dir_vector(polygons[0],polygons[1])
    deltastart = get_delta(vector,polygons[0])

    polygons = polygons.reverse()
    vector = get_dir_vector(polygons[0],polygons[1])
    deltafinish = get_delta(vector,polygons[0])
    
    return int(totaldist-deltastart-deltafinish)

sexcategories = (
    ('male','male'),
    ('female','female'),
    ('not specified','not specified'),
)
weightcategories = (
    ('hwt','heavy-weight'),
    ('lwt','light-weight'),
)


# Extension of User with rowing specific data
class Rower(models.Model):

    stravatypes = (
        ('Ride','Ride'),
        ('Kitesurf','Kitesurf'),
        ('Run','Run'),
        ('NordicSki','NordicSki'),
        ('Swim','Swim'),
        ('RockClimbing','RockClimbing'),
        ('Hike','Hike'),
        ('RollerSki','RollerSki'),
        ('Walk','Walk'),
        ('Rowing','Rowing'),
        ('AlpineSki','AlpineSki'),
        ('Snowboard','Snowboard'),
        ('BackcountrySki','BackcountrySki'),
        ('Snowshoe','Snowshoe'),
        ('Canoeing','Canoeing'),
        ('StairStepper','StairStepper'),
        ('Crossfit','Crossfit'),
        ('StandUpPaddling','StandUpPaddling'),
        ('EBikeRide','EBikeRide'),
        ('Surfing','Surfing'),
        ('Elliptical','Elliptical'),
        ('VirtualRide','VirtualRide'),
        ('IceSkate','IceSkate'),
        ('WeightTraining','WeightTraining'),
        ('InlineSkate','InlineSkate'),
        ('Windsurf','Windsurf'),
        ('Kayaking','Kayaking'),
        ('Workout','Workout'),
        ('Yoga','Yoga'),
        )
    user = models.OneToOneField(User)
    gdproptin = models.BooleanField(default=False)
    gdproptindate = models.DateTimeField(blank=True,null=True)

    # Heart Rate Zone data
    max = models.IntegerField(default=192,verbose_name="Max Heart Rate")
    rest = models.IntegerField(default=48,verbose_name="Resting Heart Rate")
    ut2 = models.IntegerField(default=105,verbose_name="UT2 band lower HR")
    ut1 = models.IntegerField(default=146,verbose_name="UT1 band lower HR")
    at = models.IntegerField(default=160,verbose_name="AT band lower HR")
    tr = models.IntegerField(default=167,verbose_name="TR band lower HR")
    an = models.IntegerField(default=180,verbose_name="AN band lower HR")
    hrftp = models.IntegerField(default=0,verbose_name="FTP heart rate")

    # Weight Category (for sync to C2)
    weightcategory = models.CharField(default="hwt",
				      max_length=30,
				      choices=weightcategories)

    sex = models.CharField(default="not specified",
                           max_length=30,
                           choices=sexcategories)

    birthdate = models.DateField(null=True,blank=True)
    # Power Zone Data
    ftp = models.IntegerField(default=226,verbose_name="Functional Threshold Power")

    p0 = models.FloatField(default=1.0,verbose_name="CP p1")
    p1 = models.FloatField(default=1.0,verbose_name="CP p2")
    p2 = models.FloatField(default=1.0,verbose_name="CP p3")
    p3 = models.FloatField(default=1.0,verbose_name="CP p4")
    cpratio = models.FloatField(default=1.0,verbose_name="CP fit ratio")

    
    ep0 = models.FloatField(default=1.0,verbose_name="erg CP p1")
    ep1 = models.FloatField(default=1.0,verbose_name="erg CP p2")
    ep2 = models.FloatField(default=1.0,verbose_name="erg CP p3")
    ep3 = models.FloatField(default=1.0,verbose_name="erg CP p4")
    ecpratio = models.FloatField(default=1.0,verbose_name="erg CP fit ratio")

    
    
    otwslack = models.IntegerField(default=0,verbose_name="OTW Power slack")

    pw_ut2 = models.IntegerField(default=124,verbose_name="UT2 Power")
    pw_ut1 = models.IntegerField(default=171,verbose_name="UT1 Power")
    pw_at = models.IntegerField(default=203,verbose_name="AT Power")
    pw_tr = models.IntegerField(default=237,verbose_name="TR Power")
    pw_an = models.IntegerField(default=273,verbose_name="AN Power")
    
    powerzones = PowerZonesField(default=['Rest',
                                          'Pwr UT2',
                                          'Pwr UT1',
                                          'Pwr AT',
                                          'Pwr TR',
                                          'Pwr AN'])
    
    # Site Settings
    workflowleftpanel = TemplateListField(default=defaultleft)
    workflowmiddlepanel = TemplateListField(default=defaultmiddle)
    defaultlandingpage = models.CharField(default='workout_edit_view',
                                          max_length=200,
                                          choices=landingpages,
                                          verbose_name="Default Landing Page")

    # Access tokens
    c2token = models.CharField(default='',max_length=200,blank=True,null=True)
    tokenexpirydate = models.DateTimeField(blank=True,null=True)
    c2refreshtoken = models.CharField(default='',max_length=200,blank=True,null=True)
    c2_auto_export = models.BooleanField(default=False)
    c2_auto_import = models.BooleanField(default=False)
    sporttrackstoken = models.CharField(default='',max_length=200,blank=True,null=True)
    sporttrackstokenexpirydate = models.DateTimeField(blank=True,null=True)
    sporttracksrefreshtoken = models.CharField(default='',max_length=200,
					       blank=True,null=True)
    sporttracks_auto_export = models.BooleanField(default=False)
    underarmourtoken = models.CharField(default='',max_length=200,blank=True,null=True)
    underarmourtokenexpirydate = models.DateTimeField(blank=True,null=True)
    underarmourrefreshtoken = models.CharField(default='',max_length=200,
					       blank=True,null=True)
    mapmyfitness_auto_export = models.BooleanField(default=False)
    tptoken = models.CharField(default='',max_length=1000,blank=True,null=True)
    tptokenexpirydate = models.DateTimeField(blank=True,null=True)
    tprefreshtoken = models.CharField(default='',max_length=1000,
					       blank=True,null=True)
    trainingpeaks_auto_export = models.BooleanField(default=False)

    polartoken = models.CharField(default='',max_length=1000,blank=True,null=True)
    polartokenexpirydate = models.DateTimeField(blank=True,null=True)
    polarrefreshtoken = models.CharField(default='',max_length=1000,
					       blank=True,null=True)
    polaruserid = models.IntegerField(default=0)
    polar_auto_import = models.BooleanField(default=False)

    stravatoken = models.CharField(default='',max_length=200,blank=True,null=True)
    stravaexportas = models.CharField(default="Rowing",
				      max_length=30,
				      choices=stravatypes,
                                      verbose_name="Export Workouts to Strava as")

    strava_auto_export = models.BooleanField(default=False)
    runkeepertoken = models.CharField(default='',max_length=200,
                                      blank=True,null=True)
    runkeeper_auto_export = models.BooleanField(default=False)

    # Plan
    plans = (
	('basic','basic'),
	('pro','pro'),
	('plan','plan'),
	('coach','coach')
	)

    privacychoices = (
        ('visible','Visible'),
        ('hidden','Hidden'),
        )

    getemailnotifications = models.BooleanField(default=False,
                                                verbose_name='Receive email notifications')
    emailbounced = models.BooleanField(default=False,
                                       verbose_name='Email Address Bounced')

    getimportantemails = models.BooleanField(default=True,
                                             verbose_name='Get Important Emails')

    rowerplan = models.CharField(default='basic',max_length=30,
				 choices=plans)

    planexpires = models.DateField(default=timezone.now)
    teamplanexpires = models.DateField(default=timezone.now)
    clubsize = models.IntegerField(default=0)
    protrialexpires = models.DateField(blank=True,null=True)
    plantrialexpires = models.DateField(blank=True,null=True)
    
    
    # Friends/Team
    friends = models.ManyToManyField("self",blank=True)
    privacy  = models.CharField(default='visible',max_length=30,
                                choices=privacychoices)

    team = models.ManyToManyField(Team,blank=True)

    # Export and Time Zone Settings
    defaulttimezone = models.CharField(default='UTC',max_length=100,
                                       choices=timezones,
                                       verbose_name='Default Time Zone')

    # Show flex chart notes
    showfavoritechartnotes = models.BooleanField(default=True,
                                                verbose_name='Show Notes for Favorite Charts')
    
    def __str__(self):
	return self.user.first_name+' '+self.user.last_name

    def __unicode__(self):
        return self.user.first_name+' '+self.user.last_name
    
    def clean_email(self):
        return self.user.email.lower()

class DeactivateUserForm(forms.ModelForm):
    class Meta:
        model = User
        fields = ['is_active']

class DeleteUserForm(forms.ModelForm):
    delete_user = forms.BooleanField(initial=False,
                                    label='Remove my account and all data')
    
    class Meta:
        model = User
        fields = []
        
@receiver(models.signals.post_save,sender=Rower)
def auto_delete_teams_on_change(sender, instance, **kwargs):
    if instance.rowerplan != 'coach':
        teams = Team.objects.filter(manager=instance.user)
        for team in teams:
            team.delete()

from rowers.metrics import axlabels
favchartlabelsx = axlabels.copy()
favchartlabelsy1 = axlabels.copy()
favchartlabelsy2 = axlabels.copy()
favchartlabelsy1.pop('None')
parchoicesy1 = list(sorted(favchartlabelsy1.items(), key = lambda x:x[1]))
parchoicesy2 = list(sorted(favchartlabelsy2.items(), key = lambda x:x[1]))
parchoicesx = list(sorted(favchartlabelsx.items(), key = lambda x:x[1]))
            
# Saving a chart as a favorite chart
class FavoriteChart(models.Model):
    workouttypechoices = (
        ('ote','Erg/SkiErg'),
        ('otw','On The Water'),
        ('both','both')
        )
    
    plottypes = (
        ('line','Line Chart'),
        ('scatter','Scatter Chart')
        )
    
    yparam1 = models.CharField(max_length=50,choices=parchoicesy1,verbose_name='Y1')
    yparam2 = models.CharField(max_length=50,choices=parchoicesy2,verbose_name='Y2',default='None',blank=True)
    xparam = models.CharField(max_length=50,choices=parchoicesx,verbose_name='X')
    plottype = models.CharField(max_length=50,choices=plottypes,
                                default='line',
                                verbose_name='Chart Type')
    workouttype = models.CharField(max_length=50,choices=workouttypechoices,
                                   default='both',
                                   verbose_name='Workout Type')
    reststrokes = models.BooleanField(default=True,verbose_name="Incl. Rest")
    notes = models.CharField(max_length=300,verbose_name='Chart Notes',
                             default='Flex Chart Notes',blank=True)
    user = models.ForeignKey(Rower)
    

class FavoriteForm(ModelForm):
    class Meta:
        model = FavoriteChart
        fields = ['xparam','yparam1','yparam2',
                  'plottype','workouttype','reststrokes','notes']
#        widgets = {
#	    'notes': forms.Textarea,
#	    }


# To generate favorite chart forms on the fly
class BaseFavoriteFormSet(BaseFormSet):
    def clean(self):
        if any(self.errors):
            return

        for form in self.forms:
            if form.cleaned_data:
                xparam = form.cleaned_data['xparam']
                yparam1 = form.cleaned_data['yparam1']
                yparam2 = form.cleaned_data['yparam2']
                plottype = form.cleaned_data['plottype']
                reststrokes = form.cleaned_data['reststrokes']
                
            if not xparam:
                raise forms.ValidationError(
                    'Must have x parameter.',
                    code='missing_xparam'
                    )

            if not yparam1:
                raise forms.ValidationError(
                    'Must have Y1 parameter.',
                    code='missing_yparam1'
                    )

            if not yparam2:
                yparam2 = 'None'

class BasePlannedSessionFormSet(BaseFormSet):
    def clean(self):
        if any(self.serrors):
            return
                
                
# Check if workout is owned by this user
def checkworkoutuser(user,workout):
    if user.is_anonymous():
        return False
    try:
	r = Rower.objects.get(user=user)
        teams = workout.team.all()
        if workout.user == r:
            return True
        elif teams:
            for team in teams:
                if user == team.manager:
                    return True
        else:
            return False
    except Rower.DoesNotExist:
	return False

# Check if user is coach or rower
def checkaccessuser(user,rower):
    try:
        r = Rower.objects.get(user=user)
        teams = Team.objects.filter(manager=user)
        if rower == r:
            return True
        elif teams:
            for team in teams:
                if team in rower.team.all():
                    return True
        else:
            return False
    except Rower.DoesNotExist:
        return False
    
timezones = (
    (x,x) for x in pytz.common_timezones
)

# models related to geo data (points, polygon, courses)

class GeoCourse(models.Model):
    manager = models.ForeignKey(Rower)
    distance = models.IntegerField(default=0)
    name = models.CharField(max_length=150,blank=True)
    country = models.CharField(max_length=150,blank=True)
    notes = models.CharField(blank=True,max_length=200,verbose_name='Course Notes')
    def __unicode__(self):
        name = self.name
        country = self.country
        d = self.distance
        if d == 0:
            self.distance = course_length(self)
            self.save()
            d = self.distance
        
        return u'{country} - {name} - {d}m'.format(
            name=name,
            country=country,
            d = d,
        )

class GeoCourseEditForm(ModelForm):
    class Meta:
        model = GeoCourse
        fields = ['name','country','notes']

        widgets = {
            'notes': forms.Textarea,
            }
    
class GeoPolygon(models.Model):
    name = models.CharField(max_length=150,blank=True)
    course = models.ForeignKey(GeoCourse, blank=True)
    order_in_course = models.IntegerField(default=0)

    def __unicode__(self):
        name = self.name
        coursename = self.course.name
        
        return u'{coursename} - {name}'.format(
            name=name,
            coursename=coursename
        )
    
    
# Need error checking to insert new polygons into existing course (all later polygons
# increase there order_in_course number
    
class GeoPoint(models.Model):
    latitude = models.FloatField(default=0)
    longitude = models.FloatField(default=0)
    polygon = models.ForeignKey(GeoPolygon,blank=True)
    order_in_poly = models.IntegerField(default=0)

# need error checking to "insert" new point into existing polygon? This affects order_in_poly
# of multiple GeoPoint instances


def half_year_from_now():
    return timezone.now()+timezone.timedelta(days=182)

def a_week_from_now():
    return timezone.now()+timezone.timedelta(days=7)

# models related to training planning - draft
# Do we need a separate class TestTarget?
class TrainingTarget(models.Model):
    rower = models.ForeignKey(Rower)
    name = models.CharField(max_length=150,blank=True)
    date = models.DateField(
        default=half_year_from_now)
    notes = models.TextField(max_length=300,blank=True)

class TrainingTargetForm(ModelForm):
    class Meta:
        model = TrainingTarget
        fields = ['name','date','notes']

	widgets = {
	    'date': SelectDateWidget(
                years=range(
                    timezone.now().year-1,timezone.now().year+1)),
	    }

# SportTracks has a TrainingGoal like this
#class TrainingGoal(models.Model):
#    rower = models.ForeignKey(Rower)
#    name = models.CharField(max_length=150,blank=True)
#    startdate = models.DateField(default=timezone.now)
#    enddate = models.DateField(
#        default=timezone.now()+datetime.timedelta(days=28))
#    goalmetric = models.CharField(max_length=150,default='rower',
#                                  choices = modechoices)
#    value = models.IntegerValue(default=1)

# I think we can use PlannedSession for that (in challenge mode)
# although such a TrainingGoal could have automatically calculated
# values without needing the user to assign


class TrainingPlan(models.Model):
    rower = models.ForeignKey(Rower)
    name = models.CharField(max_length=150,blank=True)    
    target = models.ForeignKey(TrainingTarget,blank=True)
    startdate = models.DateField(default=timezone.now)
    enddate = models.DateField(
        default=half_year_from_now)

class TrainingPlanForm(ModelForm):
    class Meta:
        model = TrainingPlan
        fields = ['name','target','startdate','enddate']

	widgets = {
	    'startdate': SelectDateWidget(
                years=range(
                    timezone.now().year-1,timezone.now().year+1)),
	    'enddate': SelectDateWidget(
                years=range(
                    timezone.now().year-1,timezone.now().year+1)),
	    }

    
cycletypechoices = (
    ('filler','System Defined'),
    ('userdefined','User Defined')
    )

class TrainingMacroCycle(models.Model):
    plan = models.ForeignKey(TrainingPlan)
    name = models.CharField(max_length=150,blank=True)
    startdate = models.DateField(default=timezone.now)
    enddate = models.DateField(
        default=half_year_from_now)
    notes = models.TextField(max_length=300,blank=True)
    type = models.CharField(default='filler',
                            choices=cycletypechoices,
                            max_length=150)

class TrainingMesoCycle(models.Model):
    plan = models.ForeignKey(TrainingMacroCycle)
    name = models.CharField(max_length=150,blank=True)
    startdate = models.DateField(default=timezone.now)
    enddate = models.DateField(
        default=half_year_from_now)
    notes = models.TextField(max_length=300,blank=True)
    type = models.CharField(default='filler',
                            choices=cycletypechoices,
                            max_length=150)


class TrainingMicroCycle(models.Model):
    plan = models.ForeignKey(TrainingMesoCycle)
    name = models.CharField(max_length=150,blank=True)
    startdate = models.DateField(default=timezone.now)
    enddate = models.DateField(
        default=half_year_from_now)
    notes = models.TextField(max_length=300,blank=True)
    type = models.CharField(default='filler',
                            choices=cycletypechoices,
                            max_length=150)


# Needs some error checking
# - Microcycles should not overlap with other microcycles, same for MesoCycles, MacroCycles
# - When a TrainingPlan is created, it should create 1 "collector" Macro, Meso & MicroCycle - this is invisible for users who choose to not use cycles
# - When a new Microcycle is inserted, the "collector" cycle is automatically adjusted to "go out of the way" of the new MicroCycle - and similar for Macro & Meso
# - If the entire MesoCycle is filled with user defined MicroCycles - there are no "filler" MicroCycles
# - Sessions are automatically linked to the correct Cycles based on their start/end date - no need for a hard link

# Cycle error checking goes in forms
    
# model for Planned Session (Workout, Challenge, Test)
class PlannedSession(models.Model):

    sessiontypechoices = (
        ('session','Training Session'),
        ('challenge','Challenge'),
        ('test','Mandatory Test'),
        ('cycletarget','Cycle Target'),
        ('coursetest','OTW test over a course'),
        ('race','Virtual Race'),
        )

    sessionmodechoices = (
        ('distance','Distance'),
        ('time','Time'),
        ('rScore','rScore'),
        ('TRIMP','TRIMP'),
        )

    criteriumchoices = (
        ('none','Approximately'),
        ('minimum','At Least'),
        ('exact','Exactly'),
        )

    verificationchoices = (
        ('none','None'),
        ('automatic','Automatic'),
        ('manual','Manual')
        )

    sessionunitchoices = (
        ('min','minutes'),
        ('m','meters'),
        ('None',None),
        )
    
    manager = models.ForeignKey(User)
    course = models.ForeignKey(GeoCourse,blank=True,null=True,
                               verbose_name='OTW Course')

    name = models.CharField(max_length=150,blank=True,
                            verbose_name='Name')

    comment = models.TextField(max_length=500,blank=True,
                               )

    startdate = models.DateField(default=timezone.now,
                                 verbose_name='On or After')

    enddate = models.DateField(default=a_week_from_now,
                               verbose_name='On or Before')

    preferreddate = models.DateField(default=a_week_from_now,
                                     verbose_name='Preferred Date')
    
    sessiontype = models.CharField(default='session',
                                   choices=sessiontypechoices,
                                   max_length=150,
                                   verbose_name='Session Type')

    sessionvalue = models.IntegerField(default=60,verbose_name='Value')

    max_nr_of_workouts = models.IntegerField(
        default=0,verbose_name='Maximum number of workouts'
        )

    sessionunit = models.CharField(
        default='min',choices=sessionunitchoices,
        max_length=150,
        verbose_name='Unit')

    criterium = models.CharField(
        default='none',
        choices=criteriumchoices,
        max_length=150,
        verbose_name='Criteria')

    verification = models.CharField(
        default='none',
        max_length=150,
        choices=verificationchoices
    )

    team = models.ManyToManyField(Team,blank=True)
    rower = models.ManyToManyField(Rower,blank=True)

    sessionmode = models.CharField(default='time',
                                   choices=sessionmodechoices,
                                   max_length=150,
                                   verbose_name='Session Mode')

    hasranking = models.BooleanField(default=False)

    def __unicode__(self):

        name = self.name
        startdate = self.startdate
        enddate = self.enddate

        stri = u'{n} {s} - {e}'.format(
            s = startdate.strftime('%Y-%m-%d'),
            e = enddate.strftime('%Y-%m-%d'),
            n = name,
        )
            
	return stri

    def save(self, *args, **kwargs):
        # sort units
        if self.sessionmode == 'distance':
            if self.sessionunit not in ['m','km']:
                self.sessionunit = 'm'
        elif self.sessionmode == 'time':
            self.sessionunit = 'min'
        else:
            self.sessionunit = 'None'

        if self.sessiontype == 'test':
            if self.sessionmode not in ['distance','time']:
                if self.sessionvalue < 100:
                    self.sessionmode = 'time'
                    self.sessionunit = 'min'
                else:
                    self.sessionmode = 'distance'
                    self.sessionunit = 'm'
            self.criterium = 'exact'
        if self.sessiontype == 'coursetest' or self.sessiontype == 'race':
            self.sessionmode = 'distance'
            self.sessionunit = 'm'
            self.criterium = 'none'
            if self.course == None:
                self.course = GeoCourse.objects.all()[0]
            self.sessionvalue = self.course.distance
        elif self.sessiontype != 'coursetest' and self.sessiontype != 'race':
            self.course = None

        if self.enddate < self.startdate:
            self.enddate = self.startdate
            
        if self.preferreddate > self.enddate:
            self.preferreddate = self.enddate
        if self.preferreddate < self.startdate:
            self.preferreddate = self.startdate
                    
        super(PlannedSession,self).save(*args, **kwargs)

from django.core.validators import RegexValidator,validate_email

registerchoices = (
    ('windowstart','Start of Race Window'),
    ('windowend','End of Race Window'),
    ('deadline','Evaluation Closure Deadline'),
    ('manual','Manual - select below'),
    )

class VirtualRace(PlannedSession):
#    has_registration = models.BooleanField(default=False)
    registration_form = models.CharField(
        max_length=100,
        default='windowstart',
        choices=registerchoices,
        verbose_name='Registration Closure Quick Selector'
    )
    registration_closure = models.DateTimeField(blank=True,null=True)
    evaluation_closure = models.DateTimeField(blank=True,null=True)
    start_time = models.TimeField(blank=True,null=True)
    end_time = models.TimeField(blank=True,null=True)
    country = models.CharField(max_length=100,blank=True)

    timezone = models.CharField(default='UTC',
                                choices=timezones,
                                max_length=100)

    phone_regex = RegexValidator(
        regex=r'^\+?1?\d{9,15}$',
        message="Phone number must be entered in the format: '+999999999'. Up to 15 digits allowed."
    )
    
    contact_phone = models.CharField(validators=[phone_regex], max_length=17, blank=True) 
    
    contact_email = models.EmailField(max_length=254,
                                      validators=[validate_email],blank=True)

    def __unicode__(self):

        name = self.name
        startdate = self.startdate
        enddate = self.enddate

        stri = u'Virtual Race {n}'.format(
            n = name,
        )
            
	return stri


    def save(self, *args, **kwargs):
        # test race window logic

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

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

        if startdatetime > enddatetime:
            self.start_time = end_time
            self.startdate = end_date
            self.end_time = start_time
            self.enddate = start_date
            enddatetime = startdatetime

        if self.evaluation_closure < enddatetime:
            self.evaluation_closure = enddatetime + timezone.timedelta(days=1)
            
        super(VirtualRace,self).save(*args, **kwargs)
    
    
# Date input utility
class DateInput(forms.DateInput):
    input_type = 'date'
    
class PlannedSessionForm(ModelForm):
    
    class Meta:
        model = PlannedSession
        fields = ['startdate',
                  'enddate',
                  'preferreddate',
                  'name',
                  'sessiontype',
                  'sessionmode',
                  'criterium',
                  'sessionvalue',
                  'sessionunit',
                  'course',
                  'comment',
        ]

        dateTimeOptions = {
            'format': 'yyyy-mm-dd',
            'autoclose': True,
            }
        
	widgets = {
	    'comment': forms.Textarea,
	    'startdate': AdminDateWidget(),
	    'enddate': AdminDateWidget(),
	    'preferreddate': AdminDateWidget(),
	}
    
    def __init__(self,*args,**kwargs):
        super(PlannedSessionForm, self).__init__(*args, **kwargs)
        self.fields['course'].queryset = GeoCourse.objects.all().order_by("country","name")

def get_course_timezone(course):
    polygons = GeoPolygon.objects.filter(course = course)
    points = GeoPoint.objects.filter(polygon = polygons[0])
    lat = points[0].latitude
    lon = points[0].longitude

    tf = TimezoneFinder()
    try:
        timezone_str = tf.timezone_at(lng=lon,lat=lat)
    except ValueError:
        timezone_str = 'UTC'

    if timezone_str is None:
        timezone_str = tf.closest_timezone_at(lng=lon,lat=lat)
        if timezone_str is None:
            timezone_str = 'UTC'

    return timezone_str

        
class VirtualRaceForm(ModelForm):
    course = forms.ModelChoiceField(queryset = GeoCourse.objects, empty_label=None)
    registration_closure = forms.SplitDateTimeField(widget=AdminSplitDateTime(),required=False)
    evaluation_closure = forms.SplitDateTimeField(widget=AdminSplitDateTime(),required=True)

    
    class Meta:
        model = VirtualRace
        fields = [
            'name',
            'startdate',
            'start_time',
            'enddate',
            'end_time',
#            'has_registration',
            'registration_form',
            'registration_closure',
            'evaluation_closure',
            'course',
            'comment',
            'contact_phone',
            'contact_email',
        ]

        dateTimeOptions = {
            'format': 'yyyy-mm-dd',
            'autoclose': True,
            }
        
	widgets = {
	    'comment': forms.Textarea,
	    'startdate': AdminDateWidget(),
	    'enddate': AdminDateWidget(),
            'start_time': AdminTimeWidget(),
            'end_time': AdminTimeWidget(),
            'registration_closure':AdminSplitDateTime(),
            'evaluation_closure':AdminSplitDateTime(),
	}
    
    def __init__(self,*args,**kwargs):
        super(VirtualRaceForm, self).__init__(*args, **kwargs)
        self.fields['course'].queryset = GeoCourse.objects.all().order_by("country","name")
    

    def clean(self):
        cd = self.cleaned_data
        course = cd['course']
        geocourse = GeoCourse.objects.get(id=course.id)
        timezone_str = get_course_timezone(geocourse)

        start_time = cd['start_time']
        if start_time is None:
            raise forms.ValidationError(
                    'Must have start time',
                    code='missing_yparam1'
                    )
        
        start_date = cd['startdate']
        startdatetime = datetime.datetime.combine(start_date,start_time)
        startdatetime = pytz.timezone(timezone_str).localize(
            startdatetime
        )

        end_time = cd['end_time']
        if end_time is None:
            raise forms.ValidationError(
                'Must have end time',
                code='missing endtime'
                )
            
        end_date = cd['enddate']
        enddatetime = datetime.datetime.combine(end_date,end_time)
        enddatetime = pytz.timezone(timezone_str).localize(
            enddatetime
        )

        registration_closure = cd['registration_closure']

        registration_form = cd['registration_form']

        try:
            evaluation_closure = cd['evaluation_closure']
        except KeyError:
            evaluation_closure = enddatetime+datetime.timedelta(days=1)
            cd['evaluation_closure'] = evaluation_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


        if registration_closure <= timezone.now():
            raise forms.ValidationError("Registration Closure cannot be in the past")


        if startdatetime > enddatetime:
            raise forms.ValidationError("The Start of the Race Window should be before the End of the Race Window")


        if cd['evaluation_closure'] <= enddatetime:
            raise forms.ValidationError("Evaluation closure deadline should be after the Race Window closes")

        if cd['evaluation_closure'] <= timezone.now():
            raise forms.ValidationError("Evaluation closure cannot be in the past")
        

        return cd

class PlannedSessionFormSmall(ModelForm):
    
    class Meta:
        model = PlannedSession
        fields = ['startdate',
                  'enddate',
                  'preferreddate',
                  'name',
                  'sessiontype',
                  'sessionmode',
                  'criterium',
                  'sessionvalue',
                  'sessionunit',
                  'manager',
        ]

        dateTimeOptions = {
            'format': 'yyyy-mm-dd',
            'autoclose': True,
            }
        
	widgets = {
	    'startdate': DateInput(attrs={'size':10}),
	    'enddate': DateInput(attrs={'size':10}),
	    'preferreddate': DateInput(attrs={'size':10}),
            'name': forms.TextInput(attrs={'size':10}),
            'sessionvalue': forms.TextInput(attrs={'style':'width:5em',
                                                   'type':'number'}),
            'manager': forms.HiddenInput(),
	}

boattypes = types.boattypes
    
# Workout
class Workout(models.Model):
    workouttypes = types.workouttypes
    workoutsources = types.workoutsources
    privacychoices = types.privacychoices
    
    user = models.ForeignKey(Rower)
    team = models.ManyToManyField(Team,blank=True)
    plannedsession = models.ForeignKey(PlannedSession, blank=True,null=True)
    name = models.CharField(max_length=150)
    date = models.DateField()
    workouttype = models.CharField(choices=workouttypes,max_length=50,
                                   verbose_name='Exercise/Boat Class')
    workoutsource = models.CharField(max_length=100,
                                     default='unknown')
    boattype = models.CharField(choices=boattypes,max_length=50,
				default='1x',
				verbose_name = 'Boat Type')
    starttime = models.TimeField(blank=True,null=True)
    startdatetime = models.DateTimeField(blank=True,null=True)
    timezone = models.CharField(default='UTC',
                                choices=timezones,
                                max_length=100)
    distance = models.IntegerField(default=0,blank=True)
    duration = models.TimeField(default=1,blank=True)
    weightcategory = models.CharField(
        default="hwt",
        max_length=10,
        choices=weightcategories,
        verbose_name='Weight Category')
    weightvalue = models.FloatField(default=80.0,blank=True,verbose_name = 'Average Crew Weight (kg)')
    csvfilename = models.CharField(blank=True,max_length=150)
    uploadedtoc2 = models.IntegerField(default=0)
    averagehr = models.IntegerField(blank=True,null=True)
    maxhr = models.IntegerField(blank=True,null=True)
    uploadedtostrava = models.IntegerField(default=0)
    uploadedtosporttracks = models.IntegerField(default=0)
    uploadedtounderarmour = models.IntegerField(default=0)
    uploadedtotp = models.IntegerField(default=0)
    uploadedtorunkeeper = models.IntegerField(default=0)
    forceunit = models.CharField(default='lbs',
                                 choices = (
                                     ('lbs','lbs'),
                                     ('N','N')
                                     ),
                                 max_length=100)
    
    # empower stuff
    inboard = models.FloatField(default=0.88)
    oarlength = models.FloatField(default=2.89)
    
    
    notes = models.CharField(blank=True,null=True,max_length=1000)
    summary = models.TextField(blank=True)
    privacy = models.CharField(default='visible',max_length=30,
                               choices=privacychoices)
    rankingpiece = models.BooleanField(default=False,verbose_name='Ranking Piece')
    
    def __unicode__(self):

	date = self.date
	name = self.name
        distance = str(self.distance)
        ownerfirst = self.user.user.first_name
        ownerlast = self.user.user.last_name
        duration = self.duration
        boattype = self.boattype
        workouttype = self.workouttype

        if workouttype != 'water':
            stri = u'{d} {n} {dist}m {duration:%H:%M:%S} {workouttype} {ownerfirst} {ownerlast}'.format(
                d = date.strftime('%Y-%m-%d'),
                n = name,
                dist = distance,
                duration = duration,
                workouttype = workouttype,
                ownerfirst = ownerfirst,
                ownerlast = ownerlast,
            )
        else:
            stri = u'{d} {n} {dist}m {duration:%H:%M:%S} {workouttype} {boattype} {ownerfirst} {ownerlast}'.format(
                d = date.strftime('%Y-%m-%d'),
                n = name,
                dist = distance,
                duration = duration,
                workouttype = workouttype,
                boattype=boattype,
                ownerfirst = ownerfirst,
                ownerlast = ownerlast,
            )
            
	return stri

# delete files belonging to workout instance
# related GraphImage objects should be deleted automatically
@receiver(models.signals.post_delete,sender=Workout)
def auto_delete_file_on_delete(sender, instance, **kwargs):
    # delete CSV file
    if instance.csvfilename:
	if os.path.isfile(instance.csvfilename):
	    os.remove(instance.csvfilename)
    if instance.csvfilename+'.gz':
	if os.path.isfile(instance.csvfilename+'.gz'):
	    os.remove(instance.csvfilename+'.gz')
            
# Delete stroke data from the database when a workout is deleted
@receiver(models.signals.post_delete,sender=Workout)
def auto_delete_strokedata_on_delete(sender, instance, **kwargs):
    if instance.id:
        query = sa.text('DELETE FROM strokedata WHERE workoutid={id};'.format(
            id=instance.id,
            ))
        engine = create_engine(database_url, echo=False)
        with engine.connect() as conn, conn.begin():
            try:
                result = conn.execute(query)
            except:
                print "Database Locked"
        conn.close()
        engine.dispose()

# Virtual Race results (for keeping results when workouts are deleted)
class VirtualRaceResult(models.Model):
    boatclasses = (type for type in types.workouttypes if type[0] in types.otwtypes)
    userid = models.IntegerField(default=0)
    teamname = models.CharField(max_length=80,verbose_name = 'Team Name',
                                blank=True,null=True)
    username = models.CharField(max_length=150)
    workoutid = models.IntegerField(null=True)
    weightcategory = models.CharField(default="hwt",max_length=10,
                                      choices=weightcategories,
                                      verbose_name='Weight Category')
    race = models.ForeignKey(VirtualRace)
    duration = models.TimeField(default=datetime.time(1,0))
    distance = models.IntegerField(default=0)
    boatclass = models.CharField(choices=boatclasses,
                                   max_length=40,
                                   default='water',
                                   verbose_name = 'Boat Class')
    boattype = models.CharField(choices=boattypes,max_length=40,
                                default='1x',
                                verbose_name = 'Boat Type'
                                )
    coursecompleted = models.BooleanField(default=False)
    sex = models.CharField(default="not specified",
                           max_length=30,
                           choices=sexcategories,
                           verbose_name='Gender')

    age = models.IntegerField(null=True)

    def __unicode__(self):
        rr = Rower.objects.get(id=self.userid)
        name = '{u1} {u2}'.format(
            u1 = rr.user.first_name,
            u2 = rr.user.last_name,
            )
        if self.teamname:
            return u'Entry for {n} for "{r}" in {c} {d} with {t} ({s})'.format(
                n = name,
                r = self.race,
                d = self.boattype,
                c = self.boatclass,
                t = self.teamname,
                s = self.sex,
            )
        else:
            return u'Entry for {n} for "{r}" in {c} {d} ({s})'.format(
                n = name,
                r = self.race,
                d = self.boattype,
                c = self.boatclass,
                s = self.sex,
                )


class CourseTestResult(models.Model):
    userid = models.IntegerField(default=0)
    workoutid = models.IntegerField(null=True)
    plannedsession = models.ForeignKey(PlannedSession)
    duration = models.TimeField(default=datetime.time(1,0))
    distance = models.IntegerField(default=0)
    coursecompleted = models.BooleanField(default=False)
    
class VirtualRaceResultForm(ModelForm):
    class Meta:
	model = VirtualRaceResult
	fields = ['teamname','weightcategory','boatclass','boattype','age']


    def __init__(self, *args, **kwargs):
        boattypes = kwargs.pop('boattypes',None)
        super(VirtualRaceResultForm, self).__init__(*args, **kwargs)

        if boattypes:
            self.fields['boattype'].choices = boattypes

        self.fields['mix'] = forms.BooleanField(initial=False,
                                                required=False,
                                                label='Mixed Gender')
    
from rowers.metrics import rowingmetrics

strokedatafields = {
    'workoutid':models.IntegerField(null=True),
    'workoutstate':models.IntegerField(null=True,default=1),
    'ftime':models.CharField(max_length=30),
    'fpace':models.CharField(max_length=30),
    'hr_ut2':models.IntegerField(null=True),
    'hr_ut1':models.IntegerField(null=True),
    'hr_at':models.IntegerField(null=True),
    'hr_tr':models.IntegerField(null=True),
    'hr_an':models.IntegerField(null=True),
    'hr_max':models.IntegerField(null=True),
    'hr_bottom':models.IntegerField(null=True),
    'x_right':models.FloatField(null=True),
    'ergpace':models.FloatField(null=True),
    'nowindpace':models.FloatField(null=True),
    'equivergpower':models.FloatField(null=True),
    'fergpace':models.CharField(max_length=30),
    'fnowindpace':models.CharField(max_length=30),
}

for name,d in rowingmetrics:
    if d['numtype'] == 'float':
        try:
            strokedatafields[name] = models.FloatField(
                null=d['null'],
                default=d['default'],
                verbose_name=d['verbose_name'])
        except KeyError:
            strokedatafields[name] = models.FloatField(
                null=d['null'],
                verbose_name=d['verbose_name'])
    elif d['numtype'] == 'integer':
        try:
            strokedatafields[name] = models.IntegerField(
                null=d['null'],
                default=d['default'],
                verbose_name=d['verbose_name'])
        except KeyError:
            strokedatafields[name] = models.IntegerField(
                null=d['null'],
                verbose_name=d['verbose_name'])
class Meta:
    db_table = 'strokedata'
    index_together = ['workoutid']
    app_label = 'rowers'

attrs = {'__module__': '', 'Meta': Meta}
attrs.update(strokedatafields)
            
# Model of StrokeData table
# the definition here is used only to enable easy Django migration
# when the StrokeData are expanded.
# No Django Instances of this model are managed. Strokedata table is
# accesssed directly with SQL commands
StrokeData = type(str('StrokeData'), (models.Model,),
                  attrs
                  )

# Storing data for the OTW CP chart
class cpdata(models.Model):
    delta = models.IntegerField(default=0)
    cp = models.FloatField(default=0)
    user = models.IntegerField(default=0)

    class Meta:
        db_table = 'cpdata'
        index_together = ['user']
        app_label = 'rowers'


# Storing data for the OTW CP chart
class cpergdata(models.Model):
    delta = models.IntegerField(default=0)
    cp = models.FloatField(default=0)
    user = models.IntegerField(default=0)

    class Meta:
        db_table = 'cpergdata'
        index_together = ['user']
        app_label = 'rowers'


        
# Storing data for the OTW CP chart
class ergcpdata(models.Model):
    delta = models.IntegerField(default=0)
    cp = models.FloatField(default=0)
    distance = models.FloatField(default=0)
    user = models.IntegerField(default=0)

    class Meta:
        db_table = 'ergcpdata'
        index_together = ['user']
        app_label = 'rowers'

# A wrapper around the png files
class GraphImage(models.Model):
    filename = models.CharField(default='',max_length=150,blank=True,null=True)
    creationdatetime = models.DateTimeField()
    workout = models.ForeignKey(Workout)
    width = models.IntegerField(default=1200)
    height = models.IntegerField(default=600)

    def __str__(self):
	return self.filename

    
# delete related file object when image is deleted
@receiver(models.signals.post_delete,sender=GraphImage)
def auto_delete_image_on_delete(sender,instance, **kwargs):
    if instance.filename:
	if os.path.isfile(instance.filename):
            others = GraphImage.objects.filter(filename=instance.filename)
            if len(others) == 0:
	        os.remove(instance.filename)
	else:
	    print "couldn't find the file "+instance.filename


# Form to update Workout data
class WorkoutForm(ModelForm):
#    duration = forms.TimeInput(format='%H:%M:%S.%f')
    class Meta:
	model = Workout
	fields = ['name','date','starttime','timezone','duration','distance','workouttype','boattype','weightcategory','notes','rankingpiece']
	widgets = {
	    'date': AdminDateWidget(),
	    'notes': forms.Textarea,
	    'duration': forms.TimeInput(format='%H:%M:%S.%f'),
	    }
        
    def __init__(self, *args, **kwargs):            
        super(WorkoutForm, self).__init__(*args, **kwargs)
        self.fields['private'] = forms.BooleanField(initial=False,
                                                    required=False,
                                                    label='Private')
        if 'instance' in kwargs:
            if kwargs['instance'].privacy == 'visible':
                self.fields['private'].initial = False
            else:
                self.fields['private'].initial = True
                
# Used for the rowing physics calculations
class AdvancedWorkoutForm(ModelForm):
    quick_calc = forms.BooleanField(initial=True,required=False)
        
    class Meta:
	model = Workout
	fields = ['boattype','weightvalue','quick_calc']

class RowerExportForm(ModelForm):
    class Meta:
        model = Rower
        fields = [
            'stravaexportas',
            'polar_auto_import',
            'c2_auto_export',
            'mapmyfitness_auto_export',
            'runkeeper_auto_export',
            'sporttracks_auto_export',
            'strava_auto_export',
            'trainingpeaks_auto_export',
         ]
        
# Simple form to set rower's Functional Threshold Power
class RowerPowerForm(ModelForm):
    class Meta:
	model = Rower
	fields = ['hrftp','ftp','otwslack']

# Form to set rower's Power zones, including test routines
# to enable consistency
class RowerPowerZonesForm(ModelForm):
        
    powerzones = ['UT3','UT2','UT1','AT','TR','AN']
    ut3name = forms.CharField(initial=powerzones[0])
    ut2name = forms.CharField(initial=powerzones[1])
    ut1name = forms.CharField(initial=powerzones[2])
    atname = forms.CharField(initial=powerzones[3])
    trname = forms.CharField(initial=powerzones[4])
    anname = forms.CharField(initial=powerzones[5])
    
    def __init__(self, *args,**kwargs):
        super(RowerPowerZonesForm, self).__init__(*args, **kwargs)
        
        if 'instance' in kwargs:
            powerzones = kwargs['instance'].powerzones
        else:
            powerzones = ['UT3','UT2','UT1','AT','TR','AN']

        self.fields['ut3name'].initial = powerzones[0]
        self.fields['ut2name'].initial = powerzones[1]
        self.fields['ut1name'].initial = powerzones[2]
        self.fields['atname'].initial = powerzones[3]
        self.fields['trname'].initial = powerzones[4]
        self.fields['anname'].initial = powerzones[5]

    class Meta:
        model = Rower
        fields = ['pw_ut2','pw_ut1','pw_at','pw_tr','pw_an']

    def clean(self):
        cleaned_data = super(RowerPowerZonesForm, self).clean()

        try:
            pw_ut2 = cleaned_data['pw_ut2']
        except KeyError:
            raise ValidationError("Value cannot be empty")
        except:
            pw_ut2 = int(self.data['pw_ut2'])
        try:
            pw_ut1 = cleaned_data['pw_ut1']
        except KeyError:
            raise ValidationError("Value cannot be empty")
        except:
            pw_ut1 = int(self.data['pw_ut1'])
        try:
            pw_at = cleaned_data['pw_at']
        except KeyError:
            raise ValidationError("Value cannot be empty")
        except:
            pw_at = int(self.data['pw_at'])
        try:
            pw_tr = cleaned_data['pw_tr']
        except KeyError:
            raise ValidationError("Value cannot be empty")
        except:
            pw_tr = int(self.data['pw_tr'])
        try:
            pw_an = cleaned_data['pw_an']
        except KeyError:
            raise ValidationError("Value cannot be empty")
        except:
            pw_an = int(self.data['pw_an'])

        try:
            ut3name = cleaned_data['ut3name']
        except:
            ut2name = 'UT3'
            cleaned_data['ut3name'] = 'UT3'
        try:
            ut2name = cleaned_data['ut2name']
        except:
            ut2name = 'UT2'
            cleaned_data['ut2name'] = 'UT2'
        try:
            ut1name = cleaned_data['ut1name']
        except:
            ut1name = 'UT1'
            cleaned_data['ut1name'] = 'UT1'
        try:
            atname = cleaned_data['atname']
        except:
            atname = 'AT'
            cleaned_data['atname'] = 'AT'
        try:
            trname = cleaned_data['trname']
        except:
            trname = 'TR'
            cleaned_data['ut1name'] = 'TR'
        try:
            anname = cleaned_data['anname']
        except:
            anname = 'AN'
            cleaned_data['ut1name'] = 'AN'


        try:
            ut3name = cleaned_data['ut3name']
        except:
            ut2name = 'UT3'
            cleaned_data['ut3name'] = 'UT3'
        try:
            ut2name = cleaned_data['ut2name']
        except:
            ut2name = 'UT2'
            cleaned_data['ut2name'] = 'UT2'
        try:
            ut1name = cleaned_data['ut1name']
        except:
            ut1name = 'UT1'
            cleaned_data['ut1name'] = 'UT1'
        try:
            atname = cleaned_data['atname']
        except:
            atname = 'AT'
            cleaned_data['atname'] = 'AT'
        try:
            trname = cleaned_data['trname']
        except:
            trname = 'TR'
            cleaned_data['ut1name'] = 'TR'
        try:
            anname = cleaned_data['anname']
        except:
            anname = 'AN'
            cleaned_data['ut1name'] = 'AN'


        if pw_ut1 <= pw_ut2:
            e = "{ut1name} should be higher than {ut2name}".format(
                ut1name = ut1name,
                ut2name= ut2name,
                )
            raise forms.ValidationError(e)
        if pw_at <= pw_ut1:
            e = "{atname} should be higher than {ut1name}".format(
                atname = atname,
                ut1name= ut1name,
                )
            raise forms.ValidationError(e)
        if pw_tr <= pw_at:
            e = "{trname} should be higher than {atname}".format(
                atname = atname,
                trname= trname,
                )
            raise forms.ValidationError(e)
        if pw_an <= pw_tr:
            e = "{anname} should be higher than {trname}".format(
                anname = anname,
                trname= trname,
                )
            raise forms.ValidationError(e)

        return cleaned_data

# Form to set rower's Auto Import and Export settings
class RowerImportExportForm(ModelForm):
    class Meta:
        model = Rower
        fields = [
            'polar_auto_import',
            'c2_auto_export',
            'c2_auto_import',
            'mapmyfitness_auto_export',
            'runkeeper_auto_export',
            'sporttracks_auto_export',
            'strava_auto_export',
            'trainingpeaks_auto_export',
         ]


    
# Form to set rower's Email and Weight category
class AccountRowerForm(ModelForm):
    class Meta:
        model = Rower
        fields = ['sex','birthdate','weightcategory',
                  'getemailnotifications',
                  'getimportantemails',
                  'defaulttimezone','showfavoritechartnotes',
                  'defaultlandingpage']

	widgets = {
	    'birthdate': SelectDateWidget(
                years=range(
                    timezone.now().year-100,timezone.now().year-10)),
            }


    def clean_email(self):
        email = self.cleaned_data.get('email')

        try:
            validate_email(email)
        except ValidationError:
            raise forms.ValidationError(
                'Please enter a valid email address')
            
        try:
            match = User.objects.get(email__iexact=email)
            if self.instance.user == match:
                return email
        except User.DoesNotExist:
            return email

        raise forms.ValidationError('This email address is not allowed')
        
        

class UserForm(ModelForm):
    class Meta:
        model = User
        fields = ['first_name','last_name','email']

    def clean_first_name(self):
        first_name = self.cleaned_data.get('first_name')

        if len(first_name):
            return first_name

        raise forms.ValidationError('Please fill in your first name')
        
    def clean_email(self):
        email = self.cleaned_data.get('email')

        try:
            validate_email(email)
        except ValidationError:
            raise forms.ValidationError(
                'Please enter a valid email address')
            
        try:
            match = User.objects.filter(email__iexact=email)
            if self.instance in match:
                return email
        except User.DoesNotExist:
            return email

        raise forms.ValidationError('This email address is not allowed')

        
# Form to set rower's Heart Rate zones, including test routines
# to enable consistency
class RowerForm(ModelForm):
    class Meta:
	model = Rower
	fields = ['rest','ut2','ut1','at','tr','an','max']


    def clean_rest(self):
	rest = self.cleaned_data['rest']

	if rest<10:
	    self.data['rest']=10
	    raise forms.ValidationError("Resting heart rate should be higher than 10 bpm")
	

	if rest>250:
	    self.data['rest'] = 250
	    raise forms.ValidationError("Resting heart rate should be lower than 250 bpm")


	return rest

    def clean_ut2(self):
	ut2 = self.cleaned_data['ut2']

	if ut2<10:
	    raise forms.ValidationError("UT2 heart rate should be higher than 10 bpm")

	if ut2>250:
	    raise forms.ValidationError("UT2 heart rate should be lower than 250 bpm")

	return ut2

    def clean_ut1(self):
	ut1 = self.cleaned_data['ut1']

	if ut1<10:
	    raise forms.ValidationError("UT1 heart rate should be higher than 10 bpm")

	if ut1>250:
	    raise forms.ValidationError("Resting heart rate should be lower than 250 bpm")

	return ut1

    def clean_at(self):
	at = self.cleaned_data['at']

	if at<10:
	    raise forms.ValidationError("AT heart rate should be higher than 10 bpm")

	if at>250:
	    raise forms.ValidationError("AT heart rate should be lower than 250 bpm")

	return at

    def clean_tr(self):
	tr = self.cleaned_data['tr']

	if tr<10:
	    raise forms.ValidationError("TR heart rate should be higher than 10 bpm")

	if tr>250:
	    raise forms.ValidationError("TR heart rate should be lower than 250 bpm")

	return tr

    def clean_an(self):
	an = self.cleaned_data['an']

	if an<10:
	    raise forms.ValidationError("AN heart rate should be higher than 10 bpm")

	if an>250:
	    raise forms.ValidationError("AN heart rate should be lower than 250 bpm")

	return an

    def clean_max(self):
	max = self.cleaned_data['max']

	if max<10:
	    raise forms.ValidationError("Max heart rate should be higher than 10 bpm")

	if max>250:
	    raise forms.ValidationError("Max heart rate should be lower than 250 bpm")

	return max


    def clean(self):

	
	try:
	    rest = self.cleaned_data['rest']
	except:
            try:
	        rest = int(self.data['rest'])
            except ValueError:
                rest = 0

	try:
	    ut2 = self.cleaned_data['ut2']
	except:
            try:
	        ut2 = self.data['ut2']
            except ValueError:
                ut2 = 0

	try:
	    ut1 = self.cleaned_data['ut1']
	except:
            try:
	        ut1 = self.data['ut1']
            except ValueError:
                ut1 = 0

	try:
	    at = self.cleaned_data['at']
	except:
            try:
	        at = self.data['at']
            except ValueError:
                at = 0

	try:
	    an = self.cleaned_data['an']
	except:
            try:
	        an = self.data['an']
            except ValueError:
                an = 0

	try:
	    tr = self.cleaned_data['tr']
	except:
            try:
	        tr = self.data['tr']
            except ValueError:
                tr = 0

	try:
	    max = self.cleaned_data['max']
	except:
            try:
	        max = self.data['max']
            except ValueError:
                max = 0
	        
	if rest>=ut2:
	    raise forms.ValidationError("Resting heart rate should be lower than UT2")
	if ut2>=ut1:
	    raise forms.ValidationError("UT2 should be lower than UT1")
	if ut2>=ut1:
	    raise forms.ValidationError("UT2 should be lower than UT1")
	if ut1>=at:
	    raise forms.ValidationError("UT1 should be lower than AT")
	if at>=tr:
	    raise forms.ValidationError("AT should be lower than TR")
	if tr>=an:
	    raise forms.ValidationError("TR should be lower than AN")
	if an>=max:
	    raise forms.ValidationError("AN should be lower than Max")

        
# An announcement that goes to the right of the workouts list
# optionally sends a tweet to our twitter account
class SiteAnnouncement(models.Model):
    created = models.DateField(default=timezone.now)
    announcement = models.TextField(max_length=280)
    expires = models.DateField(default=timezone.now)
    modified = models.DateField(default=timezone.now)
    dotweet = models.BooleanField(default=False)

    def save(self, *args, **kwargs):
        if not self.id:
            self.created = timezone.now()
            self.expires = timezone.now()+datetime.timedelta(days=10)
        self.modified = timezone.now()
        if self.dotweet:
            try:
                status = tweetapi.PostUpdate(self.announcement)
            except:
                try:
                    status = tweetapi.PostUpdate(self.announcement[:270])
                except:
                    pass
        return super(SiteAnnouncement,self).save(*args, **kwargs)

# A comment by a user on a training
class WorkoutComment(models.Model):
    comment = models.TextField(max_length=300)
    created = models.DateTimeField(default=timezone.now)
    read = models.BooleanField(default=False)
    notification = models.BooleanField(default=True,verbose_name="Subscribe to new comment notifications")
    user = models.ForeignKey(User)
    workout = models.ForeignKey(Workout)

    def __unicode__(self):
        return u'Comment to: {w} by {u1} {u2}'.format(
            w=self.workout,
            u1 = self.user.first_name,
            u2 = self.user.last_name,
            )
        
    
class WorkoutCommentForm(ModelForm):
    class Meta:
        model = WorkoutComment
        fields = ['comment','notification']
	widgets = {
	    'comment': forms.Textarea,
	    }

