# -*- coding: utf-8 -*-

# Copyright 2017 Juan José Martín Miralles
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


# Python modules
import math
import datetime
import datetime as dt
from datetime import datetime

# Third party modules
import geopy
import geopy.distance as geo_dist
import pandas as pd


TIME_FORMATS = ['%H:%M', '%H%M', '%I:%M%p', '%I%M%p', '%H:%M:%S', '%H%M%S', '%I:%M:%S%p', '%I%M%S%p']



class TrackException(Exception):
    """
    Generic exception for TrackAnimation
    """
    def __init__(self, msg, original_exception):
        super(TrackException, self).__init__(msg + (": %s" % original_exception))
        self.original_exception = original_exception




def getBearing(start_point, end_point):
	"""
	Calculates the bearing between two points.

	Parameters
	----------
	start_point: geopy.Point
	end_point: geopy.Point
		
	Returns
	-------
	point: int
		Bearing in degrees between the start and end points.
	"""
	start_lat = math.radians(start_point.latitude)
	start_lng = math.radians(start_point.longitude)
	end_lat = math.radians(end_point.latitude)
	end_lng = math.radians(end_point.longitude)

	d_lng = end_lng - start_lng
	if abs(d_lng) > math.pi:
		 if d_lng > 0.0:
			 d_lng = -(2.0 * math.pi - d_lng)
		 else:
			 d_lng = (2.0 * math.pi + d_lng)

	tan_start = math.tan(start_lat / 2.0 + math.pi / 4.0)
	tan_end = math.tan(end_lat / 2.0 + math.pi / 4.0)
	dPhi = math.log(tan_end / tan_start)
	bearing = (math.degrees(math.atan2(d_lng, dPhi)) + 360.0) % 360.0

	return bearing


def getCoordinates(start_point, end_point, distance_meters):
	"""
	Calculates the new coordinates between two points depending
	of the specified distance and the calculated bearing.

	Parameters
	----------
	start_point: geopy.Point
	end_point: geopy.Point
	distance_meters: float
		
	Returns
	-------
	point: geopy.Point
		A new point between the start and the end points.
	"""
	bearing = getBearing(start_point, end_point)

	distance_km = distance_meters / 1000
	d = geo_dist.VincentyDistance(kilometers=distance_km)
	destination = d.destination(point=start_point, bearing=bearing)

	return geopy.Point(destination.latitude, destination.longitude)


def getPointInTheMiddle(start_point, end_point, time_diff, point_idx):
	"""
	Calculates a new point between two points depending of the
	time difference between them and the point index.

	Parameters
	----------
	start_point: DataFrame
	end_point: DataFrame
	time_diff: float
	point_idx: int
		Point index between the start and the end points
		
	Returns
	-------
	point: list
		A new point between the start and the end points.
	"""
	time_proportion = (time_diff * point_idx) / end_point['TimeDifference'].item()

	distance_proportion = end_point['Distance'].item() * time_proportion
	time_diff_proportion = end_point['TimeDifference'].item() * time_proportion
	speed = distance_proportion / time_diff_proportion
	distance = time_diff * speed
	cum_time_diff = int(start_point['CumTimeDiff'].item() + time_diff_proportion)
	# date = datetime.strptime(start_point['Date'].item(), '%Y-%m-%d %H:%M:%S') + dt.timedelta(seconds=int(time_diff_proportion))
	date = pd.to_datetime(start_point['Date'].astype(str), format='%Y-%m-%d %H:%M:%S') + dt.timedelta(seconds=int(time_diff_proportion))
	altitude = (end_point['Altitude'].item() + start_point['Altitude'].item()) / 2
	name = start_point['CodeRoute'].item()

	geo_start = geopy.Point(start_point['Latitude'].item(), start_point['Longitude'].item())
	geo_end = geopy.Point(end_point['Latitude'].item(), end_point['Longitude'].item())
	middlePoint = getCoordinates(geo_start, geo_end, distance_proportion)

	df_middlePoint = ([[name, middlePoint.latitude, middlePoint.longitude, altitude, 
			date, speed, int(time_diff), distance, None, cum_time_diff]])

	return df_middlePoint


def rgb(value, minimum, maximum):
	"""
	Calculates an rgb color of a value depending of
	the minimum and maximum values.

	Parameters
	----------
	value: float or int
	minimum: float or int
	maximum: float or int
		
	Returns
	-------
	rgb: tuple
	"""
	value = float(value)
	minimum = float(minimum)
	maximum = float(maximum)

	if minimum == maximum:
		ratio = 0
	else:
		ratio = 2 * (value - minimum) / (maximum - minimum)

	b = int(max(0, 255 * (1 - ratio)))
	r = int(max(0, 255 * (ratio - 1)))
	g = 255 - b - r
	
	return r/255.0, g/255.0, b/255.0


def calculateCumTimeDiff(df):
	"""
	Calculates the cumulative of the time difference
	between points for each track of 'dfTrack'

	Parameters
	----------
	df: DataFrame
		
	Returns
	-------
	df_cum: DataFrame
	"""
	df = df.copy()

	df_cum = pd.DataFrame()
	grouped = df['CodeRoute'].unique()

	for name in grouped:
		df_slice = df[df['CodeRoute'] == name]
		df_slice = df_slice.reset_index(drop=True)
		df_slice['CumTimeDiff'] = df_slice['TimeDifference'].cumsum()
		df_cum = pd.concat([df_cum, df_slice])

	df_cum = df_cum.reset_index(drop=True)

	return df_cum


def isTimeFormat(time):
	"""
	Check if 'time' variable has the format of one
	of the 'time_formats'
	"""
	if time is None:
		return False

	for time_format in TIME_FORMATS:
		try:
			datetime.strptime(time, time_format)
			return True
		except ValueError:
			pass

	return False