'''
@author: Antorweep Chakravorty
'''
import googlemaps
import rapidjson as json
import pickle
import pprint

from datetime import datetime
from vertex import vertex
from edge import edge

class GoogleMapsAPI(object):
  def __init__(self, username, password, app_id, app_token):      
      self.username = username
      self.password = password
      self.app_id = app_id
      self.api_token = app_token
      self.gmaps = googlemaps.Client(key=self.api_token)

  @classmethod
  def from_credentials_file(self, file_name):
      f = open(file_name)
      credentials = json.loads(f.read())["google"]
      args = tuple(credentials[key] for key in ('username','password', 'app_id' ,'api_token'))
      return GoogleMapsAPI(*args)

  def decode_line(self, encoded):

      """Decodes a polyline that was encoded using the Google Maps method.

      See http://code.google.com/apis/maps/documentation/polylinealgorithm.html
      
      This is a straightforward Python port of Mark McClure's JavaScript polyline decoder
      (http://facstaff.unca.edu/mcmcclur/GoogleMaps/EncodePolyline/decode.js)
      and Peter Chng's PHP polyline decode
      (http://unitstep.net/blog/2008/08/02/decoding-google-maps-encoded-polylines-using-php/)
      """

      encoded_len = len(encoded)
      index = 0
      array = []
      lat = 0
      lng = 0

      while index < encoded_len:

          b = 0
          shift = 0
          result = 0

          while True:
              b = ord(encoded[index]) - 63
              index = index + 1
              result |= (b & 0x1f) << shift
              shift += 5
              if b < 0x20:
                  break

          dlat = ~(result >> 1) if result & 1 else result >> 1
          lat += dlat

          shift = 0
          result = 0

          while True:
              b = ord(encoded[index]) - 63
              index = index + 1
              result |= (b & 0x1f) << shift
              shift += 5
              if b < 0x20:
                  break

          dlng = ~(result >> 1) if result & 1 else result >> 1
          lng += dlng

          array.append((lat * 1e-5, lng * 1e-5))

      return array

  def getGeoCode(self, address):
      coordinates = self.gmaps.geocode(address)[0]['geometry']['location']
      return str(coordinates["lat"]) + "," + str(coordinates["lng"])

  def getAddress(self, longitude, latitude):
      return self.gmaps.reverse_geocode((latitude, longitude))

  def getRoutes(self, origin, destination, departBy, alternatives):            

      #Store all routes returned by GoogleMap API
      graphs = []

      directions_result = self.gmaps.directions(origin,
                                      destination,
                                      mode="transit",
                                      departure_time=departBy,
                                      alternatives=alternatives)
      #print(json.dumps(directions_result, indent=2))
      #itterate through each route 
      for d in directions_result:
          #describes the legs of the journey. 
          #For routes that contain no waypoints, the route will consist of a single "leg,"
          #but for routes that define one or more waypoints, 
          #the route will consist of one or more legs
          legs = d["legs"]
          
          #Store the vertexes
          vertices = []
          #Store the edges
          edges = []
          for l in legs: 
              #Contains san array of steps denoting information about each seperate step of the leg
              steps = l["steps"]
              for s in steps:
                  travel_mode = s["travel_mode"]
                  html_instructions = s["html_instructions"]

                  if travel_mode == "WALKING":
                      walking_steps = s["steps"]
                      for ws in walking_steps:
                          start_location = ws["start_location"]
                          end_location = ws["end_location"]
                          html_instructions = (ws["html_instructions"] if "html_instructions" in ws else "")
                          distance = ws["distance"]["value"]
                          duration = ws["duration"]["value"]

                          start_lat = start_location["lat"]
                          start_lng = start_location["lng"]
                          start_vertex_id = hash(str(start_lat)+str(start_lng)) 
                          
                          end_lat = end_location["lat"]
                          end_lng = end_location["lng"]
                          end_vertex_id = hash(str(end_lat)+str(end_lng))                         

                          start_vertex = vertex(start_vertex_id, start_lat, start_lng, "false")                      
                          end_vertex = vertex(end_vertex_id, end_lat, end_lng, "false")                                

                          connection = edge(start_vertex_id, end_vertex_id, travel_mode,
                          distance, duration)

                          vertices.append(start_vertex.getVertex())
                          vertices.append(end_vertex.getVertex())
                          edges.append(connection.getEdge())                        
                  elif travel_mode == "TRANSIT":
                      transit_details = s["transit_details"]
                      headsign = transit_details["headsign"]
                      departure_stop = transit_details["departure_stop"]
                      arrival_stop = transit_details["arrival_stop"]                     
                      departure_time = transit_details["departure_time"]
                      arrival_time = transit_details["arrival_time"]
                      num_stops = transit_details["num_stops"]
                      short_name = transit_details["line"]                                                            

                      start_location = s["start_location"]
                      end_location = s["end_location"]
                      stops_coordinates = self.decode_line(s["polyline"]["points"])

                      start_lat = start_location["lat"]
                      start_lng = start_location["lng"]
                      start_vertex_id = hash(str(start_lat)+str(start_lng))                                                             
                      end_lat = end_location["lat"]
                      end_lng = end_location["lng"]
                      end_vertex_id = hash(str(end_lat)+str(end_lng))

                      start_vertex = vertex(start_vertex_id, start_lat, start_lng, "true")
                      end_vertex = vertex(end_vertex_id, end_lat, end_lng, "true")                                        

                      distance = s["distance"]["value"]/num_stops
                      duration = s["duration"]["value"]/num_stops

                      vertices.append(start_vertex.getVertex())
                      vertices.append(end_vertex.getVertex())

                      prev_vertex_id = start_vertex_id                    

                      for stop in stops_coordinates:
                          lat = stop[0]
                          lng = stop[1]
                          vertex_id = hash(str(lat)+str(lng))
                          stop_vertex = vertex(vertex_id, lat, lng, "true")
          
                          connection = edge(prev_vertex_id, vertex_id, travel_mode,
                          distance, duration)
                          prev_vertex_id = vertex_id

                          vertices.append(stop_vertex.getVertex())
                          edges.append(connection.getEdge())                                            
                                              
                      connection = edge(prev_vertex_id, end_vertex, travel_mode,
                          distance, duration)
                      
                      edges.append(connection.getEdge())                    
                  #TODO: DRIVING 
          graphs.append((vertices, edges))
          return graphs
      
    

#if __name__ == "__main__":
   #main()
api = GoogleMapsAPI.from_credentials_file('credentials.json')
# Geocoding an address
origin = api.getGeoCode('Randaberg')
destination = api.getGeoCode('Kjell Arholmsgate 41, 4036 Stavanger, NO')
now = datetime.now()
#depart = datetime(2016, 11, 22, 12, 0, 0)
routes = api.getRoutes(origin, destination, now, "false")
with open("graphs.out", 'wb') as f:
          pickle.dump(routes, f)
