#!/usr/bin/python3

##--Michael duPont
##--AVWX-Engine : avwx.py
##--Shared METAR settings and functions
##--2015-09-04

# This file contains a series of functions and variables that can be used
# in any project that needs a means of fetching, interpretting, and/or
# translating METAR and TAF weather data.
#
# While the list of functions is rather large, here are the most useful:
#    getMETAR(station)          Returns METAR report for a given station
#    getTAF(station)            Returns TAF report for a given station
#    parseMETAR(txt)            Returns a key-value dict where 'txt' is the report getMETAR returns
#    parseTAF(txt)              Returns a key-value dict where 'txt' is the report getTAF returns
#    translateMETAR(wxData)     Returns a key-value dict of field translations where 'wxData' is what parseMETAR returns
#    translateTAF(wxData)       Returns a key-value dict of field translations where 'wxData' is what parseTAF returns
#    getInfoForStation(station) Returns a key-value dict of basic info for a given 'station'
#    getFlightRules(vis , splitCloud)
#         Returns int corresponding to flight rules index in flightRules
#         Standard usage states that the current rule is IFR if visibility value is not available
#         vis is string of visibility distance value
#         splitCloud is the ceiling is the format [type,height,modifier]
#         Typical usage:     flightRules[getFlightRules(parsedReport['Visibility'] , getCeiling(parsedReport['Cloud-List']))]
#         Usage with values: flightRules[getFlightRules('8' , ['BKN','014',''])] -> 'MVFR'
#
# Both METAR and TAF reports come in the Standard/International variant and
# the North American variant. parseMETAR and parseTAF automatically figure out
# which parser variant to use, but you're welcome to use the variant functions
# directly if you want. Just know that parsing errors will arise if the
# International parser tries to parse a US report.
#
# Example usage for both METAR and TAF can be found at the bottom of the file.
# You can run this test code by running this file: python avwx.py

import sqlite3 , json , sys
if sys.version_info[0] == 2: import urllib2
elif sys.version_info[0] == 3: from urllib.request import urlopen
from itertools import permutations
from time import strftime
from copy import copy
from os import path
from .xmltodict import parse

##--Logic Vars
flightRules = ['VFR','MVFR','IFR','LIFR']
cloudList = ['FEW','SCT','BKN','OVC']
wxReplacements = {
'RA':'Rain','TS':'Thunderstorm','SH':'Showers','DZ':'Drizzle','VC':'Vicinity','UP':'Unknown Precip',
'SN':'Snow','FZ':'Freezing','SG':'Snow Grains','IC':'Ice Crystals','PL':'Ice Pellets','GR':'Hail','GS':'Small Hail',
'FG':'Fog','BR':'Mist','HZ':'Haze','VA':'Volcanic Ash','DU':'Wide Dust','FU':'Smoke','SA':'Sand','SY':'Spray',
'SQ':'Squall','PO':'Dust Whirls','DS':'Duststorm','SS':'Sandstorm','FC':'Funnel Cloud',
'BL':'Blowing','MI':'Shallow','BC':'Patchy','PR':'Partial','UP':'Unknown'}

metarRMKStarts = [' BLU',' BLU+',' WHT',' GRN',' YLO',' AMB',' RED',' BECMG',' TEMPO',' INTER',' NOSIG',' RMK',' WIND',' QFE',' QFF',' INFO',' RWY',' CHECK']
tafRMKStarts = ['RMK ','AUTOMATED ','COR ','AMD ','LAST ','FCST ','CANCEL ','CHECK ','WND ','MOD ',' BY',' QFE',' QFF']
tafNewLineStarts = [' INTER ' , ' FM' , ' BECMG ' , ' TEMPO ']

##--Station Location Identifiers
RegionsUsingUSParser = ['C', 'K', 'P', 'T']
RegionsUsingInternationalParser = ['A', 'B', 'D', 'E', 'F', 'G', 'H', 'L', 'N', 'O', 'R', 'S', 'U', 'V', 'W', 'Y', 'Z']
#The Central American region is split. Therefore we need to use the first two letters
MStationsUsingUSParser = ['MB', 'MM', 'MT', 'MY']
MStationsUsingInternationalParser = ['MD' , 'MG', 'MH', 'MK' , 'MN', 'MP', 'MR', 'MS', 'MU' , 'MW' , 'MZ']

naUnits = {'Wind-Speed':'kt','Visibility':'sm','Altitude':'ft','Temperature':'C','Altimeter':'inHg'}
inUnits = {'Wind-Speed':'kt','Visibility':'m','Altitude':'ft','Temperature':'C','Altimeter':'hPa'}
curUnits = {} #Global placeholder for report units

#LOCAL_PATH = path.dirname(path.realpath(__file__))
stationDBPath = path.dirname(path.realpath(__file__))+'/stations.sqlite' #Path to the station info database
requestURL = """https://aviationweather.gov/adds/dataserver_current/httpparam?dataSource={0}s&requestType=retrieve&format=XML&stationString={1}&hoursBeforeNow=2"""

####################################################################################################################################
##--Shared Functions

#Returns the index of the earliest occurence of an item from a list in a string
def findFirstInList(txt,aList):
	startIndex = len(txt)+1
	for item in aList:
		if -1 < txt.find(item) < startIndex: startIndex = txt.find(item)
	if -1 < startIndex < len(txt)+1: return startIndex
	return -1

#Remove remarks and split
#Remarks can include RMK and on, NOSIG and on, and BECMG and on
def __getRemarks(txt):
	txt = txt.replace('?' , '').strip(' ')
	#First look for Altimeter in txt
	altIndex = len(txt)+1
	for item in [' A2',' A3',' Q1',' Q0',' Q9']:
		index = txt.find(item)
		if -1 < index < len(txt)-6 and txt[index+2:index+6].isdigit(): altIndex = index
	#Then look for earliest remarks 'signifier'
	sigIndex = findFirstInList(txt , metarRMKStarts)
	if sigIndex == -1: sigIndex = len(txt)+1
	if -1 < altIndex < sigIndex: return txt[:altIndex+6].strip().split(' ') , txt[altIndex+7:]
	elif -1 < sigIndex < altIndex: return txt[:sigIndex].strip().split(' ') , txt[sigIndex+1:]
	return txt.strip().split(' ') , ''

#Provides sanitization for operations that work better when the report is a string
#Returns the first pass sanitized report string
strReplacements = {' C A V O K ':' CAVOK ' , '?':' '}
def __sanitizeFirstPass(txt):
	if len(txt) > 4:
		stid = txt[:4]
		txt = txt[4:] #Prevent changes to station ID
	for key in strReplacements: txt = txt.replace(key , strReplacements[key])
	#Check for missing spaces in front of cloud layers
	#Ex: TSFEW004SCT012FEW///CBBKN080
	for cloud in cloudList:
		if txt.find(cloud) != -1 and txt.find(' '+cloud) == -1:
			startIndex = 0
			counter = 0
			while txt.count(cloud) != txt.count(' '+cloud):
				if len(txt[txt[startIndex:].find(cloud)+startIndex:]) >= 3:
					targetText = txt[txt[startIndex:].find(cloud)+len(cloud)+startIndex:txt[startIndex:].find(cloud)+len(cloud)+startIndex+3]
					if targetText.isdigit() or not targetText.strip('/'):
						txt = txt[:txt[startIndex:].find(cloud)+startIndex] + ' ' + txt[txt[startIndex:].find(cloud)+startIndex:]
				startIndex = startIndex + txt[startIndex:].find(cloud) + len(cloud) + 1
				#Prevent infinite loops
				if counter > txt.count(cloud): break
				counter += 1
	return stid + txt

#Return True if a space shouldn't exist between two items
#This list grew so large that it had to be moved to its own function for readability
def extraSpaceExists(s1 , s2):
	if s1.isdigit():
		# 10 SM
		if s2 in ['SM','0SM']: return True
		# 12 /10
		if len(s2) > 2 and s2[0] == '/' and s2[1:].isdigit(): return True
	if s2.isdigit():
		# OVC 040
		if s1 in cloudList: return True
		# 12/ 10
		if len(s1) > 2 and s1.endswith('/') and s1[:len(s1)-1].isdigit(): return True
		# 12/1 0
		if len(s2) == 1 and len(s1) > 3 and s1[:2].isdigit() and s1.find('/') != -1 and s1[3:].isdigit(): return True
		# Q 1001
		if s1 in ['Q','A']: return True
	# 36010G20 KT
	if s2 == 'KT' and (s1[:5].isdigit() or (s1.startswith('VRB') and s1[3:5].isdigit())) and s1[len(s1)-1].isdigit(): return True
	# 36010K T
	if s2 == 'T' and len(s1) == 6 and (s1[:5].isdigit() or (s1.startswith('VRB') and s1[3:5].isdigit())) and s1[5] == 'K': return True
	# OVC022 CB
	if s2 in cloudTranslationStrings and s2 not in cloudList and len(s1) >= 3 and s1[:3] in cloudList: return True
	# FM 122400
	if s1 in ['FM','TL'] and (s2.isdigit() or (s2.endswith('Z') and s2[:len(s2)-1].isdigit())): return True
	# TX 20/10
	if s1 in ['TX','TN'] and s2.find('/') != -1: return True
	return False

#Sanitize wxData
#We can remove and identify "one-off" elements and fix other issues before parsing a line
#We also return the runway visibility and wind shear since they are very easy to recognize
#and their location in the report is non-standard
itemRemoval = ['AUTO' , 'COR' , 'NSC' , 'NCD' , '$' , 'KT' , 'M' , '.' , 'RTD' , 'SPECI' , 'METAR' , 'CORR']
itemReplacements = {'CALM': '00000KT'}
visPermutations = [''.join(p) for p in permutations('P6SM')]
visPermutations.pop(visPermutations.index('6MPS'))
def __sanitize(wxData , removeCLRandSKC=True):
	shear = ''
	runwayVisibility = []
	for i , item in reversed(list(enumerate(wxData))):
		#print(i , item)
		#Remove elements containing only '/'
		if not item.strip('/'):
			wxData.pop(i)
		#Identify Runway Visibility
		elif len(item) > 4 and item[0] == 'R' and (item[3] == '/' \
		or item[4] == '/') and item[1:3].isdigit():
			runwayVisibility.append(wxData.pop(i))
		#Remove RE from wx codes, REVCTS -> VCTS
		elif len(item) in [4,6] and item.startswith('RE'):
			wxData.pop(i)
		#Fix a slew of easily identifiable conditions where a space does not belong
		elif i and extraSpaceExists(wxData[i-1] , item):
			wxData[i-1] += wxData.pop(i)
		#Remove spurious elements
		elif item in itemRemoval:
			wxData.pop(i)
		#Remove 'Sky Clear' from METAR but not TAF
		elif removeCLRandSKC and item in ['CLR' , 'SKC']:
			wxData.pop(i)
		#Replace certain items
		elif item in itemReplacements:
			wxData[i] = itemReplacements[item]
		#Remove ammend signifier from start of report ('CCA','CCB',etc)
		elif len(item) == 3 and item.startswith('CC') and item[2].isalpha():
			wxData.pop(i)
		#Identify Wind Shear
		elif len(item) > 6 and item.startswith('WS') and item.find('/') != -1:
			shear = wxData.pop(i).replace('KT' , '')
		#Fix inconsistant 'P6SM' Ex: TP6SM or 6PSM -> P6SM
		elif len(item) > 3 and item[len(item)-4:] in visPermutations:
			wxData[i] = 'P6SM'
		#Fix wind T
		elif (len(item) == 6 and item[5] in ['K','T'] and (item[:5].isdigit() or item.startswith('VRB'))) \
		or (len(item) == 9 and item[8] in ['K','T'] and item[5] == 'G' and (item[:5].isdigit() or item.startswith('VRB'))):
			wxData[i] = item[:len(item)-1] + 'KT'
		#Fix joined TX-TN
		elif len(item) > 16 and len(item.split('/')) == 3:
			if item.startswith('TX') and item.find('TN') != -1:
				wxData.insert(i+1 , item[:item.find('TN')])
				wxData[i] = item[item.find('TN'):]
			elif item.startswith('TN') and item.find('TX') != -1:
				wxData.insert(i+1 , item[:item.find('TX')])
				wxData[i] = item[item.find('TX'):]
		#print(i , item)
	return wxData , runwayVisibility , shear

#Altimeter
def __getAltimeterUS(wxData):
	altimeter = ''
	#Get altimeter
	if wxData:
		if wxData[len(wxData)-1][0] == 'A': altimeter = wxData.pop()[1:]
		elif wxData[len(wxData)-1][0] == 'Q':
			global curUnits
			curUnits['Altimeter'] = 'hPa'
			altimeter = wxData.pop()[1:].lstrip('.')
		elif len(wxData[len(wxData)-1]) == 4 and wxData[len(wxData)-1].isdigit(): altimeter = wxData.pop()
	#Some stations report both, but we only need one
	if wxData and (wxData[len(wxData)-1][0] == 'A' or wxData[len(wxData)-1][0] == 'Q'): wxData.pop()
	return wxData , altimeter

def __getAltimeterInternational(wxData):
	altimeter = ''
	#Get altimeter
	if wxData:
		if wxData[len(wxData)-1][0] == 'Q':
			altimeter = wxData.pop()[1:].lstrip('.')
			if altimeter.find('/') != -1: altimeter = altimeter[:altimeter.find('/')]
		elif wxData[len(wxData)-1][0] == 'A':
			global curUnits
			curUnits['Altimeter'] = 'inHg'
			altimeter = wxData.pop()[1:]
	#Some stations report both, but we only need one
	if wxData and (wxData[len(wxData)-1][0] == 'A' or wxData[len(wxData)-1][0] == 'Q'): wxData.pop()
	return wxData , altimeter

def __getTAFAltIceTurb(wxData):
	altimeter = ''
	icing , turbulence = [] , []
	for i in reversed(range(len(wxData))):
		if len(wxData[i]) > 6 and wxData[i].startswith('QNH') and wxData[i][3:7].isdigit():
			altimeter = wxData.pop(i)[3:7]
		elif wxData[i].isdigit():
			if wxData[i][0] == '6': icing.append(wxData.pop(i))
			elif wxData[i][0] == '5': turbulence.append(wxData.pop(i))
	return wxData , altimeter , icing , turbulence

def isPossibleTemp(temp):
	for index in temp:
		if index not in ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'M']: return False
	return True

#Temp/Dewpoint
def __getTempAndDewpoint(wxData):
	for i in reversed(range(len(wxData))):
		if wxData[i].find('/') != -1:
			curVal = copy(wxData[i])
			#///07
			if curVal[0] == '/': curVal = '/' + curVal.strip('/')
			#07///
			elif curVal[len(curVal)-1] == '/': curVal = curVal.strip('/') + '/'
			TD = curVal.split('/')
			if len(TD) != 2: continue
			isValid = True
			for j in range(len(TD)):
				if TD[j] in ['MM','XX']: TD[j] = ''
				elif not isPossibleTemp(TD[j]):
					isValid = False
					break
			if isValid:
				wxData.pop(i)
				return wxData , TD[0] , TD[1]
	return wxData , '' , ''

#Station and Time
def __getStationAndTime(wxData):
	station = wxData.pop(0)
	if wxData and wxData[0].endswith('Z') and wxData[0][:len(wxData[0])-1].isdigit(): rTime = wxData.pop(0)
	elif wxData and len(wxData[0]) == 6 and wxData[0].isdigit(): rTime = wxData.pop(0) + 'Z'
	else: rTime = ''
	return wxData , station , rTime

#Surface wind
def __getWindInfo(wxData):
	direction , speed , gust = '' , '' , ''
	variable = []
	global curUnits
	if wxData:
		item = copy(wxData[0])
		for rep in ['(E)']: item = item.replace(rep , '')
		item = item.replace('O' , '0')
		#09010KT , 09010G15KT
		if item.endswith('KT') \
		or item.endswith('KTS') \
		or item.endswith('MPS') \
		or item.endswith('KMH') \
		or ((len(item) == 5 or (len(item) >= 8 and item.find('G') != -1) and item.find('/') == -1) and (item[:5].isdigit() or (item.startswith('VRB') and item[3:5].isdigit()))):
			#In order of frequency
			if item.endswith('KT'): item = item.replace('KT' , '')
			elif item.endswith('KTS'): item = item.replace('KTS' , '')
			elif item.endswith('MPS'):
				curUnits['Wind-Speed'] = 'm/s'
				item = item.replace('MPS' , '')
			elif item.endswith('KMH'):
				curUnits['Wind-Speed'] = 'km/h'
				item = item.replace('KMH' , '')
			direction = item[:3]
			if item.find('G') != -1:
				gust = item[item.find('G')+1:]
				speed = item[3:item.find('G')]
			else: speed = item[3:]
			wxData.pop(0)
		#elif len(item) > 5 and item[3] == '/' and item[:3].isdigit() and item[4:6].isdigit():
			#direction = item[:3]
			#if item.find('G') != -1:
				#print('Found second G: {0}'.format(item))
				#gIndex = item.find('G')
				#gust = item[gIndex+1:gIndex+3]
				#speed = item[4:item.find('G')]
			#else:
				#speed = item[4:]
			#wxData.pop(0)
	#Separated Gust
	if wxData and 1 < len(wxData[0]) < 4 and wxData[0][0] == 'G' and wxData[0][1:].isdigit():
		gust = wxData.pop(0)[1:]
	#Variable Wind Direction
	if wxData and len(wxData[0]) == 7 and wxData[0][:3].isdigit() and wxData[0][3] == 'V' and wxData[0][4:].isdigit():
		variable = wxData.pop(0).split('V')
	return wxData , direction , speed , gust , variable

#Visibility
def __getVisibility(wxData):
	visibility = ''
	global curUnits
	if wxData:
		item = copy(wxData[0])
		#Vis reported in statue miles
		if item.endswith('SM'):   #10SM
			if item == 'P6SM': visibility = 'P6'
			elif item == 'M1/4SM': visibility = 'M1/4'
			elif item.find('/') == -1: visibility = str(int(item[:item.find('SM')]))  #str(int()) fixes 01SM
			else: visibility = item[:item.find('SM')] #1/2SM
			wxData.pop(0)
			curUnits['Visibility'] = 'sm'
		#Vis reported in meters
		elif len(item) == 4 and item.isdigit():
			visibility = wxData.pop(0)
			curUnits['Visibility'] = 'm'
		elif 5 <= len(item) <= 7 and item[:4].isdigit() and (item[4] in ['M','N','S','E','W'] or item[4:] == 'NDV'):
			visibility = wxData.pop(0)[:4]
			curUnits['Visibility'] = 'm'
		elif len(item) == 5 and item[1:5].isdigit() and item[0] in ['M','P','B']:
			visibility = wxData.pop(0)[1:5]
			curUnits['Visibility'] = 'm'
		elif item.endswith('KM') and item[:item.find('KM')].isdigit():
			visibility = item[:item.find('KM')] + '000'
			wxData.pop(0)
			curUnits['Visibility'] = 'm'
		#Vis statute miles but split
		elif len(wxData) > 1 and wxData[1].endswith('SM') and wxData[1].find('/') != -1 and item.isdigit():   #2 1/2SM
			vis1 = wxData.pop(0)  #2
			vis2 = wxData.pop(0).replace('SM','')  #1/2
			visibility = str(int(vis1)*int(vis2[2])+int(vis2[0]))+vis2[1:]  #5/2
			curUnits['Visibility'] = 'sm'
	return wxData , visibility

#TAF line report type and start/end times
def __getTypeAndTimes(wxData):
	reportType , startTime , endTime = 'BASE' , '' , ''
	#TEMPO, BECMG, INTER
	if wxData and wxData[0] in ['TEMPO','BECMG','INTER']: reportType = wxData.pop(0)
	#PROB[30,40]
	elif wxData and len(wxData[0]) == 6 and wxData[0].startswith('PROB'): reportType = wxData.pop(0)
	#1200/1306
	if wxData and len(wxData[0]) == 9 and wxData[0][4] == '/' and wxData[0][:4].isdigit() and wxData[0][5:].isdigit():
		times = wxData.pop(0).split('/')
		startTime , endTime = times[0] , times[1]
	#FM120000
	elif wxData and len(wxData[0]) > 7 and wxData[0].startswith('FM'):
		reportType = 'FROM'
		if wxData[0].find('/') != -1 and wxData[0][2:].split('/')[0].isdigit() and wxData[0][2:].split('/')[1].isdigit():
			tSplit = wxData.pop(0)[2:].split('/')
			startTime = tSplit[0]
			endTime = tSplit[1]
		elif wxData[0][2:8].isdigit(): startTime = wxData.pop(0)[2:6]
		#TL120600
		if wxData and len(wxData[0]) > 7 and wxData[0].startswith('TL') and wxData[0][2:8].isdigit(): endTime = wxData.pop(0)[2:6]
	return wxData , reportType , startTime , endTime

#Fix rare cloud layer issues
def sanitizeCloud(cloud):
	if len(cloud) < 4: return cloud
	if not cloud[3].isdigit() and cloud[3] != '/':
		if cloud[3] == 'O': cloud = cloud[:3] + '0' + cloud[4:]  #Bad "O": FEWO03 -> FEW003
		else:  #Move modifiers to end: BKNC015 -> BKN015C
			cloud = cloud[:3] + cloud[4:] + cloud[3]
	return cloud

#Transforms a cloud string into a list of strings: [Type , Height (, Optional Modifier)]
#Returns cloud string list
def splitCloud(cloud, beginsWithVV):
	splitCloud = []
	cloud = sanitizeCloud(cloud)
	if beginsWithVV:
		splitCloud.append(cloud[:2])
		cloud = cloud[2:]
	while len(cloud) >= 3:
		splitCloud.append(cloud[:3])
		cloud = cloud[3:]
	if cloud: splitCloud.append(cloud)
	if len(splitCloud) == 1: splitCloud.append('')
	return splitCloud

#Clouds
def __getClouds(wxData):
	clouds = []
	for i in reversed(range(len(wxData))):
		if wxData[i][:3] in cloudList:
			clouds.append(splitCloud(wxData.pop(i) , False))
		elif wxData[i][:2] == 'VV':
			clouds.append(splitCloud(wxData.pop(i) , True))
	return wxData , sorted(clouds , key=lambda pair: (pair[1],pair[0]))

#Returns int based on current flight rules from parsed METAR data
#0=VFR , 1=MVFR , 2=IFR , 3=LIFR
#Note: Common practice is to report IFR if visibility unavailable
def getFlightRules(vis , splitCloud):
	#Parse visibility
	if vis == '': return 2
	elif vis == 'P6': vis = 10
	elif vis.find('/') != -1:
		if vis[0] == 'M': vis = 0
		else: vis = int(vis.split('/')[0]) / int(vis.split('/')[1])
	elif len(vis) == 4 and vis.isdigit(): vis = int(vis) * 0.000621371  #Convert meters to miles
	else: vis = int(vis)
	#Parse ceiling
	if splitCloud: cld = int(splitCloud[1])
	else: cld = 99
	#Determine flight rules
	if (vis <= 5) or (cld <= 30):
		if (vis < 3) or (cld < 10):
			if (vis < 1) or (cld < 5):
				return 3 #LIFR
			return 2 #IFR
		return 1 #MVFR
	return 0 #VFR

#Returns list of ceiling layer from Cloud-List or None if none found
#Only 'Broken', 'Overcast', and 'Vertical Visibility' are considdered ceilings
#Prevents errors due to lack of cloud information (eg. '' or 'FEW///')
def getCeiling(clouds):
	for cloud in clouds:
		if len(cloud) > 1 and cloud[1].isdigit() and cloud[0] in ['OVC','BKN','VV']:
			return cloud
	return None

def parseRemarks(rmk):
	rmkData = {}
	rmk = rmk.split(' ')
	for item in rmk:
		if len(item) in [9,5] and item[0] == 'T' and item[1:].isdigit():
			if item[1] == '1': rmkData['Temp-Decimal'] = '-' + item[2].replace('0','') + item[3] + '.' + item[4]
			elif item[1] == '0': rmkData['Temp-Decimal'] = item[2].replace('0','') + item[3] + '.' + item[4]
			if len(item) == 9:
				if item[5] == '1': rmkData['Dew-Decimal'] = '-' + item[6].replace('0','') + item[7] + '.' + item[8]
				elif item[5] == '0': rmkData['Dew-Decimal'] = item[6].replace('0','') + item[7] + '.' + item[8]
	return rmkData

#Returns True if the station uses the North American format, False if the International format
def usesNAFormat(station):
	if station[0] in RegionsUsingUSParser: return True
	elif station[0] in RegionsUsingInternationalParser: return False
	elif station[:2] in MStationsUsingUSParser: return True
	elif station[:2] in MStationsUsingInternationalParser: return False

####################################################################################################################################
##--METAR Functions

#Get METAR report for 'station' from www.aviationweather.gov
#Returns METAR report string
#Else returns error int
#0=Bad connection , 1=Station DNE/Server Error
#getMETAR pulls from the ADDS API and is 3x faster than getMETAR2
def getMETAR(station):
	try:
		if sys.version_info[0] == 2:
			response = urllib2.urlopen(requestURL.format('metar' , station))
			xml = response.read()
		elif sys.version_info[0] == 3:
			response = urlopen(requestURL.format('metar' , station))
			xml = response.read().decode('utf-8')
		initDictString = json.dumps(parse(xml))
		for word in ['response' , 'data' , 'METAR' , station]:
			if initDictString.find(word) == -1: return 1
		retDict = json.loads(initDictString)['response']['data']['METAR']
		if type(retDict) == dict: return retDict['raw_text']
		elif type(retDict) == list and len(retDict) >= 1: return retDict[0]['raw_text']
		else: return 1
	except:
		return 0

#getMETAR2 scrapes the report from html
def getMETAR2(station):
	try:
		url = 'http://www.aviationweather.gov/metar/data?ids='+station+'&format=raw&date=0&hours=0'
		if sys.version_info[0] == 2:
			response = urllib2.urlopen(url)
			html = response.read()
		elif sys.version_info[0] == 3:
			response = urlopen(url)
			html = response.read().decode('utf-8')
		if html.find(station+'<') != -1: return 1   #Station does not exist/Database lookup error
		reportStart = html.find('<code>'+station+' ')+6      #Report begins with station iden
		reportEnd = html[reportStart:].find('<')        #Report ends with html bracket
		return html[reportStart:reportStart+reportEnd].replace('\n ','')
	except:
		return 0

#Returns a dictionary of parsed METAR data
#Keys: Station, Time, Wind-Direction, Wind-Speed, Wind-Gust, Wind-Variable-Dir, Visibility, Runway-Vis-List, Altimeter, Temperature, Dewpoint, Cloud-List, Other-List, Remarks, Raw-Report, Units
#Units is dict of identified units of measurement for each field
def parseMETAR(txt):
	if len(txt) < 2: return
	if usesNAFormat(txt[:2]): return parseUSMETAR(txt)
	else: return parseInternationalMETAR(txt)

def parseUSMETAR(txt):
	global curUnits
	curUnits = copy(naUnits)
	retWX = {'Raw-Report':txt}
	txt = __sanitizeFirstPass(txt)
	wxData , retWX['Remarks'] = __getRemarks(txt)
	wxData , retWX['Runway-Vis-List'] , notUsed = __sanitize(wxData)
	wxData , retWX['Station'] , retWX['Time'] = __getStationAndTime(wxData)
	wxData , retWX['Cloud-List'] = __getClouds(wxData)
	wxData , retWX['Wind-Direction'] , retWX['Wind-Speed'] , retWX['Wind-Gust'] , retWX['Wind-Variable-Dir'] = __getWindInfo(wxData)
	wxData , retWX['Altimeter'] = __getAltimeterUS(wxData)
	wxData , retWX['Visibility'] = __getVisibility(wxData)
	retWX['Other-List'] , retWX['Temperature'] , retWX['Dewpoint'] = __getTempAndDewpoint(wxData)
	retWX['Units'] = curUnits
	retWX['Flight-Rules'] = flightRules[getFlightRules(retWX['Visibility'] , getCeiling(retWX['Cloud-List']))]
	retWX['Remarks-Info'] = parseRemarks(retWX['Remarks'])
	return retWX

def parseInternationalMETAR(txt):
	global curUnits
	curUnits = copy(inUnits)
	retWX = {'Raw-Report':txt}
	txt = __sanitizeFirstPass(txt)
	wxData , retWX['Remarks'] = __getRemarks(txt)
	wxData , retWX['Runway-Vis-List'] , notUsed = __sanitize(wxData)
	wxData , retWX['Station'] , retWX['Time'] = __getStationAndTime(wxData)
	if 'CAVOK' not in wxData: wxData , retWX['Cloud-List'] = __getClouds(wxData)
	wxData , retWX['Wind-Direction'] , retWX['Wind-Speed'] , retWX['Wind-Gust'] , retWX['Wind-Variable-Dir'] = __getWindInfo(wxData)
	wxData , retWX['Altimeter'] = __getAltimeterInternational(wxData)
	if 'CAVOK' in wxData:
		retWX['Visibility'] = '9999'
		retWX['Cloud-List'] = []
		wxData.pop(wxData.index('CAVOK'))
	else:
		wxData , retWX['Visibility'] = __getVisibility(wxData)
	wxData , retWX['Temperature'] , retWX['Dewpoint'] = __getTempAndDewpoint(wxData)
	retWX['Other-List'] = wxData #Other weather
	retWX['Units'] = curUnits
	retWX['Flight-Rules'] = flightRules[getFlightRules(retWX['Visibility'] , getCeiling(retWX['Cloud-List']))]
	retWX['Remarks-Info'] = parseRemarks(retWX['Remarks'])
	return retWX

####################################################################################################################################
##--TAF Functions

#Get TAF report for 'station' from www.aviationweather.gov
#Returns TAF report string
#Else returns error int
#0=Bad Connection/Unknown Error , 1=Station DNE/Server Error , 2=Could Not Find Report Start
#getTAF pulls from the ADDS API and is 3x faster than getTAF2
def getTAF(station):
	try:
		if sys.version_info[0] == 2:
			response = urllib2.urlopen(requestURL.format('taf' , station))
			xml = response.read()
		elif sys.version_info[0] == 3:
			response = urlopen(requestURL.format('taf' , station))
			xml = response.read().decode('utf-8')
		initDictString = json.dumps(parse(xml))
		for word in ['response' , 'data' , 'TAF' , station]:
			if initDictString.find(word) == -1: return 1
		retDict = json.loads(initDictString)['response']['data']['TAF']
		if type(retDict) == dict: return retDict['raw_text']
		elif type(retDict) == list and len(retDict) >= 1: return retDict[0]['raw_text']
		else: return 1
	except:
		return 0

#getMETAR2 scrapes the report from html
def getTAF2(station):
	try:
		url = 'http://www.aviationweather.gov/taf/data?ids=' + station + '&format=raw&submit=Get+TAF+data'
		if sys.version_info[0] == 2:
			response = urllib2.urlopen(url)
			html = response.read()
		elif sys.version_info[0] == 3:
			response = urlopen(url)
			html = response.read().decode('utf-8')
		if html.find(station+'<') != -1: return 1                             #Station does not exist/Database lookup error
		reportStart = html.find('<code>TAF ')+6                               #Standard report begins with 'TAF'
		if reportStart == 5: reportStart = html.find('<code>'+station+' ')+6  #US report begins with station iden
		if reportStart == 5: return 2                                         #Beginning of report is non-standard/skewed
		reportEnd = html[reportStart:].find('</code>')                        #Report ends with html bracket
		return html[reportStart:reportStart+reportEnd].replace('\n ','')
	except:
		return 0

#Returns a dictionary of parsed TAF data
#'delim' is the divider between forecast lines. Ex: aviationweather.gov uses '<br/>  '
#Keys: Station, Time, Forecast, Remarks, Min-Temp, Max-Temp, Raw-Report, Units
#Oceania stations also have the following keys: Temp-List, Alt-List
#Forecast is list of report dicts in order of time with the following keys:
#Type , Start-Time, End-Time, Flight-Rules, Wind-Direction, Wind-Speed, Wind-Gust, Wind-Shear, Visibility, Altimeter, Cloud-List, Icing-List, Turb-List, Other-List, Probability, Raw-Line
#Units is dict of identified units of measurement for each field
def parseTAF(txt , delim='<br/>  '):
	retWX = {}
	retWX['Raw-Report'] = txt
	while len(txt) > 3 and txt[:4] in ['TAF ' , 'AMD ' , 'COR ']: txt = txt[4:]
	notUsed , retWX['Station'] , retWX['Time'] = __getStationAndTime(txt[:20].split(' '))
	txt = txt.replace(retWX['Station'] , '')
	txt = txt.replace(retWX['Time'] , '')
	global curUnits
	if usesNAFormat(retWX['Station']):
		isInternational = False
		curUnits = copy(naUnits)
	else:
		isInternational = True
		curUnits = copy(inUnits)
	retWX['Remarks'] = ''
	parsedLines = []
	prob = ''
	lines = txt.strip(' ').split(delim)
	while len(lines) > 0:
		line = lines[0].strip(' ')
		line = __sanitizeLine(line)
		#Remove Remarks from line
		index = findFirstInList(line , tafRMKStarts)
		if index != -1:
			retWX['Remarks'] = line[index:]
			line = line[:index].strip(' ')
		#Separate new lines fixed by sanitizeLine
		index = findFirstInList(line , tafNewLineStarts)
		if index != -1:
			lines.insert(1 , line[index+1:])
			line = line[:index]
		#Add empty PROB to next line data
		rawLine = line
		if len(line) == 6 and line.startswith('PROB'):
			prob = line
			line = ''
		if line:
			if isInternational: parsedLine = parseInternationalTAFLine(line)
			else: parsedLine = parseUSTAFLine(line)
			parsedLine['Probability'] = prob
			parsedLine['Raw-Line'] = rawLine
			prob = ''
			parsedLines.append(parsedLine)
		lines.pop(0)
	if parsedLines:
		parsedLines[len(parsedLines)-1]['Other-List'] , retWX['Max-Temp'] , retWX['Min-Temp'] = getTempMinAndMax(parsedLines[len(parsedLines)-1]['Other-List'])
		if not (retWX['Max-Temp'] or retWX['Min-Temp']): parsedLines[0]['Other-List'] , retWX['Max-Temp'] , retWX['Min-Temp'] = getTempMinAndMax(parsedLines[0]['Other-List'])
		parsedLines = findMissingTAFTimes(parsedLines)
		parsedLines = getTAFFlightRules(parsedLines)
	else:
		retWX['Min-Temp'] = ['','']
		retWX['Max-Temp'] = ['','']
	if retWX['Station'][0] == 'A': parsedLines[len(parsedLines)-1]['Other-List'] , retWX['Alt-List'] , retWX['Temp-List'] = getOceaTandQ(parsedLines[len(parsedLines)-1]['Other-List'])
	retWX['Forecast'] = parsedLines
	retWX['Units'] = curUnits
	return retWX

def parseUSTAFLine(txt):
	global curUnits
	curUnits = naUnits
	retWX = {}
	wxData = txt.split(' ')
	wxData , notUsed , retWX['Wind-Shear'] = __sanitize(wxData , removeCLRandSKC=False)
	wxData , retWX['Type'] , retWX['Start-Time'] , retWX['End-Time'] = __getTypeAndTimes(wxData)
	wxData , retWX['Wind-Direction'] , retWX['Wind-Speed'] , retWX['Wind-Gust'] , notUsed = __getWindInfo(wxData)
	wxData , retWX['Visibility'] = __getVisibility(wxData)
	wxData , retWX['Cloud-List'] = __getClouds(wxData)
	retWX['Other-List'] , retWX['Altimeter'] , retWX['Icing-List'] , retWX['Turb-List'] = __getTAFAltIceTurb(wxData)
	return retWX

def parseInternationalTAFLine(txt):
	global curUnits
	curUnits = inUnits
	retWX = {}
	wxData = txt.split(' ')
	wxData , notUsed , retWX['Wind-Shear'] = __sanitize(wxData , removeCLRandSKC=False)
	wxData , retWX['Type'] , retWX['Start-Time'] , retWX['End-Time'] = __getTypeAndTimes(wxData)
	wxData , retWX['Wind-Direction'] , retWX['Wind-Speed'] , retWX['Wind-Gust'] , notUsed = __getWindInfo(wxData)
	if 'CAVOK' in wxData:
		retWX['Visibility'] = '9999'
		retWX['Cloud-List'] = []
		wxData.pop(wxData.index('CAVOK'))
	else:
		wxData , retWX['Visibility'] = __getVisibility(wxData)
		wxData , retWX['Cloud-List'] = __getClouds(wxData)
	retWX['Other-List'] , retWX['Altimeter'] , retWX['Icing-List'] , retWX['Turb-List'] = __getTAFAltIceTurb(wxData)
	return retWX

#Fixes common mistakes with 'new line' signifiers so that they can be recognized
lineFixes = {'TEMP0':'TEMPO','TEMP O':'TEMPO','TMPO':'TEMPO','TE MPO':'TEMPO','TEMP ':'TEMPO ',' EMPO':' TEMPO','TEMO':'TEMPO','TMPO':'TEMPO','T EMPO':'TEMPO','BECM G':'BECMG','BEMCG':'BECMG','BE CMG':'BECMG','BEMG':'BECMG',' BEC ':' BECMG ','BCEMG':'BECMG','B ECMG':'BECMG'}
def __sanitizeLine(txt):
	for key in lineFixes:
		index = txt.find(key)
		if index > -1: txt = txt[:index] + lineFixes[key] + txt[index+len(key):]
	#Fix when space is missing following new line signifiers
	for item in ['BECMG' , 'TEMPO']:
		if txt.find(item) != -1 and txt.find(item + ' ') == -1:
			insertIndex = txt.find(item)+len(item)
			txt = txt[:insertIndex] + ' ' + txt[insertIndex:]
	return txt

#Pull out Max temp at time and Min temp at time items
def getTempMinAndMax(otherList):
	tempMax , tempMin  = '' , ''
	for i in reversed(range(len(otherList))):
		item = otherList[i]
		if len(item) > 6 and item[0] == 'T' and item.find('/') != -1:
			#TX12/1316Z
			if item[1] == 'X':
				tempMax = item
				otherList.pop(i)
			#TNM03/1404Z
			elif item[1] == 'N':
				tempMin = item
				otherList.pop(i)
			#TM03/1404Z T12/1316Z   -> Will fix TN/TX
			elif item[1] == 'M' or item[1].isdigit():
				if tempMin:
					if int(tempMin[2:tempMin.find('/')].replace('M','-')) > int(item[1:item.find('/')].replace('M','-')):
						tempMax = 'TX' + tempMin[2:]
						tempMin = 'TN' + item[1:]
					else: tempMax = 'TX' + item[1:]
				else: tempMin = 'TN' + item[1:]
				otherList.pop(i)
	return otherList , tempMax , tempMin

#Returns a list of items removed from a given list that are all digits from 'fromIndex' until hitting a non-digit item
def getDigitList(aList , fromIndex):
	retList = []
	aList.pop(fromIndex)
	while len(aList) > fromIndex and aList[fromIndex].isdigit(): retList.append(aList.pop(fromIndex))
	return aList , retList

#Get Temp and Alt list for Oceania TAFs
def getOceaTandQ(otherList):
	tList , qList = [] , []
	if 'T' in otherList: otherList , tList = getDigitList(otherList , otherList.index('T'))
	if 'Q' in otherList: otherList , qList = getDigitList(otherList , otherList.index('Q'))
	return otherList , tList , qList

#Fix any missing time issues (except for error/empty lines)
def findMissingTAFTimes(tafLines):
	lastFMLine = 0
	for i , line in enumerate(tafLines):
		if line['End-Time'] == '' and isNotTempoOrProb(line['Type']):
			lastFMLine = i
			if i < len(tafLines)-1:
				for report in tafLines[i+1:]:
					if isNotTempoOrProb(report['Type']): #Ignore TEMPO and PROB
						line['End-Time'] = report['Start-Time']
						break
	if lastFMLine > 0: tafLines[lastFMLine]['End-Time'] = tafLines[0]['End-Time'] #Special case for final forcast
	return tafLines

#Get flight rules by looking for missing data in prior reports
def getTAFFlightRules(tafLines):
	for i , line in enumerate(tafLines):
		tempVis , tempCloud = line['Visibility'] , line['Cloud-List']
		for report in reversed(tafLines[:i]):
			if isNotTempoOrProb(report['Type']): #Ignore TEMPO and PROB
				if tempVis == '': tempVis = report['Visibility']
				if 'SKC' in report['Other-List'] or 'CLR' in report['Other-List']: tempCloud = 'tempClear'
				elif tempCloud == []: tempCloud = report['Cloud-List']
				if tempVis != '' and tempCloud != []: break
		if tempCloud == 'tempClear': tempCloud = []
		line['Flight-Rules'] = flightRules[getFlightRules(tempVis , getCeiling(tempCloud))]
		#print("Using " + str(tempVis) + ' and ' + str(getCeiling(tempCloud)) + ' gives ' + str(line['Flight-Rules']))
	return tafLines

def isNotTempoOrProb(reportType):
	return reportType != 'TEMPO' and not (len(reportType) == 6 and reportType.startswith('PROB'))

####################################################################################################################################
##--Translation Functions

#Format wind elements into a readable sentence
#Returns the translation string
#Ex: NNE-020 (variable 010 to 040) at 14kt gusting to 20kt
def translateWind(wDir , wSpd , wGst , wVar=[] , unit='kt'):
	ret = ''
	#Wind Direction - Cheat Sheet
	#(360) -- 011/012 -- 033/034 -- (045) -- 056/057 -- 078/079 -- (090)
	#(090) -- 101/102 -- 123/124 -- (135) -- 146/147 -- 168/169 -- (180)
	#(180) -- 191/192 -- 213/214 -- (225) -- 236/237 -- 258/259 -- (270)
	#(270) -- 281/282 -- 303/304 -- (315) -- 326/327 -- 348/349 -- (360)
	if wDir == '000': ret += 'Calm'
	elif wDir.isdigit():
		wDir = int(wDir)
		if 304 <= wDir <= 360 or 0 <= wDir <= 56:
			ret += 'N'
			if 304 <= wDir <= 348:
				if 327 <= wDir <= 348: ret += 'N'
				ret += 'W'
			elif 11 <= wDir <= 56:
				if 11 <= wDir <= 33: ret += 'N'
				ret += 'E'
		elif 124 <= wDir <= 236:
			ret += 'S'
			if 124 <= wDir <= 168:
				if 147 <= wDir <= 168: ret += 'S'
				ret += 'E'
		elif 57 <= wDir <= 123:
			ret += 'E'
			if 57 <= wDir <= 78: ret += 'NE'
			elif 102 <= wDir <= 123: 'SE'
		elif 237 <= wDir <= 303:
			ret += 'W'
			if 237 <= wDir <= 258: ret += 'SW'
			elif 282 <= wDir <= 303: ret += 'NW'
		ret += '-' + str(wDir)
	elif wDir == 'VRB': ret += 'Variable'
	if wVar:
		ret += ' (variable ' + wVar[0] + ' to ' + wVar[1] + ')'
	if wSpd and wSpd != '00':
		ret += ' at ' + wSpd + unit
	if wGst:
		ret += ' gusting to ' + wGst + unit
	return ret

#Formats a visibility element into a string with both km and sm values
#Ex: 8km ( 5sm )
def translateVisibility(vis , unit='m'):
	if vis == 'P6': return 'Greater than 6sm ( >9999m )'
	if vis == 'M1/4': return 'Less than .25sm ( <0400m )'
	if vis.find('/') != -1: vis = float(vis[:vis.find('/')]) / int(vis[vis.find('/')+1:])
	try: float(vis)
	except ValueError: return ''
	if unit == 'm':
		converted = float(vis) * 0.000621371
		converted = str(round(converted , 1)).replace('.0' , '') + 'sm'
		vis = str(round(int(vis)/1000.0 , 1)).replace('.0' , '')
		unit = 'km'
	elif unit == 'sm':
		converted = float(vis) / 0.621371
		converted = str(round(converted , 1)).replace('.0' , '') + 'km'
		vis = str(vis).replace('.0' , '')
	else: return ''
	return vis + unit + ' (' + converted + ')'

#Formats a temperature element into a string with both C and F values
#Used for both Temp and Dew
#Ex: 34C (93F)
def translateTemp(temp , unit='C'):
	temp = temp.replace('M','-')
	try: int(temp)
	except ValueError: return ''
	unit = unit.upper()
	if unit == 'C':
		converted = int(temp) * 1.8 + 32
		converted = str(int(round(converted))) + 'F'
	elif unit == 'F':
		converted = (int(temp) - 32) / 1.8
		converted = str(int(round(converted))) + 'C'
	else: return ''
	return temp + unit + ' (' + converted + ')'

#Formats the altimter element into a string with hPa and inHg values
#Ex: 30.11 inHg (10.20 hPa)
def translateAltimeter(alt , unit='hPa'):
	if alt.isdigit(): 1
	elif not alt.isdigit() and len(alt) == 5 and alt[1:].isdigit(): alt = alt[1:]
	else: return ''
	if unit == 'hPa':
		converted = float(alt) / 33.8638866667
		converted = str(round(converted , 2)) + 'inHg'
	elif unit == 'inHg':
		alt = alt[:2] + '.' + alt[2:]
		converted = float(alt) * 33.8638866667
		converted = str(int(round(converted))) + 'hPa'
	else: return ''
	return alt + unit + ' (' + converted + ')'

#Format cloud list into a readable sentence
#Returns the translation string
#Ex: Scattered clouds at 1100ft, Broken layer at 2200ft (Cumulonimbus), Overcast layer at 3600ft - Reported AGL
cloudTranslationStrings = {
	'OVC':'Overcast layer at {0}{1}',
	'BKN':'Broken layer at {0}{1}',
	'SCT':'Scattered clouds at {0}{1}',
	'FEW':'Few clouds at {0}{1}',
	'VV':'Vertical visibility up to {0}{1}',
	'CLR':'Sky Clear',
	'SKC':'Sky Clear',
	'AC':'Altocumulus',
	'ACC':'Altocumulus Castellanus',
	'AS':'Altostratus',
	'CB':'Cumulonimbus',
	'CC':'Cirrocumulus',
	'CI':'Cirrus',
	'CS':'Cirrostratus',
	'CU':'Cumulus',
	'FC':'Fractocumulus',
	'FS':'Fractostratus',
	'NS':'Nimbostratus',
    'SC':'Stratocumulus',
	'ST':'Stratus',
	'TCU':'Towering Cumulus'
	}
def translateClouds(cloudList , unit='ft'):
	retList = []
	for cloud in cloudList:
		if len(cloud) == 2 and cloud[1].isdigit() and cloud[0] in cloudTranslationStrings: retList.append(cloudTranslationStrings[cloud[0]].format(int(cloud[1])*100 , unit))
		elif len(cloud) == 3 and cloud[1].isdigit() and cloud[0] in cloudTranslationStrings and cloud[2] in cloudTranslationStrings: retList.append((cloudTranslationStrings[cloud[0]]+' ('+cloudTranslationStrings[cloud[2]]+')').format(int(cloud[1])*100 , unit))
	if retList:	return ', '.join(retList) + ' - Reported AGL'
	return 'Sky clear'

#Translates weather codes into readable strings
#Returns translated string of variable length
def translateWX(wx):
	wxString = ''
	if wx[0] == '+':
		wxString = 'Heavy '
		wx = wx[1:]
	elif wx[0] == '-':
		wxString = 'Light '
		wx = wx[1:]
	if len(wx) not in [2,4,6]: return wx  #Return wx if wx is not a code, ex R03/03002V03
	for i in range(len(wx)//2):
		if wx[:2] in wxReplacements: wxString += wxReplacements[wx[:2]] + ' '
		else: wxString += wx[:2]
		wx = wx[2:]
	return wxString.strip(' ')

#Translate the list of wx codes (otherList) into a readable sentence
#Returns the translation string
def translateOtherList(wxList):
	retList = []
	for item in wxList: retList.append(translateWX(item))
	return ', '.join(retList)

#Translate wind shear into a readable string
#Ex: Wind shear 2000ft from 140 at 30kt
def translateWindShear(shear , unitAlt='ft' , unitWnd='kt'):
	if not shear or shear.find('WS') == -1 or shear.find('/') == -1: return ''
	shear = shear[2:].split('/')
	return 'Wind shear ' + str(int(shear[0])*100) + unitAlt + ' from ' + shear[1][:3] + ' at ' + shear[1][3:] + unitWnd

#Translate the list of turbulance or icing into a readable sentence
#Ex: Occasional moderate turbulence in clouds from 3000ft to 14000ft
turbConditions = {'0':'None','1':'Light turbulence','2':'Occasional moderate turbulence in clear air','3':'Frequent moderate turbulence in clear air','4':'Occasional moderate turbulence in clouds','5':'Frequent moderate turbulence in clouds','6':'Occasional severe turbulence in clear air','7':'Frequent severe turbulence in clear air','8':'Occasional severe turbulence in clouds','9':'Frequent severe turbulence in clouds','X':'Extreme turbulence'}
iceConditions = {'0':'No icing','1':'Light icing','2':'Light icing in clouds','3':'Light icing in precipitation','4':'Moderate icing','5':'Moderate icing in clouds','6':'Moderate icing in precipitation','7':'Severe icing','8':'Severe icing in clouds','9':'Severe icing in precipitation'}
def translateTurbIce(aList , unit='ft'):
	if not aList: return ''
	#Determine turbulance or icing
	if aList[0][0] == '5':
		layerType = 'Turbulance'
		conditions = turbConditions
	elif aList[0][0] == '6':
		layerType = 'Icing'
		conditions = iceConditions
	else: return ''
	#Create list of split items (type , floor , height)
	splitList = []
	for item in aList:
		if len(item) == 6: splitList.append([item[1:2],item[2:5],item[5:6]])
	#Combine items that cover a layer greater than 9000ft
	for i in reversed(range(len(splitList)-1)):
		if splitList[i][2] == '9' and splitList[i][0] == splitList[i+1][0] and int(splitList[i+1][1]) == (int(splitList[i][1]) + int(splitList[i][2])*10):
			splitList[i][2] = str(int(splitList[i][2]) + int(splitList[i+1][2]))
			splitList.pop(i+1)
	#Return joined, formatted string from splitList items
	return ', '.join(['{0} from {1}{3} to {2}{3}'.format(conditions[item[0]] , int(item[1])*100 , int(item[1])*100 + int(item[2])*1000 , unit) for item in splitList])

#Format the Min and Max temp elemets into a readable string
#Ex: Maximum temperature of 23C (73F) at 18-15:00Z
def translateMinMaxTemp(temp , unit='C'):
	if not temp or len(temp) < 7: return ''
	if temp[:2] == 'TX': tempType = 'Maximum'
	elif temp[:2] == 'TN': tempType = 'Minimum'
	else: return ''
	temp = temp[2:].replace('M' , '-').replace('Z','').split('/')
	if len(temp[1]) > 2: temp[1] = temp[1][:2] + '-' + temp[1][2:]
	return tempType + ' temperature of ' + translateTemp(temp[0] , unit) + ' at ' + temp[1] + ':00Z'

#Translate Visibility, Altimeter, Clouds, and Other
def translateShared(wxData , units):
	translations = {}
	translations['Visibility'] = translateVisibility(wxData['Visibility'] , units['Visibility'])
	translations['Altimeter'] = translateAltimeter(wxData['Altimeter'] , units['Altimeter'])
	translations['Clouds'] = translateClouds(wxData['Cloud-List'] , units['Altitude'])
	translations['Other'] = translateOtherList(wxData['Other-List'])
	return translations

#Translate the results of parseMETAR
#Keys: Wind, Visibility, Clouds, Temperature, Dewpoint, Altimeter, Other
def translateMETAR(wxData):
	units = wxData['Units']
	translations = translateShared(wxData , units)
	translations['Wind'] = translateWind(wxData['Wind-Direction'] , wxData['Wind-Speed'] , wxData['Wind-Gust'] , wxData['Wind-Variable-Dir'] , units['Wind-Speed'])
	translations['Temperature'] = translateTemp(wxData['Temperature'] , units['Temperature'])
	translations['Dewpoint'] = translateTemp(wxData['Dewpoint'] , units['Temperature'])
	return translations

#Translate the results of parseTAF
#Keys: Forecast, Min-Temp, Max-Temp
#Forecast keys: Wind, Visibility, Clouds, Altimeter, Wind-Shear, Turbulance, Icing, Other
def translateTAF(wxData):
	translations = {'Forecast':[]}
	units = wxData['Units']
	for line in wxData['Forecast']:
		transLine = translateShared(line , units)
		transLine['Wind'] = translateWind(line['Wind-Direction'] , line['Wind-Speed'] , line['Wind-Gust'] , unit=units['Wind-Speed'])
		transLine['Wind-Shear'] = translateWindShear(line['Wind-Shear'] , wxData['Units']['Altitude'] , units['Wind-Speed'])
		transLine['Turbulance'] = translateTurbIce(line['Turb-List'] , units['Altitude'])
		transLine['Icing'] = translateTurbIce(line['Icing-List'] , units['Altitude'])
		translations['Forecast'].append(transLine)
	translations['Min-Temp'] = translateMinMaxTemp(wxData['Min-Temp'] , units['Temperature'])
	translations['Max-Temp'] = translateMinMaxTemp(wxData['Max-Temp'] , units['Temperature'])
	return translations

def createMETARSummary(wxTrans):
	sumList = []
	if 'Wind' in wxTrans and wxTrans['Wind']: sumList.append('Winds ' + wxTrans['Wind'])
	if 'Visibility' in wxTrans and wxTrans['Visibility']: sumList.append('Vis ' + wxTrans['Visibility'][:wxTrans['Visibility'].find(' (')].lower())
	if 'Temperature' in wxTrans and wxTrans['Temperature']: sumList.append('Temp ' + wxTrans['Temperature'][:wxTrans['Temperature'].find(' (')])
	if 'Dewpoint' in wxTrans and wxTrans['Dewpoint']: sumList.append('Dew ' + wxTrans['Dewpoint'][:wxTrans['Dewpoint'].find(' (')])
	if 'Altimeter' in wxTrans and wxTrans['Altimeter']: sumList.append('Alt ' + wxTrans['Altimeter'][:wxTrans['Altimeter'].find(' (')])
	if 'Other' in wxTrans and wxTrans['Other']: sumList.append(wxTrans['Other'])
	if 'Clouds' in wxTrans and wxTrans['Clouds']: sumList.append(wxTrans['Clouds'].replace(' - Reported AGL' , ''))
	return ', '.join(sumList)

def createTAFLineSummary(wxTrans):
	sumList = []
	if 'Wind' in wxTrans and wxTrans['Wind']: sumList.append('Winds ' + wxTrans['Wind'])
	if 'Visibility' in wxTrans and wxTrans['Visibility']: sumList.append('Vis ' + wxTrans['Visibility'][:wxTrans['Visibility'].find(' (')].lower())
	if 'Altimeter' in wxTrans and wxTrans['Altimeter']: sumList.append('Alt ' + wxTrans['Altimeter'][:wxTrans['Altimeter'].find(' (')])
	if 'Other' in wxTrans and wxTrans['Other']: sumList.append(wxTrans['Other'])
	if 'Clouds' in wxTrans and wxTrans['Clouds']: sumList.append(wxTrans['Clouds'].replace(' - Reported AGL' , ''))
	if 'Wind-Shear' in wxTrans and wxTrans['Wind-Shear']: sumList.append(wxTrans['Wind-Shear'])
	if 'Turbulance' in wxTrans and wxTrans['Turbulance']: sumList.append(wxTrans['Turbulance'])
	if 'Icing' in wxTrans and wxTrans['Icing']: sumList.append(wxTrans['Icing'])
	return ', '.join(sumList)

####################################################################################################################################
##--Station data

#Provide basic station info with the keys below
dbHeaders = ['ICAO','Country','State','City','Name','IATA','Elevation','Latitude','Longitude','Priority']
def getInfoForStation(station):
	conn = sqlite3.connect(stationDBPath)
	#conn.text_factory = str
	curs = conn.cursor()
	curs.execute('SELECT '+','.join(dbHeaders)+' FROM Stations WHERE icao=?' , (station,))
	row = curs.fetchone()
	ret = {}
	if row:
		for i in range(len(row)): ret[dbHeaders[i]] = row[i]
	return ret

####################################################################################################################################
##--Example Testing
#These tests provide example usage for the primary public functions

#Adds timestamp to begining of print statement
#Returns string of time + logString
def timestamp(logString): return strftime('%d %H:%M:%S - ') + logString

#Retrive, parse, and display METAR report
def metarTest(station):
	ret = timestamp(station + '\n\n')
	txt = getMETAR(station)
	#txt = 'VTSF 230200Z 22006KT 1701V240 9999 FEW020 30/25 Q1013 A2993 INFO C / RWY 19'
	if type(txt) == int:
		if txt: ret += 'Station does not exist/Database lookup error'
		else: ret += 'http connection error'
	else:
		data = parseMETAR(txt)
		for key in data: ret += '{0}  --  {1}\n'.format(key , data[key])
		ret += 'Flight rules for "{0}" and "{1}"  --  "{2}"'.format(data['Visibility'] , getCeiling(data['Cloud-List']) , flightRules[getFlightRules(data['Visibility'] , getCeiling(data['Cloud-List']))])
		translation = translateMETAR(data)
		ret += '\n\nTranslation'
		for key in translation: ret += '\n' + key + ':   ' + translation[key]
		ret += '\nSummary: ' + createMETARSummary(translation) + '\n'
		#ret += str(getInfoForStation(station))
	print(ret)

#Retrive, parse, and display TAF report
def tafTest(station):
	ret = timestamp(station + '\n\n')
	txt = getTAF(station)
	#txt = 'TAF OPLA 180345Z 1806/1912 33008KT 4000 HZ NSC TX42/1810Z TN29/1900Z <br/>  PROB30 <br/>  TEMPO 1810/1812 32015G25KT 3000 DRDU FM 181500 TL 190100 25003KT 4000 HZ NSC <br/>  TEMPO 1901/1903 30004KT 2500 FU'
	if type(txt) == int:
		if txt: ret += 'Station does not exist/Database lookup error'
		else: ret += 'http connection error'
	else:
		delim = '<br/>  '
		ret = timestamp(station + '\n\n') + txt + '\n\n'
		taf = parseTAF(txt , delim)
		#Print report lines
		for line in txt.strip(' ').split(delim): ret += line + '\n'
		#Print header data
		ret += '\n' + taf['Station'] + '\n' + taf['Time'] + '\n' + taf['Remarks'] + '\n' + str(taf['Units']) + '\n' + str(taf['Min-Temp']) + '\n' + str(taf['Max-Temp']) + '\n\n'
		#Print Forecasts' start and end times
		for line in taf['Forecast']:
			ret += line['Start-Time'] + ' - ' + line['End-Time'] + '\n'
		ret += '\n'
		#Print Forecast dicts
		for lineDict in taf['Forecast']: ret += str(lineDict) + '\n\n'
		#Print Translation
		trans = translateTAF(taf)
		ret += 'Translation\n\n' + str(trans) + '\n\nSummary:'
		for line in trans['Forecast']: ret += '\n' + createTAFLineSummary(line)
	print(ret)

if __name__ == '__main__':
	station = 'CWER'
	#print(getInfoForStation(station))
	metarTest(station)
	print('\n------------------------------------------\n')
	#tafTest(station)


from enums.signal import Signal
from indicators import MacdIndicator, RsiIndicator
from strategies import Strategy
from common.exceptions import StrategyException

class MacdRsiStrategy(Strategy):

    def __init__(self, macd: MacdIndicator, rsi: RsiIndicator):
        self._macd = macd
        self._rsi = rsi

    @property
    def name(self):
        return self.__class__.__name__

    def execute(self) -> Signal:
        macds, signals = self._macd.calculate()
        rsis = self._rsi.calculate()

        last_macd, last_signal = macds[-1], signals[-1]
        last_rsi = rsis[-1]

        if last_macd > last_signal and last_rsi < self._rsi.overbought:
            signal = Signal.BUY
        elif last_macd < last_signal and last_rsi > self._rsi.oversold:
            signal = Signal.SELL
        else:
            signal = Signal.HOLD

        return signal


# print((50000000000/100)*4000 + 1684)
# Part 2: var 100 generation ökar 4000. m värdet är 1684
# # 98 5684
# # 198 9684
# # 298 13684
# # 398 17684
# # 498 21684

init_state = "##.......#.######.##..#...#.#.#..#...#..####..#.##...#....#...##..#..#.##.##.###.##.#.......###....#"
test1 = "...##"

# print(file_lines[1][0:5])
# print(file_lines[1][9:10], end="")

def next_generation(state):
    if type(state) is str:
        pots = {}
        for idx, ch in enumerate(state):
            pots[idx] = ch
    else:
        pots = state

    #for key in sorted(pots.keys()):
        #print(key, pots[key])

    first_key = None
    last_key = None
    for k in sorted(pots.keys()):
        first_key = k if first_key == None else first_key
        last_key = k if last_key == None or last_key < k else last_key

    for a in range(last_key + 1, last_key + 6):
        pots[a] = "."
    for b in range(first_key - 1, first_key - 6, -1):
        pots[b] = "."

    #for key in sorted(pots.keys()):
    #    print(key, pots[key])

    return_dick = {}
    path = r"C:\Users\Herman\Desktop\PROGRAMMERING\Projekt\Advent_of_code\adv12.txt"
    with open(path) as file:
        file_lines = file.read().splitlines()
    for key in sorted(pots.keys()):
        # print(key)
        part_of_line = ""
        is_in_dot = False
        is_in_hash = False
        if (key - 2) in pots and (key + 2) in pots:
            for k2 in range(key - 2, key + 3):
                #print("k2", k2)
                #print(pots[k2])
                part_of_line += pots[k2]
            for line in file_lines:
                #print(key, part_of_line, line[0:5], " ", end="")
                if line[9:10] == "#" and part_of_line == line[0:5]:
                    is_in_hash = True
                if line[9:10] == "." and part_of_line == line[0:5]:
                    is_in_dot = True
        if is_in_hash:
            return_dick[key] = "#"
        if is_in_dot:
            return_dick[key] = "."
        if not is_in_hash and not is_in_dot:
            return_dick[key] = "."
    return return_dick

g = next_generation(init_state)
for d in sorted(g.keys()):
    print(d, g[d])
for _ in range(2000 - 1):
    # print("generation",_)
    g = next_generation(g)
    if (_ + 2) % 100 == 0:
        # print(g)
        plant_id_sum = 0
        for ke in sorted(g.keys()):
            if g[ke] == "#":
                plant_id_sum += ke
        print(_, plant_id_sum)

# print((50000000000/100)*4000 + 1684)
# Part 2: var 100 ökning ökar 4000. m värdet är 1684
# # 98 5684
# # 198 9684
# # 298 13684
# # 398 17684
# # 498 21684

# print((500/100)

#     first_key = None
#     last_key = None
#     for key in sorted(pots.keys()):
#         first_key = first_key or key
#         last_key = last_key or key
#         last_key = key if last_key < key else last_key
#
#     for a in range(last_key + 1, last_key + 6):
#         pots[a] = "."
#     for b in range(first_key - 1, first_key - 6, -1):
#         pots[b] = "."
#
#
#

#
#     return_dick = {}
#     for key in sorted(pots.keys()):
#         part_of_line = ""
#         is_in = False
#         if (key - 2) in pots and (key + 2) in pots:
#             for k in range(key - 2, key + 3):
#                 part_of_line += pots[k]
#             for line in file_lines:
#                 #print(key, part_of_line, line[0:5], " ", end="")
#                 if part_of_line == line[0:5]:
#                     #print("3","match", end="")
#                     is_in = True
#                 #print()
#         if is_in:
#             return_dick[key] = "#"
#         else:
#             return_dick[key] = "."
#     return return_dick
#
# gen1 = generationizer(init_state)
# gen2 = generationizer(gen1)
#
# for key2 in sorted(gen2.keys()):
#     print(key2,gen2[key2], end="")


# -*- coding: utf-8 -*-
"""
Created on Sun Sep 29 00:26:54 2019

@author: RUDRAJIT
"""
from PIL import Image

img=Image.open('test2.png')
rgb_im=img.convert('RGB')#convert image to rgb value
r,g,b=rgb_im.getpixel((101,1))#row column
print(r,g,b)


#
# @lc app=leetcode id=445 lang=python3
#
# [445] Add Two Numbers II
#

# @lc code=start
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
  '''
  Accepted
  1563/1563 cases passed (72 ms)
  Your runtime beats 99.55 % of python3 submissions
  Your memory usage beats 12.96 % of python3 submissions (14.4 MB)

  Time complexity : O(max(m, n))
  Space complexity : O(max(m, n))
  '''
  def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:
    num1, num2 = [], []

    while l1 is not None:
      num1.append(l1.val)
      l1 = l1.next

    while l2 is not None:
      num2.append(l2.val)
      l2 = l2.next

    retval = None
    carry = 0
    while len(num1) > 0 or len(num2) > 0:
      if len(num1) > 0 and len(num2) > 0:
        sum_ = num1.pop() + num2.pop()
      elif len(num1) > 0:
        sum_ = num1.pop()
      else:
        sum_ = num2.pop()

      retval = ListNode((sum_ + carry) % 10, retval)
      carry = (sum_ + carry) // 10

    if carry > 0:
      retval = ListNode(carry, retval)

    return retval
# @lc code=end


"""
    Veti primi un string de la tastatura.
    Va trebui sa printati un tuplu care sa contina toate literele acelui string.

    exemplu:
        Veti primi: 'cmi'
        Veti printa: ('c', 'm', 'i')
"""
x = input()
l1 = []
for i in x:
    l1.append(i)
print(tuple(l1))


#!/usr/bin/env python
"""Sub-sample sequence from a FASTA, FASTQ or SFF file.

This tool is a short Python script which requires Biopython 1.62 or later
for SFF file support. If you use this tool in scientific work leading to a
publication, please cite the Biopython application note:

Cock et al 2009. Biopython: freely available Python tools for computational
molecular biology and bioinformatics. Bioinformatics 25(11) 1422-3.
http://dx.doi.org/10.1093/bioinformatics/btp163 pmid:19304878.

This script is copyright 2010-2013 by Peter Cock, The James Hutton Institute
(formerly the Scottish Crop Research Institute, SCRI), UK. All rights reserved.
See accompanying text file for licence details (MIT license).

This is version 0.1.0 of the script, use -v or --version to get the version.
"""
import os
import sys

def stop_err(msg, err=1):
    sys.stderr.write(msg.rstrip() + "\n")
    sys.exit(err)

if "-v" in sys.argv or "--version" in sys.argv:
    print("v0.1.0")
    sys.exit(0)

#Parse Command Line
if len(sys.argv) < 5:
    stop_err("Requires at least four arguments: seq_format, in_file, out_file, mode, ...")
seq_format, in_file, out_file, mode = sys.argv[1:5]
if in_file != "/dev/stdin" and not os.path.isfile(in_file):
    stop_err("Missing input file %r" % in_file)

if mode == "everyNth":
    if len(sys.argv) != 6:
        stop_err("If using everyNth, just need argument N (integer, at least 2)")
    try:
        N = int(sys.argv[5])
    except:
        stop_err("Bad N argument %r" % sys.argv[5])
    if N < 2:
        stop_err("Bad N argument %r" % sys.argv[5])
    if (N % 10) == 1:
        sys.stderr.write("Sampling every %ist sequence\n" % N)
    elif (N % 10) == 2:
        sys.stderr.write("Sampling every %ind sequence\n" % N)
    elif (N % 10) == 3:
        sys.stderr.write("Sampling every %ird sequence\n" % N)
    else:
        sys.stderr.write("Sampling every %ith sequence\n" % N)
    def sampler(iterator):
        global N
        count = 0
        for record in iterator:
            count += 1
            if count % N == 1:
                yield record
elif mode == "percentage":
    if len(sys.argv) != 6:
        stop_err("If using percentage, just need percentage argument (float, range 0 to 100)")
    try:
        percent = float(sys.argv[5]) / 100.0
    except:
        stop_err("Bad percent argument %r" % sys.argv[5])
    if percent <= 0.0 or 1.0 <= percent:
        stop_err("Bad percent argument %r" % sys.argv[5])
    sys.stderr.write("Sampling %0.3f%% of sequences\n" % (100.0 * percent))
    def sampler(iterator):
        global percent
        count = 0
        taken = 0
        for record in iterator:
            count += 1
            if percent * count > taken:
                taken += 1
                yield record
else:
    stop_err("Unsupported mode %r" % mode)

def raw_fasta_iterator(handle):
    """Yields raw FASTA records as multi-line strings."""
    while True:
        line = handle.readline()
        if line == "":
            return # Premature end of file, or just empty?
        if line[0] == ">":
            break

    no_id_warned = False
    while True:
        if line[0] != ">":
            raise ValueError(
                "Records in Fasta files should start with '>' character")
        try:
            id = line[1:].split(None, 1)[0]
        except IndexError:
            if not no_id_warned:
                sys.stderr.write("WARNING - Malformed FASTA entry with no identifier\n")
        no_id_warned = True
        id = None
        lines = [line]
        line = handle.readline()
        while True:
            if not line:
                break
            if line[0] == ">":
                break
            lines.append(line)
            line = handle.readline()
        yield "".join(lines)
        if not line:
            return # StopIteration

def fasta_filter(in_file, out_file, iterator_filter):
    count = 0
    #Galaxy now requires Python 2.5+ so can use with statements,
    with open(in_file) as in_handle:
        with open(out_file, "w") as pos_handle:
            for record in iterator_filter(raw_fasta_iterator(in_handle)):
                count += 1
                pos_handle.write(record)
    return count

try:
    from galaxy_utils.sequence.fastq import fastqReader, fastqWriter
    def fastq_filter(in_file, out_file, iterator_filter):
        count = 0
        #from galaxy_utils.sequence.fastq import fastqReader, fastqWriter
        reader = fastqReader(open(in_file, "rU"))
        writer = fastqWriter(open(out_file, "w"))
        for record in iterator_filter(reader):
            count += 1
            writer.write(record)
        writer.close()
        reader.close()
        return count
except ImportError:
    from Bio.SeqIO.QualityIO import FastqGeneralIterator
    def fastq_filter(in_file, out_file, iterator_filter):
        count = 0
        with open(in_file) as in_handle:
            with open(out_file, "w") as pos_handle:
                for title, seq, qual in iterator_filter(FastqGeneralIterator(in_handle)):
                    count += 1
                    pos_handle.write("@%s\n%s\n+\n%s\n" % (title, seq, qual))
        return count

def sff_filter(in_file, out_file, iterator_filter):
    count = 0
    try:
        from Bio.SeqIO.SffIO import SffIterator, SffWriter
    except ImportError:
        stop_err("SFF filtering requires Biopython 1.54 or later")
    try:
        from Bio.SeqIO.SffIO import ReadRocheXmlManifest
    except ImportError:
        #Prior to Biopython 1.56 this was a private function
        from Bio.SeqIO.SffIO import _sff_read_roche_index_xml as ReadRocheXmlManifest
    with open(in_file, "rb") as in_handle:
        try:
            manifest = ReadRocheXmlManifest(in_handle)
        except ValueError:
            manifest = None
        in_handle.seek(0)
        with open(out_file, "wb") as out_handle:
            writer = SffWriter(out_handle, xml=manifest)
            in_handle.seek(0) #start again after getting manifest
            count = writer.write_file(iterator_filter(SffIterator(in_handle)))
            #count = writer.write_file(SffIterator(in_handle))
    return count

if seq_format.lower()=="sff":
    count = sff_filter(in_file, out_file, sampler)
elif seq_format.lower()=="fasta":
    count = fasta_filter(in_file, out_file, sampler)
elif seq_format.lower().startswith("fastq"):
    count = fastq_filter(in_file, out_file, sampler)
else:
    stop_err("Unsupported file type %r" % seq_format)

sys.stderr.write("Sampled %i records\n" % count)


# # for windows laptop
rootPath = 'jiangnan2020_Simple\\'
trainImage = rootPath + 'train\\train\\'
testImage = rootPath + 'test\\test\\'
modelPath = 'model\\LR.pkl'
trainSize = 900
predictSize = 100

# # for linux server
# rootPath = 'jiangnan2020/'
# trainImage = rootPath + 'train/train/'
# testImage = rootPath + 'test/test/'
# modelPath = 'model/LR.pkl'
# trainSize = 15000
# predictSize = 5708

trainCSV = rootPath + 'train.csv'
testCSV = rootPath + 'test.csv'
submitCSV = rootPath + 'submit.csv'

imageW = 200
imageH = 200
imageSize = imageW * imageH


"""
Tests scikit-imputer converter.
"""
import unittest
import numpy as np
from sklearn.preprocessing import Imputer
from onnxmltools.convert.sklearn.ImputerConverter import ImputerConverter
from onnxmltools.convert.sklearn.convert import convert
from onnxmltools.convert.common.ConvertContext import ConvertContext
from onnxmltools.convert.common.model_util import make_tensor_value_info
from onnxmltools.proto import onnx_proto

class TestSklearnImputerConverter(unittest.TestCase):

    def test_model_imputer(self):
        model = Imputer(missing_values='NaN', strategy='mean', axis=0)
        model.fit([[1, 2], [np.nan, 3], [7, 6]])
        model_onnx = convert(model, 'scikit-learn imputer', [('features', 'int32', 2)])
        self.assertTrue(model_onnx is not None)

    def test_imputer_int_inputs(self):
        model = Imputer(missing_values='NaN', strategy='mean', axis=0)
        model.fit([[1, 2], [np.nan, 3], [7, 6]])

        context = ConvertContext()
        node = ImputerConverter.convert(context, model,
            [make_tensor_value_info('features', onnx_proto.TensorProto.INT32, [2])])
        self.assertTrue(node is not None)

        # should contain two nodes
        self.assertEqual(len(node), 2)
        # last node should contain the Imputer
        outputs = node[-1].outputs
        self.assertEqual(len(outputs), 1)
        self.assertEqual(outputs[0].type.tensor_type.shape.dim[-1].dim_value, 2)

    def test_imputer_float_inputs(self):
        model = Imputer(missing_values='NaN', strategy='mean', axis=0)
        model.fit([[1, 2], [np.nan, 3], [7, 6]])

        context = ConvertContext()
        node = ImputerConverter.convert(context, model,
            [make_tensor_value_info('features', onnx_proto.TensorProto.FLOAT, [2])])
        self.assertTrue(node is not None)

        # should contain two nodes
        self.assertEqual(len(node), 1)

        # last node should contain the Imputer
        outputs = node[-1].outputs
        self.assertEqual(len(outputs), 1)
        self.assertEqual(outputs[0].type.tensor_type.shape.dim[-1].dim_value, 2)


import random
PALABRAS = [
	"de",
	"la",
	"que",
	"el",
	"en",
	"yo",
	"ayer",
	"los",
	"se",
	"del",
	"las",
	"un",
	"por",
	"con",
	"no",
	"una",
	"su",
	"para",
	"es",
	"al",
	"lo",
	"como",
	"mas",
	"octavo",
	"pero",
	"sus",
	"le",
	"ha",
	"me",
	"si",
	"sin",
	"sobre",
	"este",
	"ya",
	"entre",
	"cuando",
	"todo",
	"esta",
	"ser",
	"son",
	"dos",
	"tambien",
	"fue",
	"habia",
	"era",
	"muy",
	"anos",
	"hasta",
	"desde",
	"esta",
	"mi",
	"porque",
	"que",
	"solo",
	"han",
	"yo",
	"hay",
	"vez",
	"puede",
	"todos",
	"asi",
	"nos",
	"ni",
	"parte",
	"tiene",
	"el",
	"uno",
	"donde",
	"bien",
	"tiempo",
	"mismo",
	"ese",
	"ahora",
	"cada",
	"e",
	"vida",
	"otro",
	"despues",
	"te",
	"otros",
	"aunque",
	"esa",
	"eso",
	"hace",
	"otra",
	"gobierno",
	"tan",
	"durante",
	"siempre",
	"dia",
	"tanto",
	"ella",
	"tres",
	"si",
	"dijo",
	"sido",
	"gran",
	"pais",
	"segun",
	"menos",
	"mundo",
	"ano",
	"antes",
	"estado",
	"contra",
	"sino",
	"forma",
	"caso",
	"nada",
	"hacer",
	"general",
	"estaba",
	"poco",
	"estos",
	"presidente",
	"mayor",
	"ante",
	"unos",
	"les",
	"algo",
	"hacia",
	"casa",
	"ellos",
	"ayer",
	"hecho",
	"primera",
	"mucho",
	"mientras",
	"ademas",
	"quien",
	"momento",
	"millones",
	"esto",
	"espana",
	"hombre",
	"estan",
	"pues",
	"hoy",
	"lugar",
	"madrid",
	"nacional",
	"trabajo",
	"otras",
	"mejor",
	"nuevo",
	"decir",
	"algunos",
	"entonces",
	"todas",
	"dias",
	"debe",
	"politica",
	"como",
	"casi",
	"toda",
	"tal",
	"luego",
	"pasado",
	"primer",
	"medio",
	"va",
	"estas",
	"sea",
	"tenia",
	"nunca",
	"poder",
	"aqui",
	"ver",
	"veces",
	"embargo",
	"partido",
	"personas",
	"grupo",
	"cuenta",
	"pueden",
	"tienen",
	"misma",
	"nueva",
	"cual",
	"fueron",
	"mujer",
	"frente",
	"jose",
	"tras",
	"cosas",
	"fin",
	"ciudad",
	"he",
	"social",
	"manera",
	"tener",
	"sistema",
	"sera",
	"historia",
	"muchos",
	"juan",
	"tipo",
	"cuatro",
	"dentro",
	"nuestro",
	"punto",
	"dice",
	"ello",
	"cualquier",
	"noche",
	"aun",
	"agua",
	"parece",
	"haber",
	"situacion",
	"fuera",
	"bajo",
	"grandes",
	"nuestra",
	"ejemplo",
	"acuerdo",
	"habian",
	"usted",
	"estados",
	"hizo",
	"nadie",
	"paises",
	"horas",
	"posible",
	"tarde",
	"ley",
	"importante",
	"guerra",
	"desarrollo",
	"proceso",
	"realidad",
	"sentido",
	"lado",
	"mi",
	"tu",
	"cambio",
	"alli",
	"mano",
	"eran",
	"estar",
	"san",
	"numero",
	"sociedad",
	"unas",
	"centro",
	"padre",
	"gente",
	"final",
	"relacion",
	"cuerpo",
	"obra",
	"incluso",
	"traves",
	"ultimo",
	"madre",
	"mis",
	"modo",
	"problemas",
	"cinco",
	"carlos",
	"hombres",
	"informacion",
	"ojos",
	"muerte",
	"nombre",
	"algunas",
	"publico",
	"mujeres",
	"siglo",
	"todavia",
	"meses",
	"manana",
	"esos",
	"nosotros",
	"hora",
	"muchas",
	"pueblo",
	"alguna",
	"dar",
	"problema",
	"don",
	"da",
	"tu",
	"derecho",
	"verdad",
	"maria",
	"unidos",
	"podria",
	"seria",
	"junto",
	"cabeza",
	"aquel",
	"luis",
	"cuanto",
	"tierra",
	"equipo",
	"segundo",
	"director",
	"dicho",
	"cierto",
	"casos",
	"manos",
	"nivel",
	"podia",
	"familia",
	"largo",
	"partir",
	"falta",
	"llegar",
	"propio",
	"ministro",
	"cosa",
	"primero",
	"seguridad",
	"hemos",
	"mal",
	"trata",
	"algun",
	"tuvo",
	"respecto",
	"semana",
	"varios",
	"real",
	"se",
	"voz",
	"paso",
	"senor",
	"mil",
	"quienes",
	"proyecto",
	"mercado",
	"mayoria",
	"luz",
	"claro",
	"iba",
	"este",
	"pesetas",
	"orden",
	"espanol",
	"buena",
	"quiere",
	"aquella",
	"programa",
	"palabras",
	"internacional",
	"van",
	"esas",
	"segunda",
	"empresa",
	"puesto",
	"ahi",
	"propia",
	"matrmonio",
	"libro",
	"igual",
	"politico",
	"persona",
	"ultimos",
	"ellas",
	"total",
	"creo",
	"tengo",
	"dios",
	"costilla",
	"espanola",
	"condiciones",
	"mexico",
	"fuerza",
	"solo",
	"unico",
	"accion",
	"amor",
	"policia",
	"puerta",
	"pesar",
	"zona",
	"sabe",
	"calle",
	"interior",
	"tampoco",
	"musica",
	"ningun",
	"vista",
	"campo",
	"buen",
	"hubiera",
	"saber",
	"obras",
	"razon",
	"extrano",
	"ninos",
	"presencia",
	"tema",
	"dinero",
	"comision",
	"antonio",
	"servicio",
	"hijo",
	"ultima",
	"ciento",
	"estoy",
	"hablar",
	"dio",
	"minutos",
	"produccion",
	"camino",
	"seis",
	"quien",
	"fondo",
	"direccion",
	"papel",
	"demas",
	"barcelona",
	"idea",
	"especial",
	"diferentes",
	"dado",
	"base",
	"capital",
	"ambos",
	"europa",
	"libertad",
	"relaciones",
	"espacio",
	"medios",
	"ir",
	"actual",
	"poblacion",
	"empresas",
	"estudio",
	"salud",
	"servicios",
	"haya",
	"principio",
	"siendo",
	"cultura",
	"anterior",
	"alto",
	"media",
	"mediante",
	"primeros",
	"arte",
	"paz",
	"sector",
	"imagen",
	"medida",
	"deben",
	"datos",
	"consejo",
	"personal",
	"interes",
	"julio",
	"grupos",
	"miembros",
	"ninguna",
	"existe",
	"cara",
	"edad",
	"enano",
	"movimiento",
	"visto",
	"llego",
	"puntos",
	"actividad",
	"bueno",
	"uso",
	"nino",
	"dificil",
	"joven",
	"futuro",
	"aquellos",
	"mes",
	"pronto",
	"soy",
	"hacia",
	"nuevos",
	"nuestros",
	"estaban",
	"posibilidad",
	"sigue",
	"cerca",
	"resultados",
	"educacion",
	"atencion",
	"gonzalez",
	"capacidad",
	"efecto",
	"necesario",
	"valor",
	"aire",
	"investigacion",
	"siguiente",
	"figura",
	"central",
	"comunidad",
	"necesidad",
	"serie",
	"organizacion",
	"nuevas",
	"calidad",
]
def palabra():
	p=random.choice(PALABRAS)
	return p


# Copyright 2018 VMware, Inc.
# All Rights Reserved
#
#    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.

import functools

from neutron_lib import exceptions as n_exc
from oslo_log import helpers as log_helpers
from oslo_log import log as logging
from oslo_utils import excutils

from vmware_nsx._i18n import _
from vmware_nsx.services.lbaas import base_mgr
from vmware_nsx.services.lbaas import lb_common
from vmware_nsx.services.lbaas import lb_const
from vmware_nsx.services.lbaas.nsx_v3.implementation import lb_utils
from vmware_nsxlib.v3 import exceptions as nsxlib_exc
from vmware_nsxlib.v3 import load_balancer as nsxlib_lb
from vmware_nsxlib.v3 import utils

LOG = logging.getLogger(__name__)

class EdgePoolManagerFromDict(base_mgr.NsxpLoadbalancerBaseManager):
    def _get_pool_kwargs(self, name=None, tags=None, algorithm=None,
                         description=None):
        kwargs = {
            'snat_translation': {'type': "LBSnatAutoMap"}}
        if name:
            kwargs['name'] = name
        if tags:
            kwargs['tags'] = tags
        if algorithm:
            kwargs['algorithm'] = algorithm
        if description:
            kwargs['description'] = description
        return kwargs

    def _get_pool_tags(self, context, pool):
        return lb_utils.get_tags(self.core_plugin, pool['id'],
                                 lb_const.LB_POOL_TYPE, pool['tenant_id'],
                                 context.project_name)

    def _remove_persistence(self, pool, vs_data):
        sp = pool.get('session_persistence')
        lb_client = self.core_plugin.nsxpolicy.load_balancer
        pp_client = None
        if not sp:
            LOG.debug("No session persistence info for pool %s", pool['id'])
        elif sp['type'] == lb_const.LB_SESSION_PERSISTENCE_HTTP_COOKIE:
            pp_client = lb_client.lb_cookie_persistence_profile
        elif sp['type'] == lb_const.LB_SESSION_PERSISTENCE_APP_COOKIE:
            pp_client = lb_client.lb_cookie_persistence_profile
        else:
            pp_client = lb_client.lb_source_ip_persistence_profile

        persistence_profile_id = vs_data.get('persistence_profile_id')
        if persistence_profile_id:
            pp_client.delete(persistence_profile_id)

    def _process_vs_update(self, context, pool, pool_id, listener, completor):
        vs_client = self.core_plugin.nsxpolicy.load_balancer.virtual_server
        try:
            # Process pool persistence profile and
            # create/update/delete profile for virtual server
            vs_data = vs_client.get(listener['id'])
            if pool and pool_id:
                (persistence_profile_id,
                 post_process_func) = self._setup_session_persistence(
                    pool, self._get_pool_tags(context, pool),
                    listener, vs_data)
            else:
                post_process_func = functools.partial(
                    self._remove_persistence, pool, vs_data)
                persistence_profile_id = None
        except nsxlib_exc.ManagerError:
            with excutils.save_and_reraise_exception():
                completor(success=False)
                LOG.error("Failed to configure session persistence "
                          "profile for pool %(pool_id)s",
                          {'pool_id': pool['id']})
        try:
            # Update persistence profile and pool on virtual server
            vs_client.update(
                listener['id'],
                pool_id=pool_id,
                lb_persistence_profile_id=persistence_profile_id)

            LOG.debug("Updated NSX virtual server %(vs_id)s with "
                      "pool %(pool_id)s and persistence profile %(prof)s",
                      {'vs_id': listener['id'], 'pool_id': pool['id'],
                       'prof': persistence_profile_id})
            if post_process_func:
                post_process_func()
        except nsxlib_exc.ManagerError:
            with excutils.save_and_reraise_exception():
                completor(success=False)
                LOG.error('Failed to attach pool %s to virtual '
                          'server %s', pool['id'], listener['id'])

    def _build_persistence_profile_tags(self, pool_tags, listener):
        tags = pool_tags[:]
        # With octavia loadbalancer name might not be among data passed
        # down to the driver
        lb_data = listener.get('loadbalancer')
        if lb_data:
            tags.append({
                'scope': 'os-lbaas-lb-name',
                'tag': lb_data['name'][:utils.MAX_TAG_LEN]})
        tags.append({
            'scope': 'os-lbaas-lb-id',
            'tag': listener['loadbalancer_id']})
        tags.append({
            'scope': 'os-lbaas-listener-id',
            'tag': listener['id']})
        return tags

    def _setup_session_persistence(self, pool, pool_tags, listener, vs_data):
        sp = pool.get('session_persistence')
        pers_type = None
        cookie_name = None
        cookie_mode = None
        lb_client = self.core_plugin.nsxpolicy.load_balancer
        pp_client = None
        if not sp:
            LOG.debug("No session persistence info for pool %s", pool['id'])
        elif sp['type'] == lb_const.LB_SESSION_PERSISTENCE_HTTP_COOKIE:
            pp_client = lb_client.lb_cookie_persistence_profile
            pers_type = nsxlib_lb.PersistenceProfileTypes.COOKIE
            cookie_name = sp.get('cookie_name')
            if not cookie_name:
                cookie_name = lb_const.SESSION_PERSISTENCE_DEFAULT_COOKIE_NAME
            cookie_mode = "INSERT"
        elif sp['type'] == lb_const.LB_SESSION_PERSISTENCE_APP_COOKIE:
            pp_client = lb_client.lb_cookie_persistence_profile
            pers_type = nsxlib_lb.PersistenceProfileTypes.COOKIE
            # In this case cookie name is mandatory
            cookie_name = sp['cookie_name']
            cookie_mode = "REWRITE"
        else:
            pp_client = lb_client.lb_source_ip_persistence_profile
            pers_type = nsxlib_lb.PersistenceProfileTypes.SOURCE_IP

        if pers_type:
            # There is a profile to create or update
            pp_kwargs = {
                'persistence_profile_id': pool['id'],
                'name': "persistence_%s" % utils.get_name_and_uuid(
                    pool['name'] or 'pool', pool['id'], maxlen=235),
                'tags': self._build_persistence_profile_tags(
                    pool_tags, listener)
            }
            if cookie_name:
                pp_kwargs['cookie_name'] = cookie_name
                pp_kwargs['cookie_mode'] = cookie_mode

        persistence_profile_id = vs_data.get('persistence_profile_id')
        if persistence_profile_id:
            # NOTE: removal of the persistence profile must be executed
            # after the virtual server has been updated
            if pers_type:
                # Update existing profile
                LOG.debug("Updating persistence profile %(profile_id)s for "
                          "listener %(listener_id)s with pool %(pool_id)s",
                          {'profile_id': persistence_profile_id,
                           'listener_id': listener['id'],
                           'pool_id': pool['id']})
                pp_client.update(persistence_profile_id, **pp_kwargs)
                return persistence_profile_id, None
            else:
                # Prepare removal of persistence profile
                return (None, functools.partial(self._remove_persistence,
                                                vs_data))
        elif pers_type:
            # Create persistence profile
            pp_id = pp_client.create_or_overwrite(**pp_kwargs)
            LOG.debug("Created persistence profile %(profile_id)s for "
                      "listener %(listener_id)s with pool %(pool_id)s",
                      {'profile_id': pp_id,
                       'listener_id': listener['id'],
                       'pool_id': pool['id']})
            return pp_id, None
        return None, None

    @log_helpers.log_method_call
    def create(self, context, pool, completor):
        pool_client = self.core_plugin.nsxpolicy.load_balancer.lb_pool

        pool_name = utils.get_name_and_uuid(pool['name'] or 'pool', pool['id'])
        tags = self._get_pool_tags(context, pool)

        description = pool.get('description')
        lb_algorithm = lb_const.LB_POOL_ALGORITHM_MAP.get(pool['lb_algorithm'])
        # NOTE(salv-orlando): Guard against accidental compat breakages
        try:
            listener = pool['listener'] or pool['listeners'][0]
        except IndexError:
            # If listeners is an empty list we hit this exception
            listener = None
        # Perform additional validation for session persistence before
        # creating resources in the backend
        lb_common.validate_session_persistence(pool, listener, completor)
        try:
            kwargs = self._get_pool_kwargs(
                pool_name, tags, lb_algorithm, description)
            pool_client.create_or_overwrite(lb_pool_id=pool['id'], **kwargs)
        except nsxlib_exc.ManagerError:
            completor(success=False)
            msg = (_('Failed to create pool on NSX backend: %(pool)s') %
                   {'pool': pool['id']})
            raise n_exc.BadRequest(resource='lbaas-pool', msg=msg)

        # The pool object can be created with either --listener or
        # --loadbalancer option. If listener is present, the virtual server
        # will be updated with the pool. Otherwise, just return. The binding
        # will be added later when the pool is associated with layer7 rule.
        # FIXME(salv-orlando): This two-step process can leave a zombie pool on
        # NSX if the VS update operation fails
        if listener:
            self._process_vs_update(context, pool, pool['id'], listener,
                                    completor)
        completor(success=True)

    @log_helpers.log_method_call
    def update(self, context, old_pool, new_pool, completor):
        pool_client = self.core_plugin.nsxpolicy.load_balancer.lb_pool

        pool_name = None
        tags = None
        lb_algorithm = None
        description = None
        if new_pool['name'] != old_pool['name']:
            pool_name = utils.get_name_and_uuid(new_pool['name'] or 'pool',
                                                new_pool['id'])
            tags = self._get_pool_tags(context, new_pool)
        if new_pool['lb_algorithm'] != old_pool['lb_algorithm']:
            lb_algorithm = lb_const.LB_POOL_ALGORITHM_MAP.get(
                new_pool['lb_algorithm'])
        if new_pool.get('description') != old_pool.get('description'):
            description = new_pool['description']
        # NOTE(salv-orlando): Guard against accidental compat breakages
        try:
            listener = new_pool['listener'] or new_pool['listeners'][0]
        except IndexError:
            # If listeners is an empty list we hit this exception
            listener = None
            # Perform additional validation for session persistence before
            # operating on resources in the backend
        lb_common.validate_session_persistence(new_pool, listener, completor,
                                               old_pool=old_pool)

        try:
            kwargs = self._get_pool_kwargs(pool_name, tags, lb_algorithm,
                                           description)
            pool_client.update(lb_pool_id=new_pool['id'], **kwargs)
            if (listener and new_pool['session_persistence'] !=
                old_pool['session_persistence']):
                self._process_vs_update(context, new_pool, new_pool['id'],
                                        listener, completor)
            completor(success=True)
        except Exception as e:
            with excutils.save_and_reraise_exception():
                completor(success=False)
                LOG.error('Failed to update pool %(pool)s with '
                          'error %(error)s',
                          {'pool': old_pool['id'], 'error': e})

    @log_helpers.log_method_call
    def delete(self, context, pool, completor):
        pool_client = self.core_plugin.nsxpolicy.load_balancer.lb_pool

        # NOTE(salv-orlando): Guard against accidental compat breakages
        try:
            listener = pool['listener'] or pool['listeners'][0]
        except IndexError:
            # If listeners is an empty list we hit this exception
            listener = None
        if listener:
            try:
                self._process_vs_update(
                    context, pool, None, listener, completor)
            except Exception as e:
                LOG.error('Disassociation of listener %(lsn)s from pool '
                          '%(pool)s failed with error %(err)s',
                          {'lsn': listener['id'],
                           'pool': pool['id'],
                           'err': e})
        try:
            pool_client.delete(pool['id'])
        except nsxlib_exc.ResourceNotFound:
            pass
        except nsxlib_exc.ManagerError:
            completor(success=False)
            msg = (_('Failed to delete lb pool from nsx: %(pool)s') %
                   {'pool': pool['id']})
            raise n_exc.BadRequest(resource='lbaas-pool', msg=msg)

        completor(success=True)

    @log_helpers.log_method_call
    def delete_cascade(self, context, pool, completor):
        self.delete(context, pool, completor)


#!/usr/bin/env python2.7
from __future__ import print_function, division, absolute_import, unicode_literals
from matplotlib import pyplot as plt
from urdf_optcontrol import optimizer
import rospy
from std_msgs.msg import Float64
import numpy as np
from casadi import sin, cos, fmax
from std_srvs.srv import Empty
import time
import pathlib
from jump import homing, jumping, rehoming

def publish_inputs(u, publishers):
    for ankle, knee, hip in zip(u['ankle'], u['knee'], u['hip']):
        publishers[0].publish(float(ankle))
        publishers[1].publish(float(knee))
        publishers[2].publish(float(hip))
        rospy.Rate(u['rate']).sleep()

def reset_simulation(publishers, restart_function):
    for i in range(3):
        publishers[0].publish(0)
        publishers[1].publish(0)
        publishers[2].publish(0)
        time.sleep(0.1)
    restart_function()
    time.sleep(0.2)

ros = True
recalc_homing = False
recalc_jump = False
recalc_rehoming = False

filepath = pathlib.Path(__file__).parent

if ros:
    # INITIALIZING NODE AND PUBLISHERS
    rospy.init_node("softleg_control")
    pub_ankle = rospy.Publisher('/oneleg/ankle/theta_command', Float64, queue_size=10)
    pub_knee = rospy.Publisher('/oneleg/knee/theta_command', Float64, queue_size=10)
    pub_hip = rospy.Publisher('/oneleg/hip/theta_command', Float64, queue_size=10)
    publishers = [pub_ankle, pub_knee, pub_hip]

# INITIALIZING GAZEBO SERVICES TO STOP AND UNSTOP
pause_gazebo=rospy.ServiceProxy('/gazebo/pause_physics',Empty)
unpause_gazebo=rospy.ServiceProxy('/gazebo/unpause_physics',Empty)
restart_gazebo=rospy.ServiceProxy('/gazebo/reset_simulation',Empty)

# ROBOT SPECIFICATIONS
robot = {
    'urdf_path': pathlib.Path(__file__).absolute().parent.parent.parent.joinpath('urdf','softleg-light.urdf'),
    'root': 'foot',
    'end': 'body',
    'sea_damping': dict(leg_J1=0.05, leg_J2=0.05, leg_J3=0.05)
}

# POSITION AND VELOCITY OF THE CENTER OF THE MASS OF THE ROBOT
CoM = {
    'pos_x': lambda q: -0.1209*sin(q[0])-0.0488*sin(q[0]+q[1])-0.0291*sin(q[0]+q[1]+q[2]),
    'pos_y': lambda q: 0.1209*cos(q[0])+0.0488*cos(q[0]+q[1])+0.0291*cos(q[0]+q[1]+q[2]),
    'vel_x': lambda q,qd: -0.1209*qd[0]*cos(q[0])-0.0488*(qd[0]+qd[1])*cos(q[0]+q[1])-0.0291*(qd[0]+qd[1]+qd[2])*cos(q[0]+q[1]+q[2]),
    'vel_y': lambda q,qd: -0.1209*qd[0]*sin(q[0])-0.0488*(qd[0]+qd[1])*sin(q[0]+q[1])-0.0291*(qd[0]+qd[1]+qd[2])*sin(q[0]+q[1]+q[2]),
    'acc_x': lambda q,qd,qdd: 0.0488*(qd[0]+qd[1])**2*sin(q[0]+q[1]) -0.0488*(qdd[0]+qdd[1])*cos(q[0]+q[1]) +0.0291*(qd[0]+qd[1]+qd[2])**2*sin(q[0]+q[1]+q[2]) -0.0291*(qdd[0]+qdd[1]+qdd[2])*cos(q[0]+q[1]+q[2]) +0.1209*sin(q[0])*(qd[0])**2 -0.1209*cos(q[0])*qdd[0],
    'acc_y': lambda q,qd,qdd: -0.0488*(qd[0]+qd[1])**2*cos(q[0]+q[1]) -0.0488*(qdd[0]+qdd[1])*sin(q[0]+q[1]) -0.0291*(qd[0]+qd[1]+qd[2])**2*cos(q[0]+q[1]+q[2]) -0.0291*(qdd[0]+qdd[1]+qdd[2])*sin(q[0]+q[1]+q[2]) -0.1209*sin(q[0])*qdd[0] -0.1209*cos(q[0])*(qd[0])**2
}

def p_zero_mom(q,qd,qdd,m=3.2,g=9.81):
    F = m*(g+CoM['acc_y'](q,qd,qdd))
    return CoM['pos_x'](q)-(m*CoM['acc_y'](q,qd,qdd)*CoM['pos_y'](q))/F
CoM['p_zero_mom'] = p_zero_mom

# INITIAL POSE AND VELOCITY
squat_position = [0.35,-2.1745,2.0245]

# HOMING PROCEDURE
u_home = homing(final_cond=squat_position,
                recalc=recalc_homing,
                center_of_mass=CoM,
                robot=robot
)
# JUMP PROCEDURE
u_jump = jumping(   initial_cond=squat_position,
                    recalc=recalc_jump,
                    center_of_mass=CoM,
                    robot=robot
)

qf = [u_jump['q'][0][-1], u_jump['q'][1][-1], u_jump['q'][2][-1]]
qdf = [u_jump['qd'][0][-1], u_jump['qd'][1][-1], u_jump['qd'][2][-1]]

# RE-HOMING PROCEDURE
initial_cond = [float(x) for x in qf+qdf]

u_rehome = rehoming(initial_cond=initial_cond,
                    final_cond=squat_position,
                    recalc=recalc_rehoming,
                    center_of_mass=CoM,
                    robot=robot
)

if ros:
    reset_simulation(publishers, restart_gazebo)
    input('Press Enter to start')
    publish_inputs(u_home, publishers)
    time.sleep(1.0)
    for i in range(100):

        publish_inputs(u_jump, publishers)
        # pause_gazebo()
        time.sleep(0.1)

        publish_inputs(u_rehome, publishers)
        time.sleep(1.0)

plt.show()


import datetime
import io
import time
from fractions import Fraction

import gpiozero
import picamera
from PIL import Image, ImageStat

def get_average_brightness(image):
    image = image.copy().convert("L")
    return ImageStat.Stat(image).mean[0]

def capture(shutter_speed, iso=800, exposure_mode="off"):
    stream = io.BytesIO()
    camera = picamera.PiCamera(resolution=(1024, 768), framerate=Fraction(1, 6), sensor_mode=3)
    camera.shutter_speed = shutter_speed
    camera.iso = iso
    camera.exposure_mode = exposure_mode

    camera.start_preview()
    time.sleep(1)
    camera.capture(stream, format="png")
    stream.seek(0)
    camera.close()

    return stream

filename = datetime.datetime.now().strftime(r"still_%Y%m%d_%H%M%S.png")

shutter_speed = 500_000
max_shutter_speed = 6_000_000
min_shutter_speed = 1_000

wanted_brightness = 128

led17 = gpiozero.LED(17)
led17.on()
time.sleep(1)

best_image = None
best_brightness = None
best_brightness_delta = 256

print(datetime.datetime.now().isoformat(timespec="seconds"), "START")

for x in range(5):
    stream = capture(shutter_speed)
    image = Image.open(stream)
    brightness = get_average_brightness(image)
    brightness_delta = abs(wanted_brightness-brightness)
    factor = wanted_brightness/brightness
    new_shutter_speed = int(shutter_speed*factor)

    print(
        datetime.datetime.now().isoformat(timespec="seconds"),
        f"seq={x}",
        f"ss=[{shutter_speed:,} => {new_shutter_speed:,}]",
        f"brightness=[{brightness:,.2f}*{factor:,.2f} ~= {wanted_brightness}]"
    )

    if brightness_delta < best_brightness_delta:
        best_image = image
        best_brightness = brightness
        best_brightness_delta = brightness_delta

    if shutter_speed == max_shutter_speed:
        print(datetime.datetime.now().isoformat(timespec="seconds"), "BREAK max shutter speed")
        break
    elif shutter_speed == min_shutter_speed:
        print(datetime.datetime.now().isoformat(timespec="seconds"), "BREAK min shutter speed")
        break
    else:
        shutter_speed = max(min(new_shutter_speed, max_shutter_speed), min_shutter_speed)

print(datetime.datetime.now().isoformat(timespec="seconds"), f"Best brightness: {best_brightness:,.2f}", f"Saving {filename}")
best_image.save(filename)

led17.off()

print(datetime.datetime.now().isoformat(timespec="seconds"), "STOP")


#!/usr/bin/env python

from pip.req import parse_requirements
from setuptools import find_packages, setup

install_reqs = parse_requirements('requirements.txt', session=False)
reqs = [str(ir.req) for ir in install_reqs]

setup(
    name='asyncexec',
    version='0.3.0',
    packages=find_packages(),
    include_package_data=True,
    install_requires=reqs,
)