implementation module MetarParser
import StdEnv, Parsers
from StdIO import :: Maybe (..)

wordify :: ![a] -> [[a]]	| space a	
wordify cs = splitUp (dropWhile space cs)
where	splitUp :: ![a] -> [[a]]	| space a	
		splitUp [] = []
		splitUp cs
			# (first,rest)	= span (not o space) cs
			= [first:wordify rest]

//===========================

testMetar :: [[Char]] -> Result MetarData
testMetar myText = parse metar myText "METAR" "word"

:: SurfaceWind = {direction :: WindDirection, speed :: Int, gust :: Gust}

:: WindDirection = StraightDir Int | BoundedDir Int Int | VariableWindDirection

:: Gust :== Maybe Int

surfaceWind :: Parser [Char] SurfaceWind t
surfaceWind = "Surface Wind" :> p
where	p	= (word constantWind) <&> \surfaceWind -> <!?> (word directionVariation)
							(\dir2->{surfaceWind & direction=dir2})	// if found
							surfaceWind								// if nothing found

constantWind :: Parser Char SurfaceWind a
constantWind =	direction	<&> \dir	 ->
				speed		<&> \(u,spd) ->
				gust u		<@  \g		 -> {direction=dir,speed=spd,gust=g}
				
directionVariation :: Parser Char WindDirection t
directionVariation = "Wind Direction Variation (such as 120V150)" :> p 
where p = BoundedDir @> degrees <++> variableDir

direction :: Parser Char WindDirection t
direction = "Wind Direction" :> p
where	p	= ("Variable Designator (VRB)" :> token ['VRB'] <@ const VariableWindDirection)
				<!> degrees <&> \firstDir -> <!?> variableDir
							(\s->BoundedDir firstDir s)	// if found
							(StraightDir firstDir)		// if nothing found

degrees :: Parser Char Int a
degrees = "Degrees Rounded to Multiple of 10 (three digits)" :> p
where	p = twoDigits <& symbol '0' <@ \tenDeg -> norm (tenDeg * 10) 360

variableDir :: Parser Char Int a
variableDir = "Symbol V" :> symbol 'V' &> degrees

gust :: SpeedUnit -> Parser Char Gust t
gust u = "Gust" :> <!?> ("Symbol G" :> symbol 'G' &> twoDigits)
						(\sp->Just (snd (convSpeedJ sp u)))	// if found
						Nothing								// if not found
		
speed :: Parser Char (SpeedUnit,Int) t
speed = "Wind Speed (two digits)" :> twoDigits
		<&> \sp -> (<!?> speedUnit (convSpeedJ sp) (convSpeedN sp))
		
metersPerNauticalMile	:== 1852
metersPerStatuteMile	:== 1609
metersPerKilometer		:== 1000

convSpeedJ :: Int SpeedUnit -> (SpeedUnit,Int)
convSpeedJ sp u
	# (u,i)	= case u of
		Knots	-> (Knots,	sp)
		KmH		-> (KmH,	sp *: (metersPerKilometer,metersPerNauticalMile))
		StMH	-> (StMH,	sp *: (metersPerStatuteMile,metersPerNauticalMile))
	= (u, norm i 100)

(*:) infix :: Int (Int,Int) -> Int
(*:) var (num,denom) = (var*num + denom/2) / denom

convSpeedN :: Int -> (SpeedUnit,Int)
convSpeedN sp = (Knots,	norm sp 100)

speedUnit :: Parser Char SpeedUnit t
speedUnit = "Wind Speed Unit" :> p
where	p = 	("Knots, i.e. Nautical Miles per Hour (KT)"	:> token ['KT']		<@ const Knots)
			<!> ("Kilometers per Hour (KMH)"				:> token ['KMH']	<@ const KmH)
			<!>	("Statute Miles per Hour (MPH)"				:> token ['MPH']	<@ const StMH)
			
:: SpeedUnit = Knots | KmH | StMH

twoDigits :: Parser Char Int a
twoDigits = (\d1 d2 -> d1*10 + d2) @> digit` <++> digit`

digit` :: Parser Char Int t
digit` = digit <@ digitToInt

//==============================

:: VisibilityDir = {range :: Int, compass :: Int}
:: Visibility = StraightVis Int | MinVis VisibilityDir | MinMax VisibilityDir VisibilityDir

visibility :: Parser Char Visibility t
visibility = "Horizontal Visibility" :> p
where	p =	fourDigits "Visibility in Meters" <&> \r1 -> (<!?> minMaxVis (conv r1) (StraightVis r1))
		conv r1 (c1,Nothing)	  = MinVis {range=r1,compass=c1}
		conv r1 (c1,Just (r2,c2)) = MinMax {range=r1,compass=c1} {range=r2,compass=c2}
		
minMaxVis :: Parser Char (Int,Maybe (Int,Int)) t
minMaxVis = compass <&&> (<!?> maxVis Just Nothing)

maxVis :: Parser Char (Int,Int) t
maxVis = "Maximum Visibility" :> (fourDigits "Visibility in Meters" <&&> compass)

fourDigits :: String -> Parser Char Int t
fourDigits str = (str+++" (four digits)") :> ((\i1 i2 -> i1*100 + i2) @> twoDigits <++> twoDigits)

compass :: Parser Char Int t
compass = "Compass Heading for min/max Windspeed" :> p
where	p =		"North West (NW)"	:>	(token ['NW']	<@ const 45)
			<!> "North East (NE)"	:>	(token ['NE']	<@ const 315)	
			<!> "South West (SW)"	:>	(token ['SW']	<@ const 135)	
			<!> "South East (SE)"	:>	(token ['SE']	<@ const 225)	
			<!> "North (N)"			:>	(symbol 'N'		<@ const 0)	
			<!> "West (W)"			:>	(symbol 'W'		<@ const 90)
			<!> "South (S)"			:>	(symbol 'S'		<@ const 180)
			<!> "East (E)"			:>	(symbol 'E'		<@ const 270)
			
// runway visibility

:: RunwayVisGroup :== [RunwayVisibility]
:: RunwayVisibility = {id :: Int, range :: Int, tendency :: Maybe Tendency}
:: Tendency = Increasing | Decreasing | NoChange

runwayVisibility :: Parser Char RunwayVisibility t
runwayVisibility =	"Runway Visibility" :> p
where	p =	(\rw rg t -> {id=rw,range=rg,tendency=t}) @>
			runwayID <++> fourDigits "visibility in meters" <++> (<!?> tendency Just Nothing)

tendency :: Parser Char Tendency t
tendency = "Change in Runway Visibility" :> p
where	p =		"Increasing (U)"	:>	(symbol 'U'	<@ const Increasing)
			<!> "Decreasing (D)"	:>	(symbol 'D'	<@ const Decreasing)
			<!>	"No Change (N)"		:>	(symbol 'N' <@ const NoChange)
			
runwayID :: Parser Char Int t
runwayID = "Runway ID (R followed by two digits and /)" :> symbol 'R' &> twoDigits <& symbol '/'

//presentWeather

:: PresentWeather :== [WeatherGroup] 
:: WeatherGroup = {intensity :: Intensity, descriptor :: Maybe Descriptor,
					phenomenon :: WeatherPhenomenon}
:: Intensity = Light | Moderate | Heavy | Vicinity

:: Descriptor		= Shallow | Patches | Partial | Drifting | Blowing | Showers | Thunderstorm
					| SuperCooled
:: WeatherPhenomenon= Precip [Precipitation] | OO ObscurationOther
:: Precipitation	= Drizzle | Rain | Snow | SnowGrains | IceCrystals | IcePellets | Hail
					| SmallHailOrSnowPellets
:: ObscurationOther	= Mist | Fog | Smoke | VolcanicAsh | WidespreadDust | Sand | Haze
					| WellDevDustSandWhirls | Squalls | FunnelClouds | SandStorm | DustStorm

weatherGroup :: Parser Char WeatherGroup t
weatherGroup	= (\intens (d,ph) -> {intensity=intens,descriptor=d,phenomenon=ph}) @>
				  (<!?> intensity id Moderate) <++> descrPhenom
where	descrPhenom = ((<!?> descriptor Just Nothing) <&&> phenomenon)		<!>
					  "Showers (SH)" :> token ['SH'] <@ const (Just Showers,Precip [Rain])

intensity :: Parser Char Intensity t
intensity	= "Weather Intensity/Proximity" :> p
where	p =		"Light (-)" 	:> (symbol '-'		<@ const Light)		
			<!>	"Heavy (+)"		:> (symbol '+'		<@ const Heavy)		
			<!>	"Vicinity (VC)" :> (token ['VC']	<@ const Vicinity)
			
descriptor :: Parser Char Descriptor t
descriptor	= "Weather Phenomenon Descriptor" :> p
where	p =		"Shallow (MI)"		:> (token ['MI']	<@ const Shallow)
			<!>	"Patches (BC)"		:> (token ['BC']	<@ const Patches)
			<!>	"Partial (PR)"		:> (token ['PR']	<@ const Partial)
			<!>	"Drifting (DR)"		:> (token ['DR']	<@ const Drifting)
			<!>	"Blowing (BL)"		:> (token ['BL']	<@ const Blowing)
			<!>	"Showers (SH)"		:> (token ['SH']	<@ const Showers)
			<!>	"Thunderstorm (TS)"	:> (token ['TS']	<@ const Thunderstorm)
			<!>	"Super Cooled (FZ)"	:> (token ['FZ']	<@ const SuperCooled)
			
phenomenon :: Parser Char WeatherPhenomenon t
phenomenon =	<!+> precipitation		<@ Precip
				<!> obscurationOther	<@ OO
				
precipitation :: Parser Char Precipitation t
precipitation = "Precipitation" :> p
where	p =		"Drizzle (DZ)"		:>	(token ['DZ']	<@ const Drizzle)
			<!>	"Rain (RA)"			:>	(token ['RA']	<@ const Rain)
			<!>	"Snow (SN)"			:>	(token ['SN']	<@ const Snow)
			<!>	"Snow grains (SG)"	:>	(token ['SG']	<@ const SnowGrains)
			<!>	"Ice Crystals (IC)"	:>	(token ['IC']	<@ const IceCrystals)
			<!>	"Ice Pellets (PL)"	:>	(token ['PL']	<@ const IcePellets)
			<!>	"Hail(GR)"			:>	(token ['GR']	<@ const Hail)
			<!>	"Small Hail or Snow Pellets (GS)"
									:>	(token ['GS']	<@ const SmallHailOrSnowPellets)

obscurationOther :: Parser Char ObscurationOther t
obscurationOther	= "Obscuration etc." :> p
where	p =		"Mist (BR)"			:>	(token ['BR']	<@ const Mist)
			<!> "Fog (FG)"			:>	(token ['FG']	<@ const Fog)
			<!> "Smoke (FU)"		:>	(token ['FU']	<@ const Smoke)
			<!> "Volcanic Ash (VA)"	:>	(token ['VA']	<@ const VolcanicAsh)
			<!> "Widespread Dust (DU)"
									:>	(token ['DU']	<@ const WidespreadDust)
			<!> "Sand (SA)"			:>	(token ['SA']	<@ const Sand)
			<!> "Haze (HZ)"			:>	(token ['HZ']	<@ const Haze)
			<!> "Well Developped Dust or Sand Whirls (PO)"
									:>	(token ['PO']	<@ const WellDevDustSandWhirls)
			<!> "Squalls (SQ)"		:>	(token ['SQ']	<@ const Squalls)
			<!> "Funnel Clouds (FC)":>	(token ['FC']	<@ const FunnelClouds)
			<!> "Sand Storm (SS)"	:>	(token ['SS']	<@ const SandStorm)
			<!> "Dust Storm (DS)"	:>	(token ['DS']	<@ const DustStorm)
					
// clouds and vertical visibility

:: CloudsVertVisibility = L [CloudsVV] | SkyClear

:: CloudsVV = {amount :: CloudAmount,visibility :: Int}

:: CloudAmount = Few | Scattered | Broken | Overcast

cloudsVV :: Parser Char CloudsVV t
cloudsVV = "Clouds" :> p
where	p = (\ca vv -> {amount=ca,visibility=vv}) @> cloudAmount <++> verticalVisibility

skyClear :: Parser Char CloudsVertVisibility t
skyClear = "Sky Clear (SKC)" :> token ['SKC'] <@ const SkyClear

cloudAmount :: Parser Char CloudAmount t
cloudAmount = "Cloud Amount" :> p
where	p =		"Few (FEW)"			:>	(token ['FEW']	<@ const Few)
			<!>	"Scattered (SCT)"	:>	(token ['SCT']	<@ const Scattered)
			<!>	"Broken (BKN)"		:>	(token ['BKN']	<@ const Broken)
			<!>	"Overcast (OVC)"	:>	(token ['OVC']	<@ const Overcast)
				
verticalVisibility :: Parser Char Int t
verticalVisibility = "Vertical Visibility in Hundreds of Feet (three digits)" :> p
where	p = (\kFeet hFeet -> (1000*kFeet+100*hFeet)) @> twoDigits <++> digit`

// temperature and dew point

:: TempAndDewPoint = {temperature::Int,dewPoint::Int}

temperature :: String -> Parser Char Int t
temperature str = str :> p
where	p = (<!?> ("Minus Sign (M)" :> symbol 'M') (\min temp -> ~temp) id) <++>
			"Centigrade (two digits)" :> twoDigits
		
tempAndDewPoint :: Parser [Char] TempAndDewPoint t
tempAndDewPoint	= "Temperature and Dew Point" :> (word p)
where	p = (\t d -> {temperature=t,dewPoint=d}) @>
			(temperature "Temperature" <& ("Slash" :> symbol '/')) <++> (temperature "Dew Point")		

// pressure

pressure :: Parser [Char] Int t
pressure = "pressure" :> (word p)
where	p = ("Symbol Q" :> symbol 'Q') &> fourDigits "Pressure in mm Mercury"

// visibility, weather and cloud group

:: VisibilityWeatherCloud	= CloudsVisibilityOK
							| VWCGroup Visibility RunwayVisGroup PresentWeather CloudsVertVisibility

word p	:== drill p "position"

/*	The following might be wrong: when you type in a horizontal visibility, that is enough. You are not
	prompted to type in SKC (sky clear). It just seems to be assumed. */

visibilityWeatherCloud :: Parser [Char] VisibilityWeatherCloud t
visibilityWeatherCloud = p
where	p = word cloudsVisibilityOK <!>
			VWCGroup @>	
			 word visibility <++> <!*> (word runwayVisibility)	<++> <!*> (word weatherGroup) <++>
			 (word skyClear <!> <!*>  (word cloudsVV) <@ L)

cloudsVisibilityOK :: Parser Char VisibilityWeatherCloud t
cloudsVisibilityOK = "Clouds and Visibility OK (CAVOK)" :> token ['CAVOK']	<@ const CloudsVisibilityOK
							 
// metar data

:: MetarData = {	surfaceWind				:: SurfaceWind,
					visibilityWeatherCloud	:: VisibilityWeatherCloud,
					tempAndDewPoint			:: TempAndDewPoint,
					pressure				:: Int}
					
metarData :: Parser [Char] MetarData t
metarData	=	(\sw vwc td p -> {surfaceWind = sw, visibilityWeatherCloud	= vwc,
					 			  tempAndDewPoint = td, pressure = p})				@>
				surfaceWind <++> visibilityWeatherCloud	<++> tempAndDewPoint <++> pressure				

// metar prefix

metarPrefix :: Parser [Char] Char t
metarPrefix = "Date and Time Prefix (four or six digits followed by Z)" :> (word p)
where	p =  twoDigits &> twoDigits &> (<!?> twoDigits undef undef) &> (symbol 'Z')

// metar

metar :: Parser [Char] MetarData t
metar = (<!?> metarPrefix undef undef) &> metarData

// reporting

class report a :: a -> String

instance report MetarData
where	report {surfaceWind,visibilityWeatherCloud,tempAndDewPoint,pressure} =
			report surfaceWind +++ " " +++
			report visibilityWeatherCloud +++ " " +++
			report tempAndDewPoint +++ " " +++
			toDigits pressure 4

instance report WindDirection
where	report VariableWindDirection = "VRB VRB"
		report d = sixDigits (decode d)
		where	decode :: WindDirection -> (Int,Int)
				decode (StraightDir i)	= (i,i)
				decode (BoundedDir a b)	= (a,b)
				sixDigits (i,j) = toDigits i 3 +++ " " +++ toDigits j 3

toDigits :: !Int !Int -> String
toDigits x n = toString (p 10 n + abs x) % (1,n)
where	p :: !Int !Int -> Int
		p cum n
			| n < 2		= cum
			| otherwise	= p (cum*10) (n-1)

instance report SurfaceWind
where	report {direction,speed} = report direction +++ " " +++ toDigits speed 2

instance report Visibility
where	report vis
			# i = case vis of
					StraightVis i					-> i
					MinVis {VisibilityDir|range}	-> range
					MinMax {VisibilityDir|range} _	-> range
			= toDigits i 4
		
instance report TempAndDewPoint
where	report {temperature}
			# n= norm (abs temperature) 100
			= sign temperature +++ toDigits n 2
		where	sign t	| t < 0 = "-"
						| t > 0 = "+"
						= " "  

instance report VisibilityWeatherCloud
where	report CloudsVisibilityOK					= "9999 >05000"
		report (VWCGroup visibility _ _ cloudsVV)	= report visibility +++ " " +++ report cloudsVV
		
instance report CloudsVertVisibility
where	report SkyClear	=  ">05000"
		report (L cs) = case getCeilings cs of
			[]	-> ">05000"
			cs	# visibility = minList cs
				-> " " +++ toDigits visibility 5

getCeilings [{amount,visibility}:rest] = case amount of
	Broken	-> [visibility:getCeilings rest]
	Overcast-> [visibility:getCeilings rest]
	_		-> getCeilings rest
getCeilings [] = []

// utilities

norm :: Int Int -> Int
norm a bound
	# t = a / bound
	| a >= 0	= a - t*bound
				= a - (t-1)*bound