# Script to generate an ARC Shape File from SBDR Table Data

# Built in Python functions
import time
import logging
import math

# External Python libraries (installed via pip install)
import numpy as np
import pandas as pd
import pdr
import pyproj # replicate gc_az1 = azimuth( [pt1x,pt1y],[pt2x,pt2y],titan_def ) [from Matlab]

# Internal Pydar reference to access functions, global variables, and error handling
import pydar

## Logging set up for .DEBUG
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
stream_handler = logging.StreamHandler()
logger.addHandler(stream_handler)

field_options = ["act_pol_angle",
				"act_incidence_angle",
				"act_azimuth_angle",
				"burst_id",
				"sar_azimuth_res",
				"sar_range_res",
				"sigma0_corrected",
				"sigma0_uncorrected",
				"sigma0_uncorrected_std"]

def intersectionOfTwoGreatCircles(p1_lat1, p1_long1, p1_lat2, p1_long2,
								p2_lat1, p2_long1, p2_lat2, p2_long2):
	# Intersection Point of Two Great Circles (latitude/longitude)

	# Convert points in great circle 1 from degrees to radians
	p1_lat1_rad = np.deg2rad(p1_lat1)
	p1_long1_rad = np.deg2rad(p1_long1)
	p1_lat2_rad = np.deg2rad(p1_lat2)
	p1_long2_rad = np.deg2rad(p1_long2)
	# Convert points in great circle 2 from degrees to radians
	p2_lat1_rad = np.deg2rad(p2_lat1)
	p2_long1_rad = np.deg2rad(p2_long1)
	p2_lat2_rad = np.deg2rad(p2_lat2)
	p2_long2_rad = np.deg2rad(p2_long2)

	# Convert to Polar coordinates
	x1 = math.cos(p1_lat1_rad) * math.cos(p1_long1_rad)
	y1 = math.cos(p1_lat1_rad) * math.sin(p1_long1_rad)
	z1 = math.sin(p1_lat1_rad)
	x2 = math.cos(p1_lat2_rad) * math.cos(p1_long2_rad)
	y2 = math.cos(p1_lat2_rad) * math.sin(p1_long2_rad)
	z2 = math.sin(p1_lat2_rad)
	cx1 = math.cos(p2_lat1_rad) * math.cos(p2_long1_rad)
	cy1 = math.cos(p2_lat1_rad) * math.sin(p2_long1_rad)
	cz1 = math.sin(p2_lat1_rad)
	cx2 = math.cos(p2_lat2_rad) * math.cos(p2_long2_rad)
	cy2 = math.cos(p2_lat2_rad) * math.sin(p2_long2_rad)
	cz2 = math.sin(p2_lat2_rad)

	# Find normal to the planes containing great circles
	# np.cross is the product of vector to each point from the origin
	N1 = np.cross([x1, y1, z1], [x2, y2, z2])
	N2 = np.cross([cx1, cy1, cz1], [cx2, cy2, cz2])

	# Find line of intersection between two planes
	L = np.cross(N1, N2)

	# Find two intersection points
	X1 = L / np.sqrt(L[0]**2 + L[1]**2 + L[2]**2)
	X2 = -X1
	i_lat1 = math.asin(X1[2]) * 180./np.pi
	i_long1 = math.atan2(X1[1], X1[0]) * 180./np.pi
	i_lat2 = math.asin(X2[2]) * 180./np.pi
	i_long2 = math.atan2(X2[1], X2[0]) * 180./np.pi

	return i_lat1, i_long1, i_lat2, i_long2

def sbdrMakeShapeFile(filename=None, 
						fields=[],
						write_files=False,
						saronly=0, 
						usepassive=False, 
						ind=None, 
						file_out=None, 
						lon360=False):
	# filename: name of the SPDR file (string)
	# fields: all burst data if you don't specify (list)
	# write_files : should you create a shp file? (boolean)
	# saronly: what sar data you will take out (conditional statements)
	# usepassive : include passive radar data (boolean)
	# ind : indexes of burst data (list)
	# file_out : name of file output (string)
	# lon360: using 360 Longitude system (boolean) (false = not using, true = using)

	pydar.errorHandlingSbdrMakeShapeFile(filename=filename,
										fields=fields,
										write_files=write_files,
										saronly=saronly,
										usepassive=usepassive,
										ind=ind,
										file_out=file_out,
										lon360=lon360)

	start = time.time()

	# If fields are empty: set to all possible data fields
	if len(fields) == 0:
		fields = field_options

	# saroly defaults to getting all data, not just sar (saronly = 0)
	if saronly == 0: logger.debug("All data, not just SAR")
	if saronly == 1: logger.debug("Using SAR+HiSAR")
	if saronly == 2: logger.debug("Only Using High Resolution SAR Data")
	if saronly == 3: logger.debug("Using SAR + Scatterometry")

	# lon360 is False, default behavior: defined from -180 to 180
	# lon360 is True: defined from 0 to 360
	if lon360: logger.debug("Longitude system: -180 to 180")
	if not lon360: logger.debug("Using 360 Longitude System! (0-360)")

	# Set Filename default is filename is not characters: (TODO: does this need to be included?")
	#if not filename.isalpha():
	#	info = filename
	#	filename = "DefaultFile.sbdr"
	logger.debug("Reading {0} ...".format(filename))
	info = pdr.read(filename)
	info = info['SBDR_TABLE']
	logger.debug("Headers = {0}\n".format(list(info)))
	#print(info['ACT_POL_ANGLE'])

	# Define the Titan Ellipsoid
	r_titan = 2575000 # radius in meters
	flat_titan = 0 # titan simplified to a sphere (no eccentricity)
	titan_def = [r_titan/1000 , flat_titan] # [radius in km, 0]

	# Create Output Filename
	if file_out is None:
		file_out = filename.split(".")[0] + ".shp"
		file_out2 = filename.split(".")[0] + "_centroid.shp"
		file_out3 = filename.split(".")[0] + "_center.shp"
	else:
		file_out = file_out + ".shp"
		file_out2 = file_out + "_centroid.shp"
		file_out3 = file_out + "_center.shp"
	logger.debug("Filenames for Output:\n{0}\n{1}\n{2}".format(file_out, file_out2, file_out3))

	# Filtering based on SAR and Active/Passive Radar
	# Find the Good Active Points (Usable data with low distortion from spacecraft orientation)
	if saronly >= 0: # if not just looking at SAR data, get the radarmode from the data
		r = info.RADAR_MODE
		if saronly == 2: # Get the Low Res SAR data
			if usepassive: # Passive Radar
				# Find all indices of data that are high rest SAR data and the azimuth angle and highest(?) latitude of the ellipse is not zero
				ind = info[info.RADAR_MODE.isin([3, 11])]
				ind = ind[~ind.ACT_AZIMUTH_ANGLE.isin([0])]
				ind = ind[~ind.PASS_ELLIPSE_PT1_LAT.isin([0])]
			else: # Active Radar
				ind = info[info.RADAR_MODE.isin([3, 11])]
				ind = ind[~ind.ACT_AZIMUTH_ANGLE.isin([0])]
				ind = ind[~ind.ACT_ELLIPSE_PT1_LAT.isin([0])]
		elif saronly == 3: # High Res SAR
			ind = info[~info.PASS_ELLIPSE_PT1_LAT.isin([0])]
			ind = ind[~ind.SAR_RANGE_RES.isin([0])]
			ind = ind[~ind.ACT_MAJOR_WIDTH.isin([0])]
		else: # If not 2 or 3
			if usepassive:
				ind = info[~info.PASS_ELLIPSE_PT1_LAT.isin([0])]
				ind = ind[~ind.SAR_RANGE_RES.isin([0])]
				ind = ind[~ind.SAR_AZIMUTH_RES.isin([0])]
			else:
				# TODO: note: usepassive and not use the same functionality
				ind = info[~info.PASS_ELLIPSE_PT1_LAT.isin([0])]
				ind = ind[~ind.SAR_RANGE_RES.isin([0])]
				ind = ind[~ind.SAR_AZIMUTH_RES.isin([0])]

		if len(ind.index) == 0: # No data  satifies all conditions
			ind = info[~info.ACT_AZIMUTH_ANGLE.isin([0])]
			ind = ind[~ind.ACT_ELLIPSE_PT1_LAT.isin([0])]
			logger.info("ERROR: No Active SAR Model")
			struct = []
			return
		else:
			logger.info("Found {0} Active Pulses".format(len(ind.index)))
	else:
		ind = info[~info.ACT_AZIMUTH_ANGLE.isin([0])]
		ind = ind[~ind.ACT_ELLIPSE_PT1_LAT.isin([0])]
	
	num_record = len(ind.index) # Number of good data

	# Get the Required Data
	ind_lst = ind.index.values.tolist() # index of all valid points
	if usepassive:
		act_pt1_lat = info.iloc[ind_lst].PASS_ELLIPSE_PT1_LAT
		act_pt2_lat = info.iloc[ind_lst].PASS_ELLIPSE_PT2_LAT
		act_pt3_lat = info.iloc[ind_lst].PASS_ELLIPSE_PT3_LAT
		act_pt4_lat = info.iloc[ind_lst].PASS_ELLIPSE_PT3_LAT
		
		act_pt1_lon = info.iloc[ind_lst].PASS_ELLIPSE_PT1_LON
		act_pt2_lon = info.iloc[ind_lst].PASS_ELLIPSE_PT2_LON
		act_pt3_lon = info.iloc[ind_lst].PASS_ELLIPSE_PT3_LON
		act_pt4_lon = info.iloc[ind_lst].PASS_ELLIPSE_PT4_LON
		
		act_min_wid = info.iloc[ind_lst].PASS_MINOR_WIDTH
		act_maj_wid = info.iloc[ind_lst].PASS_MINOR_WIDTH
		act_cen_lat = info.iloc[ind_lst].PASS_CENTROID_LAT
		act_cen_lon = info.iloc[ind_lst].PASS_CENTROID_LON
		act_az = info.iloc[ind_lst].ACT_AZIMUTH_ANGLE
	else:
		act_pt1_lat = info.iloc[ind_lst].ACT_ELLIPSE_PT1_LAT
		act_pt2_lat = info.iloc[ind_lst].ACT_ELLIPSE_PT2_LAT
		act_pt3_lat = info.iloc[ind_lst].ACT_ELLIPSE_PT3_LAT
		act_pt4_lat = info.iloc[ind_lst].ACT_ELLIPSE_PT3_LAT
		
		act_pt1_lon = info.iloc[ind_lst].ACT_ELLIPSE_PT1_LON
		act_pt2_lon = info.iloc[ind_lst].ACT_ELLIPSE_PT2_LON
		act_pt3_lon = info.iloc[ind_lst].ACT_ELLIPSE_PT3_LON
		act_pt4_lon = info.iloc[ind_lst].ACT_ELLIPSE_PT4_LON
		
		act_min_wid = info.iloc[ind_lst].ACT_MINOR_WIDTH
		act_maj_wid = info.iloc[ind_lst].ACT_MINOR_WIDTH
		act_cen_lat = info.iloc[ind_lst].ACT_CENTROID_LAT
		act_cen_lon = info.iloc[ind_lst].ACT_CENTROID_LON
		act_az = info.iloc[ind_lst].ACT_AZIMUTH_ANGLE

	if saronly == 3:
		min_wid = info.iloc[ind_lst].ACT_MINOR_WIDTH
		maj_wid = info.iloc[ind_lst].ACT_MAJOR_WIDTH

	def azimuth(lat1, lat2, long1, long2, radius):
		# convert decimal degrees to radians 
		long1, lat1, long2, lat2 = map(math.radians, [long1, lat1, long2, lat2])
		
		diff_lats = lat2 - lat1
		diff_long = long2 - long1
		a = math.sin(diff_lats/2)**2 + math.cos(lat1) * math.cos(lat2) * math.sin(diff_long/2)**2
		c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a))
		az = c * radius
		return az


	# Loop through Data fields
	cnt = 1
	gfields = {}
	for field in fields:
		if field.upper() in list(info): # checks that field is an element in the info dataframe 
			gfields[cnt] = field # Convert matlab structure to dict
			cnt += 1
		else:
			logger.info("{0} Not a Valid Field!".format(field))
	if cnt == 1: # If the fields are not moved through, then no fields are returned
		logger.info("No Valid Fields Returning...")
		return

	# Progress Bar: Print an update to the screen
	# TODO: Currently does nothing
	#eta_start = time.time()
	#for num in range(num_record):
	#	if num < num_record:
	#		print("{0} of {1} ({2} Percent Complete".format(num+1, num_record, num+1/num_record), end="\r")
	#print("") # reset printing to terminal

	# Step through each footprint and create 100 latitude/longitude points around the ellipse to create a perimeter
	for i in range(num_record): # TODO: check if i should be the index or the value?
		# TODO: num_record + 1? to get all?
		# 1 to all valid data points
		# Get the ellipse center and semi-major axis, eccentricity, and azimuth
		pt1x = float(act_pt1_lat.iloc[[i]])
		pt1y = float(act_pt1_lon.iloc[[i]])
		pt2x = float(act_pt2_lat.iloc[[i]])
		pt2y = float(act_pt2_lon.iloc[[i]])
		pt3x = float(act_pt3_lat.iloc[[i]])
		pt3y = float(act_pt3_lon.iloc[[i]])
		pt4x = float(act_pt4_lat.iloc[[i]])
		pt4y = float(act_pt4_lon.iloc[[i]])
		ptx = [pt1x, pt2x, pt3x, pt4x] # TODO: duplicate of below line, remove?
		pty = [pt1y, pt2y, pt3y, pt4y]

		# Convert lons into -180 to 180
		if pt1y > 180: pt1y = pt1y - 360
		if pt2y > 180: pt2y = pt2y - 360
		if pt3y > 180: pt3y = pt3y - 360
		if pt4y > 180: pt4y = pt4y - 360
		if pt1y < -180: pt1y = pt1y + 360
		if pt2y < -180: pt2y = pt2y + 360
		if pt3y < -180: pt3y = pt3y + 360
		if pt4y < -180: pt4y = pt4y + 360

		ptx = [pt1x, pt2x, pt3x, pt4x] # Latitudes
		pty = [-x for x in pty] # Convert Longitudes to negative

		# Check if Crosses 180 or 360
		if (min([abs(i) - 360 for i in pty]) < 2) or (min([abs(i) for i in pty]) < 2):
			# Convert to -180 or 180
			for i, pt in enumerate(pty):
				if pt > 180:
					pty[i] = pt - 360 # TODO: verify: pty(pty > 180) = pty(pty > 180) -360
		elif (min([abs(i) - -180 for i in pty]) < 2):
			# Convert between 0 and 360
			for i, pt in enumerate(pty):
				if pt < 0:
					pty[i] = pt + 360 # TODO: verify: pty(pty < 0) = pty(pty < 0) + 360

		# If lon360, convert to between 0 and 360
		if lon360:
			for i, pt in enumerate(pty):
				if pt < 0:
					pty[i] = pt + 360 # TODO: verify: pty(pty < 0) = pty(pty < 0) + 360

		# Ensure that single records exist
		pt1y = pty[0]
		pt2y = pty[1]
		pt3y = pty[2]
		pt4y = pty[3]
		
		pt1x = ptx[0]
		pt2x = ptx[1]
		pt3x = ptx[2]
		pt4x = ptx[3]

		# Setting azimuth for intersection of ellipsis 
		geodesic = pyproj.Geod(a =r_titan,es=0) # define Titan ellipsoid
		
		fwd_azimuth, back_azimuth, distance = geodesic.inv(pt1y, pt1x, pt2y, pt2x)
		gc_az1 = fwd_azimuth # considering azimuth from starting lat,lon pair like in the matlab function
		
		fwd_azimuth, back_azimuth, distance = geodesic.inv(pt3y, pt3x, pt4y, pt4x)

		# Calculate the Intersection Point
		#i_lat1, i_long1, i_lat2, i_long2 = intersectionOfTwoGreatCircles(p1_lat1, p1_long1, p1_lat2, p1_long2,
		#																p2_lat1, p2_long1, p2_lat2, p2_long2)
