import folium
import pandas as pd
import numpy as np
import json
from itertools import combinations,permutations
import requests
from datetime import datetime
from python_tsp.exact import solve_tsp_dynamic_programming
import geojson
from geojson import Feature,FeatureCollection,MultiLineString

mydf=pd.read_excel('POI.xlsx')
mydf.reset_index(drop=True, inplace=True)

om_token='eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOjcyMjIsInVzZXJfaWQiOjcyMjIsImVtYWlsIjoiZTA1NTQwNTZAdS5udXMuZWR1IiwiZm9yZXZlciI6ZmFsc2UsImlzcyI6Imh0dHA6XC9cL29tMi5kZmUub25lbWFwLnNnXC9hcGlcL3YyXC91c2VyXC9zZXNzaW9uIiwiaWF0IjoxNjE2NjQ4NDczLCJleHAiOjE2MTcwODA0NzMsIm5iZiI6MTYxNjY0ODQ3MywianRpIjoiZGEwZDk4MzFlZjkzMjJlY2Y3MWJiNjVkMjEwNmRlMzgifQ.bg7QfPMXnKsM8xAyac3CMzGyLKOEomWijVEILMvSx40'
routeType='pt'
mode=['TRANSIT','BUS']
head='https://developers.onemap.sg/privateapi/routingsvc/route?'
dt=datetime.now().strftime("%Y-%m-%d")
tm=datetime.now().strftime('%H:%M:%S')

# this function: input: list of coordinate of each POI, return matrix
comb = combinations(mydf['POI_name'], 2)  
comb_list=list(comb)
distance_matrix,iti_matrix=construct_matrix(comb_list)
permutation,distance=shortest_path(distance_matrix)
m=visualisation(permutation,iti_matrix)

# for each place combination, query onemap
def construct_matrix(combination_list):
    distance_matrix=np.zeros((len(mydf),len(mydf)))
    iti_matrix=np.empty((len(mydf),len(mydf)),dtype='object')

    for i in combination_list:  
        start=str(mydf.loc[(mydf['POI_name']==i[0]),['latitude']].values[0][0])+','+str(mydf.loc[(mydf['POI_name']==i[0]),['longitude']].values[0][0])
        end=str(mydf.loc[(mydf['POI_name']==i[1]),['latitude']].values[0][0])+','+str(mydf.loc[(mydf['POI_name']==i[1]),['longitude']].values[0][0])

        duration_list=[]
        min_dur=1000000
        response_list=[]
        num_iti=[]
        selected_iti=None

        for j in mode: # each mode
            url=head+'start='+start+'&end='+end+'&routeType='+routeType+'&token='+om_token+'&date='+str(dt)+'&time='+str(tm)+'&mode='+j+'&numItineraries='+str(3)
            temp_rp=requests.get(url=url)
            # how to handle bad request? (500, 504 etc.) One way is to slightly change the coordinates of the location
            temp_response_json=temp_rp.json()
            response_list.append(temp_response_json)

            temp_num_iti=len(temp_response_json['plan']['itineraries']) # ranges from 1 to 3
            num_iti.append(temp_num_iti)
            for k in range(temp_num_iti): # each possible itinerary
                temp_dur=temp_response_json['plan']['itineraries'][k]['duration']
                duration_list.append(temp_dur)
                min_dur=min(min_dur,temp_dur)
        best_dur_index=duration_list.index(min_dur) # ranges from 0 to 5
        if best_dur_index<=num_iti[0]-1: # belongs to transit
            selected_iti=response_list[0]['plan']['itineraries'][best_dur_index]
        else: # belongs to bus
            selected_iti=response_list[1]['plan']['itineraries'][best_dur_index-num_iti[0]]

        # min_dur write into matrix
        x=mydf.loc[(mydf['POI_name']==i[0])].index.values[0]
        y=mydf.loc[(mydf['POI_name']==i[1])].index.values[0]
        distance_matrix[x][y]=min_dur
        distance_matrix[y][x]=min_dur
        iti_matrix[x][y]=selected_iti
        iti_matrix[y][x]=selected_iti
    return distance_matrix,iti_matrix

# find shortest path
def shortest_path(distance_matrix):
    distance_matrix[:, 0] = 0
    permutation, distance = solve_tsp_dynamic_programming(distance_matrix)
    return permutation,distance

def visualisation(permutation,iti_matrix):
    # draw folium graph
    str_fea_list=[]
    tooltip = 'Click For More Info'
    des_dict={'WALK':'Walk to ','SUBWAY':'Take subway to ','BUS':'Take bus to '}
    m = folium.Map(location=[1.2791,103.8154], zoom_start=12)
    for i in range(len(permutation)-1): # for one itinerary
        sta_plc_idx=permutation[i]
        end_plc_idx=permutation[i+1]
        itinerary=iti_matrix[sta_plc_idx][end_plc_idx]

        true_sta_pt=np.array((mydf._get_value(sta_plc_idx,'latitude'),mydf._get_value(sta_plc_idx,'longitude')))
        true_end_pt=np.array((mydf._get_value(end_plc_idx,'latitude'),mydf._get_value(end_plc_idx,'longitude')))

        temp_num_legs=len(itinerary['legs']) # num of legs
        pt_lat=[]
        pt_lon=[]
        tpl_list=[]
        pt_name=[]
        mode_list=[]
        dist_list=[]
        for k in range(temp_num_legs): # for each leg
            pt_lon.append(itinerary['legs'][k]['from']['lon'])
            pt_lat.append(itinerary['legs'][k]['from']['lat'])
            tpl_list.append((itinerary['legs'][k]['from']['lon'],itinerary['legs'][k]['from']['lat']))
            pt_name.append(itinerary['legs'][k]['to']['name'])
            mode_list.append(des_dict[itinerary['legs'][k]['mode']])
            dist_list.append(str(round(float(itinerary['legs'][k]['distance'])/1000,2))+' km.')
            if k==temp_num_legs-1:
                pt_lon.append(itinerary['legs'][k]['to']['lon'])
                pt_lat.append(itinerary['legs'][k]['to']['lat'])
                tpl_list.append((itinerary['legs'][k]['to']['lon'],itinerary['legs'][k]['to']['lat']))
        temp_feature=Feature(geometry=MultiLineString([tpl_list]),properties={'stroke':'#AF4646'})
        str_fea_list.append(temp_feature)
        first_point=np.array((pt_lat[0],pt_lon[0]))

        distance1 = np.linalg.norm(first_point-true_sta_pt)
        distance2 = np.linalg.norm(first_point-true_end_pt)

        start_point=[pt_lat[0],pt_lon[0]]
        end_point=[pt_lat[-1],pt_lon[-1]]
        iterator=range(len(mode_list))
        # only affect formatting the text
        string=''
        if distance1>distance2:
            iterator=range(len(mode_list)-1,-1,-1)
            start_point=[pt_lat[-1],pt_lon[-1]]
            end_point=[pt_lat[0],pt_lon[0]]
        counter=0
        for j in iterator:
            string+=str(counter+1)+'. '+mode_list[j]+pt_name[j]+'. Estimated distance is '+dist_list[j]+'\n'
            counter+=1

        folium.Marker(start_point,
                  popup='<strong>'+string+'</strong>',
                  tooltip=tooltip,
                  icon=folium.Icon(icon='trophy' if i!=0 else 'flag')).add_to(m),
        folium.Marker(end_point,icon=folium.Icon(icon='trophy' if i!=len(permutation)-2 else 'star')).add_to(m)

    feature_collection=FeatureCollection(str_fea_list)
    ms=geojson.dumps(feature_collection)
    folium.GeoJson(ms, name='multistring').add_to(m)

    # Generate map
    m.save('map.html')
    return m