#!/usr/bin/python
#By Rhys McCaig @mccaig / mccaig@gmail.com

import sys
import json
import logging
import re
from datetime import datetime, timedelta
import xml.etree.ElementTree as ET
import thread

import numpy
import requests


class NikePlus:
	"""Class for working with the Nike+ API"""
	#Nike+ Settings

	#You might need to adjust these three below in the future to match your developer credentials. These work fine as of August 2013.
	nikeplus_app_id = 'ONEPLUSSDKSAMPLE'
	nikeplus_client_id = 'c002b5e3fd045be3bf357c8534edb38b'
	nikeplus_client_secret =  'd7c94a86a6a389c4'

	#Probably shouldnt touch anything below here, unless something drastically changes
	nikeplus_base_url = 'https://api.nike.com'

	nikeplus_headers = {	'Appid' : nikeplus_app_id,
				'Accept' : 'application/json'}

	nikeplus_authentication_parameters = {'app' : nikeplus_app_id,
				'client_id' : nikeplus_client_id,
				'client_secret' : nikeplus_client_secret}

	nikeplus_endpoints = {	'aggregate_sports_data' : 	nikeplus_base_url+'/me/sport',
				'list_activities' : 	  	nikeplus_base_url+'/me/sport/activities',
				'activity_detail' :		nikeplus_base_url+'/me/sport/activities/%(activity_id)s',
				'gps_data' : 			nikeplus_base_url+'/me/sport/activities/%(activity_id)s/gps',
				'login' :			nikeplus_base_url+'/nsl/v2.0/user/login'}

	nikeplus_activity_list_limit = 50 #API shits itself if we set this too high
	nikeplus_timeout_seconds = 30

	def __init__(self):
		self.logger = logging.getLogger('nikeplus.NikePlus')
		self.id = id(self)
		self.logger.info('[{i}] {f}({m})'.format(i=self.id, f=str(sys._getframe().f_code.co_name), m="Creating Instance"))
		self.cookie = None
		self.authentication_data = {}
		self.token = None
		self.session = requests.Session()
		self.session.headers.update(self.nikeplus_headers)
		self.aggregate_data = None
		self.activities = {}
		return

	#Authentication - Only implement Nike+ Login at this stage (email+password)
	def Authenticate(self,username=None,password=None):
		if username == None:
			self.logger.error("[{i}] {f}({m})".format(i=self.id, f=str(sys._getframe().f_code.co_name), m="No Username"))
			raise NikePlusError("Cant Authenticate() without a Username")
		if password == None:
			self.logger.error("[{i}] {f}({m})".format(i=self.id, f=str(sys._getframe().f_code.co_name), m="No Password"))
			raise NikePlusError("Cant Authenticate() without a Password")
		authentication_payload = {'email':username,'password':password}
		r = self.session.post(self.nikeplus_endpoints['login'], data=authentication_payload, params=self.nikeplus_authentication_parameters, headers=self.nikeplus_headers,timeout=self.nikeplus_timeout_seconds)
		self.logger.debug("[{i}] {f}({m})".format(i=self.id, f=str(sys._getframe().f_code.co_name), m="Request: "+str([r.url,r.request.headers])))
		self.logger.debug("[{i}] {f}({m})".format(i=self.id, f=str(sys._getframe().f_code.co_name), m="Response: "+str([r.headers,r.text])))
		try:
			self.authentication_data = get_json_auth_data(r)
			self.logger.debug("[{i}] {f}({m})".format(i=self.id, f=str(sys._getframe().f_code.co_name), m="Setting Token: "+str(self.authentication_data['access_token'])))
			self.token = self.authentication_data['access_token']
		except:
			#If json parsing fails then it was probably a bad username/password combo
			self.logger.warn("[{i}] {f}({m})".format(i=self.id, f=str(sys._getframe().f_code.co_name), m=r.url+" "+str(sys.exc_info()[0])))
			self.Token = None
		return self

	#You can manually set an oAuth token here if you have one from a previous session

	#Get aggregate workout data
	def RetrieveAggregateData(self):
		if self.token is None:
			self.logger.error("[{i}] {f}({m})".format(i=self.id, f=str(sys._getframe().f_code.co_name), m="No Auth Token set"))
			raise NikePlusError("No Auth Token - Try to Authenticate() or manually set a NikePlus.token first")
		else:
			parameters = {'access_token':self.token}
			r = self.session.get(self.nikeplus_endpoints['aggregate_sports_data'], params=parameters, headers=self.nikeplus_headers,timeout=self.nikeplus_timeout_seconds)
			self.logger.debug("[{i}] {f}({m})".format(i=self.id, f=str(sys._getframe().f_code.co_name), m="Request: "+str([r.url,r.request.headers])))
			self.logger.debug("[{i}] {f}({m})".format(i=self.id, f=str(sys._getframe().f_code.co_name), m="Response: "+str([r.headers,r.text])))
			try:
				self.aggregate_data = r.json()
				self.logger.info("[{i}] {f}({m})".format(i=self.id, f=str(sys._getframe().f_code.co_name), m="Retrieved Data"))
			except:
				self.logger.error("[{i}] {f}({m})".format(i=self.id, f=str(sys._getframe().f_code.co_name), m=r.url+" "+str(sys.exc_info()[0])))
				raise
		return self

	#Retrieve activity list/summaries
	def RetrieveActivities(self,limit=100000,offset=1):
		count = self.nikeplus_activity_list_limit #Nike+ API shits itself if you try to request more than 500 or so, keeping this relatively low to be safe. We have to page through results.
		self.logger.info("[{i}] [{t}] [{f}] ({m})".format(i=self.id, t=thread.get_ident(), f=str(sys._getframe().f_code.co_name), m="Retrieving activities in batches of {c}".format(c=count)))
		activities = {}
		eof = False
		if self.token is None:
			self.logger.error("[{i}] {f}({m})".
format(i=self.id, f=str(sys._getframe().f_code.co_name), m="No Auth Token set"))
			raise NikePlusError("No Auth Token - Try to Authenticate() or manually set a NikePlus.token first")
		else:
			while (len(activities)<limit and not eof):
				parameters = {'access_token':self.token,'count':count,'offset':offset}
				r = self.session.get(self.nikeplus_endpoints['list_activities'], params=parameters, headers=self.nikeplus_headers,timeout=self.nikeplus_timeout_seconds)
				
self.logger.debug("[{i}] [{t}] [{f}] ({m})".format(i=self.id, t=thread.get_ident(), f=str(sys._getframe().f_code.co_name), m="Request: "+str([r.url,r.request.headers])))
				self.logger.debug("[{i}] [{t}] [{f}] ({m})".format(i=self.id, t=thread.get_ident(), f=str(sys._getframe().f_code.co_name), m="Response: "+str([r.headers,r.text])))
				try:
					data = r.json()
					if "data" in data:
						self.logger.debug("[{i}] [{t}] [{f}] ({m})".format(i=self.id, t=thread.get_ident(), f=str(sys._getframe().f_code.co_name), m="Retreived {c} activities".format(c=len(data['data']))))
						for a in data['data']:
							if len(activities)<limit:
								activities[str(a['activityId'])] = a #so we can search by activityId
								activities[str(a['activityId'])]['gps'] = None
						if len(data['data']) == 0 or len(activities) % count != 0:
							eof = True
					else:
						eof = True
					offset += count
				except:
					self.logger.error("[{i}] [{t}] [{f}] ({m})".format(i=self.id, t=thread.get_ident(), f=str(sys._getframe().f_code.co_name), m=r.url+" "+str(sys.exc_info()[0])))
					raise
		self.activities = activities
		self.logger.info("[{i}] [{t}] [{f}] ({m})".format(i=self.id, t=thread.get_ident(), f=str(sys._getframe().f_code.co_name), m="Retreived a total of {c} activities".format(c=len(self.activities))))
		return self

	#Downloads details for an individual Activity
	def GetActivityDetails(self,activity_id):
		if self.token is None:
			self.logger.error("[{i}] [{t}] [{f}] ({m})".format(i=self.id, t=thread.get_ident(), f=str(sys._getframe().f_code.co_name), m="No Auth Token set"))
			raise NikePlusError("No Auth Token - Try to Authenticate() or manually set a NikePlus.token first")
		else:
			self.activities.setdefault(str(activity_id),{})	#If not being invoked after a RetrieveActivities, (if manually setting the activity_id) activity_id wont yet be set
			parameters = {'access_token':self.token,'activityId':activity_id}
			#Note we have to replace activityID into the endpoint in the below code

			self.logger.info("[{i}] [{t}] [{f}] ({m})".format(i=self.id, t=thread.get_ident(), f=str(sys._getframe().f_code.co_name), m="Request: "+ self.nikeplus_endpoints['activity_detail'] % {'activity_id' : activity_id}))
			r = self.session.get(self.nikeplus_endpoints['activity_detail'] % {'activity_id' : activity_id}, params=parameters, headers=self.nikeplus_headers,timeout=self.nikeplus_timeout_seconds)
			self.logger.info("[{i}] [{