#!/usr/bin/env python
import rospy
import numpy as np
import math
from std_msgs.msg import Float32 as float_msg
from genesis_path_follower.msg import state_est

class VehicleSimulator():
	'''
	A vehicle dynamics simulator using a linear tire model.  This uses the Genesis control/estimation ROS topics.
	Modified from Ugo Rosolia's Code: https://github.com/urosolia/RacingLMPC/blob/master/src/fnc/SysModel.py
	'''
	def __init__(self):
		rospy.init_node('vehicle_simulator', anonymous=True)
		rospy.Subscriber('/control/accel', float_msg, self._acc_cmd_callback, queue_size=1)
		rospy.Subscriber('/control/steer_angle', float_msg, self._df_cmd_callback, queue_size=1)
		self.state_pub = rospy.Publisher('state_est', state_est, queue_size=1)

		self.tcmd_a = None	# rostime (s) of received acc command
		self.tcmd_d = None	# rostime (s) of received df command
		self.acc = 0.0		# actual acceleration (m/s^2)
		self.df = 0.0		# actual steering angle (rad)
		self.acc_des = 0.0	# desired acceleration	(m/s^2)
		self.df_des = 0.0	# desired steering_angle (rad)

		self.dt_model = 0.01				# vehicle model update period (s) and frequency (Hz)
		self.hz = int(1.0/self.dt_model)
		self.r = rospy.Rate(self.hz)
		
		# Simulated Vehicle State.
		self.X   = rospy.get_param('X0', -300.0) 	# X position (m)
		self.Y   = rospy.get_param('Y0', -450.0) 	# Y position (m)
		self.psi = rospy.get_param('Psi0', 1.0) 	# yaw angle (rad)
		self.vx  = rospy.get_param('V0', 0.0)		# longitudinal velocity (m/s)
		self.vy  = 0.0								# lateral velocity (m/s)
		self.wz  = 0.0								# yaw rate (rad/s)
		
		self.acc_time_constant = 0.4 # s
		self.df_time_constant  = 0.1 # s

		self.pub_loop()

	def pub_loop(self):
		while not rospy.is_shutdown():
			self._update_vehicle_model()
			
			curr_state = state_est()
			curr_state.header.stamp = rospy.Time.now()
			curr_state.x   = self.X
			curr_state.y   = self.Y
			curr_state.psi = self.psi
			curr_state.v   = self.vx
			curr_state.a   = self.acc
			curr_state.df  = self.df

			self.state_pub.publish(curr_state)
			self.r.sleep()

	def _acc_cmd_callback(self, msg):
		self.tcmd_a = rospy.Time.now()
		self.acc_des = msg.data

	def _df_cmd_callback(self, msg):
		self.tcmd_d = rospy.Time.now()
		self.df_des    = msg.data

	def _update_vehicle_model(self, disc_steps = 10):
		# Genesis Parameters from HCE:
		lf = 1.5213  			# m  	(CoG to front axle)
		lr = 1.4987  			# m  	(CoG to rear axle)
		d  = 0.945	 			# m  	(half-width, currently unused)
		m  = 2303.1   			# kg 	(vehicle mass)
		Iz  = 5520.1			# kg*m2 (vehicle inertia)
		C_alpha_f = 7.6419e4*2  # N/rad	(front axle cornering stiffness) # 200k
		C_alpha_r = 13.4851e4*2	# N/rad	(rear axle cornering stiffness)	 # 250k

		deltaT = self.dt_model/disc_steps
		self._update_low_level_control(self.dt_model)
		for i in range(disc_steps):			

			# Compute tire slip angle
			alpha_f = 0.0
			alpha_r = 0.0
			if math.fabs(self.vx) > 1.0:
				alpha_f = self.df - np.arctan2( self.vy+lf*self.wz, self.vx )
				alpha_r = - np.arctan2( self.vy-lr*self.wz , self.vx)        		
			
			# Compute lateral force at front and rear tire (linear model)
			Fyf = C_alpha_f * alpha_f
			Fyr = C_alpha_r * alpha_r

			# Propagate the vehicle dynamics deltaT seconds ahead.
			# Max with 0 is to prevent moving backwards.
			vx_n  = max(0.0, self.vx  + deltaT * ( self.acc - 1/m*Fyf*np.sin(self.df) + self.wz*self.vy ) )
			
			# Ensure only forward driving.
			if vx_n > 1e-6:
				vy_n  = self.vy  + deltaT * ( 1.0/m*(Fyf*np.cos(self.df) + Fyr) - self.wz*self.vx )
				wz_n  = self.wz  + deltaT * ( 1.0/Iz*(lf*Fyf*np.cos(self.df) - lr*Fyr) )
			else:
				vy_n = 0.0
				wz_n = 0.0

			psi_n = self.psi + deltaT * ( self.wz )
			X_n   = self.X   + deltaT * ( self.vx*np.cos(self.psi) - self.vy*np.sin(self.psi) )
			Y_n   = self.Y   + deltaT * ( self.vx*np.sin(self.psi) + self.vy*np.cos(self.psi) )


			self.X 	 = X_n
			self.Y   = Y_n
			self.psi = (psi_n + np.pi) % (2.0 * np.pi) - np.pi # https://stackoverflow.com/questions/15927755/opposite-of-numpy-unwrap
			self.vx  = vx_n
			self.vy  = vy_n
			self.wz  = wz_n

	def _update_low_level_control(self, dt_control):
		# e_<n> = self.<n> - self.<n>_des
		# d/dt e_<n> = - kp * e_<n>
		# or kp = alpha, low pass filter gain
		# kp = alpha = discretization time/(time constant + discretization time)
		# This is just to simulate some first order control delay in acceleration/steering.
		self.acc = dt_control/(dt_control + self.acc_time_constant) * (self.acc_des - self.acc) + self.acc
		self.df  = dt_control/(dt_control + self.df_time_constant)  * (self.df_des  - self.df) + self.df

if __name__=='__main__':
	print 'Starting Simulator.'
	try:
		v = VehicleSimulator()
	except rospy.ROSInterruptException:
		pass