# coding=utf-8

import requests, threading, time, json, six, math
from datetime import datetime

# This really should be written using multiprocessing instead,
# because of the GIL, but setting up full 2-way communication 
# like that is a pain
#
# Also, this is fairly naive implementation to begin with, as
# far as timing goes
class PollingThreadReader(object):
	endpoint = None
	pollTime = None

	requestHeaders = None

	_running = False
	_response = None
	_modified = datetime.utcfromtimestamp(0)

	def __init__(self, endpointURI, userAgent, pollFreq = 3):
		self.endpoint = endpointURI
		self.pollTime = pollFreq
		self.requestHeaders = { 'User-Agent': userAgent }

		self._lock = threading.RLock()

		self._thread = threading.Thread(target = self.run, args = ())
		self._thread.daemon = True

	# this is from Google transitfeed utils, copied here to avoid dependency
	def _approximateDist(self, lat1, lon1, lat2, lon2):
		earthRadius = 6378135 # meters
		lat1 = math.radians(lat1)
		lon1 = math.radians(lon1)
		lat2 = math.radians(lat2)
		lon2 = math.radians(lon2)

		dlat = math.sin(0.5 * (lat2 - lat1))
		dlon = math.sin(0.5 * (lon2 - lon1))
		x = dlat * dlat + dlon * dlon * math.cos(lat1) * math.cos(lat2)
		return earthRadius * (2 * math.atan2(math.sqrt(x), math.sqrt(max(0.0, 1.0 - x))))

	def start(self):
		self._running = True
		self._thread.start()

	def join(self):
		self._running = False
		self._thread.join()

	def run(self):
		while self._running:
			response = requests.get(self.endpoint, headers = self.requestHeaders).content
			with self._lock:
				try:
					self._response = json.loads(response)
					self._modified = datetime.utcnow()
				except ValueError:
					pass

			time.sleep(self.pollTime)

	def getLastResponse(self):
		with self._lock:
			return (self._modified, self._response)

	def getLocations(self, lines):
		pass

	def getNearbyVehicles(self, lat, lon, maxDist):
		pass

class FoliLocator(PollingThreadReader):
	def _linePredicate(self, vehicle, lines):
		line = vehicle['publishedlinename']
		return line in lines

	def _distancePredicate(self, vehicle, lat, lon, maxDist):
		if not vehicle['monitored']:
			return False

		distance = self._approximateDist(lat, lon, vehicle['latitude'], vehicle['longitude'])
		return distance <= maxDist

	def getLocations(self, lines):
		modified, data = self.getLastResponse()
		if not data or data.get('status', None) != 'OK':
			return (modified, {})

		data = data['result']
		vehicles = data.get('vehicles', None)
		if not vehicles:
			return (modified, {})

		if isinstance(lines, six.string_types):
			lines = [lines]

		return (modified, {v['vehicleref']:v for v in vehicles.values() if self._linePredicate(v, lines)})

	def getNearbyVehicles(self, lat, lon, maxDist):
		modified, data = self.getLastResponse()
		if not data or data.get('status', None) != 'OK':
			return (modified, {})

		data = data['result']
		vehicles = data.get('vehicles', None)
		if not vehicles:
			return (modified, {})

		return (modified, {v['vehicleref']:v for v in vehicles.values() if self._distancePredicate(v, lat, lon, maxDist)})
