# All the functionality needed to connect to Strava

# Python
import oauth2 as oauth
import cgi
import requests
import requests.auth
import json
from django.utils import timezone
from datetime import datetime
import numpy as np
from dateutil import parser
import time
import math
from math import sin,cos,atan2,sqrt
import os,sys
import gzip

# Django
from django.shortcuts import render_to_response
from django.http import HttpResponseRedirect, HttpResponse,JsonResponse
from django.conf import settings
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.models import User
from django.contrib.auth.decorators import login_required

# Project
# from .models import Profile
from rowingdata import rowingdata
import pandas as pd
from rowers.models import Rower,Workout
from rowers.models import checkworkoutuser

import stravalib
from stravalib.exc import ActivityUploadFailed,TimeoutExceeded

from rowsandall_app.settings import C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET, STRAVA_CLIENT_ID, STRAVA_REDIRECT_URI, STRAVA_CLIENT_SECRET

# Exponentially weighted moving average
# Used for data smoothing of the jagged data obtained by Strava
# See bitbucket issue 72
def ewmovingaverage(interval,window_size):
    # Experimental code using Exponential Weighted moving average

    try:
        intervaldf = pd.DataFrame({'v':interval})
        idf_ewma1 = intervaldf.ewm(span=window_size)
        idf_ewma2 = intervaldf[::-1].ewm(span=window_size)
        
        i_ewma1 = idf_ewma1.mean().ix[:,'v']
        i_ewma2 = idf_ewma2.mean().ix[:,'v']

        interval2 = np.vstack((i_ewma1,i_ewma2[::-1]))
        interval2 = np.mean( interval2, axis=0) # average
    except ValueError:
        interval2 = interval

    return interval2

from utils import geo_distance


# Custom exception handler, returns a 401 HTTP message
# with exception details in the json data
def custom_exception_handler(exc,message):

    response = {
	"errors": [
	    {
		"code": str(exc),
		"detail": message,
		}
	    ]
	}

    res = HttpResponse(message)
    res.status_code = 401
    res.json = json.dumps(response)

    return res

# Custom error class - to raise a NoTokenError
class StravaNoTokenError(Exception):
    def __init__(self,value):
	self.value=value

    def __str__(self):
	return repr(self.value)


# Exchange access code for long-lived access token
def get_token(code):
    client_auth = requests.auth.HTTPBasicAuth(STRAVA_CLIENT_ID, STRAVA_CLIENT_SECRET)
    post_data = {"grant_type": "authorization_code",
                 "code": code,
                 "redirect_uri": STRAVA_REDIRECT_URI,
		 "client_secret": STRAVA_CLIENT_SECRET,
		 "client_id":STRAVA_CLIENT_ID,
		 }
    headers = {'user-agent': 'sanderroosendaal'}
    response = requests.post("https://www.strava.com/oauth/token",
                             data=post_data,
			     headers=headers)
    try:
        token_json = response.json()
        thetoken = token_json['access_token']
    except KeyError:
        thetoken = 0

    return [thetoken]

# Make authorization URL including random string
def make_authorization_url(request):
    # Generate a random string for the state parameter
    # Save it for use later to prevent xsrf attacks
    from uuid import uuid4
    state = str(uuid4())

    params = {"client_id": STRAVA_CLIENT_ID,
              "response_type": "code",
              "redirect_uri": STRAVA_REDIRECT_URI,
	      "scope":"write"}
    import urllib
    url = "https://www.strava.com/oauth/authorize" +urllib.urlencode(params)

    return HttpResponseRedirect(url)

# Get list of workouts available on Strava
def get_strava_workout_list(user):
    r = Rower.objects.get(user=user)
    if (r.stravatoken == '') or (r.stravatoken is None):
	s = "Token doesn't exist. Need to authorize"
	return custom_exception_handler(401,s)
    else:
	# ready to fetch. Hurray
	authorizationstring = str('Bearer ' + r.stravatoken)
	headers = {'Authorization': authorizationstring,
		   'user-agent': 'sanderroosendaal',
		   'Content-Type': 'application/json'}
	url = "https://www.strava.com/api/v3/athlete/activities"
	s = requests.get(url,headers=headers)

    return s

# Get a Strava workout summary data and stroke data by ID
def get_strava_workout(user,stravaid):
    r = Rower.objects.get(user=user)
    if (r.stravatoken == '') or (r.stravatoken is None):
	s = "Token doesn't exist. Need to authorize"
	return custom_exception_handler(401,s)	
    else:
	# ready to fetch. Hurray
	fetchresolution = 'high'
        series_type = 'time'
	authorizationstring = str('Bearer ' + r.stravatoken)
	headers = {'Authorization': authorizationstring,
		   'user-agent': 'sanderroosendaal',
		   'Content-Type': 'application/json',
		   'resolution': 'medium',}
	url = "https://www.strava.com/api/v3/activities/"+str(stravaid)
	workoutsummary = requests.get(url,headers=headers).json()

	workoutsummary['timezone'] = "Etc/UTC"
	startdatetime = workoutsummary['start_date']

	url = "https://www.strava.com/api/v3/activities/"+str(stravaid)+"/streams/cadence?resolution="+fetchresolution+"&series_type="+series_type
	spmjson = requests.get(url,headers=headers)
	url = "https://www.strava.com/api/v3/activities/"+str(stravaid)+"/streams/heartrate?resolution="+fetchresolution+"&series_type="+series_type
	hrjson = requests.get(url,headers=headers)
	url = "https://www.strava.com/api/v3/activities/"+str(stravaid)+"/streams/time?resolution="+fetchresolution+"&series_type="+series_type
	timejson = requests.get(url,headers=headers)
	url = "https://www.strava.com/api/v3/activities/"+str(stravaid)+"/streams/velocity_smooth?resolution="+fetchresolution+"&series_type="+series_type
	velojson = requests.get(url,headers=headers)
	url = "https://www.strava.com/api/v3/activities/"+str(stravaid)+"/streams/distance?resolution="+fetchresolution+"&series_type="+series_type
	distancejson = requests.get(url,headers=headers)
	url = "https://www.strava.com/api/v3/activities/"+str(stravaid)+"/streams/latlng?resolution="+fetchresolution+"&series_type="+series_type
	latlongjson = requests.get(url,headers=headers)

        try:
	    t = np.array(timejson.json()[0]['data'])
	    nr_rows = len(t)
	    d =  np.array(distancejson.json()[1]['data'])
            if nr_rows == 0:
                return (0,"Error: Time data had zero length")
        except IndexError:
            return (0,"Error: No Distance information in the Strava data")
        except KeyError:
            return (0,"something went wrong with the Strava import")

	try:
	    spm = np.array(spmjson.json()[1]['data'])
	except:
	    spm = np.zeros(nr_rows)

	try:
	    hr = np.array(hrjson.json()[1]['data'])
	except IndexError:
	    hr = np.zeros(nr_rows)
        except KeyError:
	    hr = np.zeros(nr_rows)

	try:
	    velo = np.array(velojson.json()[1]['data'])
	except IndexError:
	    velo = np.zeros(nr_rows)
        except KeyError:
            velo = np.zeros(nr_rows)

	dt = np.diff(t).mean()
	wsize = round(5./dt)

	velo2 = ewmovingaverage(velo,wsize)
	coords = np.array(latlongjson.json()[0]['data'])
	try:
	    lat = coords[:,0]
	    lon = coords[:,1]
	except IndexError:
	    lat = np.zeros(len(t))
	    lon = np.zeros(len(t))
        except KeyError:
	    lat = np.zeros(len(t))
	    lon = np.zeros(len(t))




	strokelength = velo*60./(spm)
	strokelength[np.isinf(strokelength)] = 0.0

	pace = 500./(1.0*velo2)
	pace[np.isinf(pace)] = 0.0

	df = pd.DataFrame({'t':10*t,
			   'd':10*d,
			   'p':10*pace,
			   'spm':spm,
			   'hr':hr,
			   'lat':lat,
			   'lon':lon,
			   'strokelength':strokelength,
			   })

	# startdatetime = datetime.datetime.strptime(startdatetime,"%Y-%m-%d-%H:%M:%S")

	return [workoutsummary,df]

# Generate Workout data for Strava (a TCX file)
def createstravaworkoutdata(w,dozip=True):
    filename = w.csvfilename

    row = rowingdata(filename)
    tcxfilename = filename[:-4]+'.tcx'
    try:
        newnotes = w.notes+'\n from '+w.workoutsource+' via rowsandall.com'
    except TypeError:
        newnotes = 'from '+w.workoutsource+' via rowsandall.com'

    row.exporttotcx(tcxfilename,notes=newnotes)
    if dozip:
        gzfilename = tcxfilename+'.gz'
        with file(tcxfilename,'rb') as inF:
            s = inF.read()
            with gzip.GzipFile(gzfilename,'wb') as outF:
                outF.write(s)

        try:
            os.remove(tcxfilename)
        except WindowError:
            pass

        return gzfilename,""

    else:
        return tcxfilename,""


# Upload the TCX file to Strava and set the workout activity type
# to rowing on Strava
def handle_stravaexport(f2,workoutname,stravatoken,description=''):
    # w = Workout.objects.get(id=workoutid)
    client = stravalib.Client(access_token=stravatoken)

    act = client.upload_activity(f2,'tcx.gz',name=workoutname)
    try:
        res = act.wait(poll_interval=5.0,timeout=30)
        message = 'Workout successfully synchronized to Strava'
    except:
        res = 0
        message = 'Strava upload timed out'


    # description doesn't work yet. Have to wait for stravalib to update
    if res:
        act = client.update_activity(res.id,activity_type='Rowing',description=description,device_name='Rowsandall.com')
    else:
        message = 'Strava activity update timed out.'
        return (0,message)

    return (res.id,message)


def workout_strava_upload(user,w):
    message = "Uploading to Strava"
    stravaid=-1
    r = Rower.objects.get(user=user)
    res = -1
    if (r.stravatoken == '') or (r.stravatoken is None):
	s = "Token doesn't exist. Need to authorize"
        raise StravaNoTokenError("Your hovercraft is full of eels")
    else:
	if (checkworkoutuser(user,w)):
            try:
	        tcxfile,tcxmesg = createstravaworkoutdata(w)
                if tcxfile:
		    with open(tcxfile,'rb') as f:
		        res,mes = handle_stravaexport(f,w.name,
						      r.stravatoken,
						      description=w.notes+'\n from '+w.workoutsource+' via rowsandall.com')
                        if res==0:
		            message = mes
		            w.uploadedtostrava = -1
                            stravaid = -1
		            w.save()
                            try:
		                os.remove(tcxfile)
                            except WindowsError:
                                pass
                            return message,stravaid
                                
        	        w.uploadedtostrava = res
		        w.save()
                        try:
		            os.remove(tcxfile)
                        except WindowsError:
                            pass
                        message = mes
                        stravaid = res
                        return message,stravaid
                else:
                    message = "Strava TCX data error "+tcxmesg
                    w.uploadedtostrava = -1
                    stravaid = -1
                    w.save()
                    return message, stravaid

	    except ActivityUploadFailed as e:
		message = "Strava Upload error: %s" % e
		w.uploadedtostrava = -1
                stravaid = -1
		w.save()
		os.remove(tcxfile)
                return message,stravaid
	return message,stravaid
    return message,stravaid
