module GetWeather (main, getWeatherTests)
    where

import IO
import Network
import List
import HUnit
import WeatherCommon

wpath = "/Users/dsg/weather"
predpage = "/Saltlake/forecast/SFT.shtml"
actualpage = "/Saltlake/climate/STP.shtml"

main :: IO ()
main = do
       processPredictions
       processActual

-- grabs predictions and writes them out to disk
processPredictions :: IO ()
processPredictions = 
    do
    pageWords <- getWeatherPageWords predpage
    let forecast = stripExtraneous pageWords 
	foreDate = getDate pageWords "UT" 4
	predictions = getPredictions forecast
	predictionDates = getDates forecast
	in sequence_ (map writePredictions 
		      (zip3
		       (map (\x -> x ++ "p") (map show predictionDates))
		       (replicate (length predictions) foreDate)
		       predictions))

-- gets the words from a particular page on the weather site
getWeatherPageWords :: String -> IO [String]
getWeatherPageWords page = do 
			   contents <- getWebPage "www.wrh.noaa.gov" page 80
			   return (words contents)

processActual :: IO ()
processActual = do
		pageWords <- getWeatherPageWords actualpage
		let aDate = getDate pageWords "DATE:" 1
		    aWeather = getActual pageWords
			       in writeActual aDate aWeather

-- writes the actual weather for a day out to disk
writeActual :: MyDate -> Actual -> IO ()
writeActual date (low, high, prec) =
    bracket (openFile (wpath ++ "/" ++ (show date) ++ "a") WriteMode)
	    hClose
	    (\h -> hPutStrLn h ((show low) ++ " " ++ (show high) ++ " " ++ prec))

-- writes the predictions out to disk in filenames of the appropriate date
writePredictions :: (String, MyDate, Prediction) -> IO ()
writePredictions (filename, dateTaken, (low, high, prec)) = 
    do
    bracket (openFile (wpath ++ "/" ++ filename) ReadWriteMode)
	    hClose
	    (\h -> do
	     exists <- dateExists h dateTaken
	     case exists of 
	       Nothing   -> return ()
	       Just newH -> hPutStrLn newH ((show dateTaken) ++ " " ++  low ++ " " ++  high ++ " " ++  prec) )
 
-- checks if the current date already exists in the file
dateExists :: Handle -> MyDate -> IO (Maybe Handle)
dateExists h date = 
    do end <- hIsEOF h
       if end
	  then return (Just h)
	  else do
	       curLine <- hGetLine h
	       if date == (readDate (head (words curLine)))
		  then return Nothing
		  else (dateExists h date)

		    
-- gets the date of the forecast which appears d words after the word provided
getDate :: [String] -> String -> Int -> MyDate
getDate page word d =
    let beginDate = drop (d + 1) (dropWhile ((/=) word) page)
	in readDate ((head beginDate) ++ (head (tail beginDate)))

getDateTests = 
    TestLabel "getDateTest"
	      (TestList [getDate ["My", "Dear", "Aunt", "Sally", "JAN", "03"]
			         "Sally" 0 ~?= MyDate(JAN, 3),
			 getDate ["THU", "2330", "MST", "BLAH", "JUN", "23"]
			         "THU" 3 ~?= MyDate(JUN, 23)])

-- strips unneeded data from weather forecast
stripExtraneous :: [String] -> [String]    
stripExtraneous original =
    takeWhile ((/=) "OGDEN")
		  (drop 14 
		   (dropWhile ((/=) "FCST")
		    original))

stripExtraneousTests =
    TestLabel "stripExtraneousTest"
	      (TestList [stripExtraneous ["Q", "V", "FCST", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "A", "B", "C", "D", "E", "OGDEN", "Z", "Y", "X", "W"] ~?= ["14", "A", "B", "C", "D", "E"]])

-- gets the dates from the forecast
getDates :: [String] -> [MyDate]
getDates forecast =
    map readDate (unpair $ take 14 forecast)

getDatesTests =
    TestLabel "getDatesTest"
	      (TestList [getDates ["JAN", "01", "JAN", "02", "JAN", "03", "JAN", "04", "JAN", "05", "JAN", "06", "JAN", "07", "Extra", "Stuff", "Here"] ~?= [MyDate(JAN, 1), MyDate(JAN, 2), MyDate(JAN, 3), MyDate(JAN, 4), MyDate(JAN, 5), MyDate(JAN, 6), MyDate(JAN, 7)]])

-- takes a list with an even number of elements and combines each pair of first two 
unpair :: [String] -> [String]
unpair [] = []
unpair (f:s:rest) = (f ++ s):(unpair rest)

unpairTests =
    TestLabel "unpairTest"
	      (TestList [unpair [] ~?= [],
			 unpair ["a", "b", "c", "d"] ~?= ["ab", "cd"]])

-- gets the actual weather
getActual :: [String] -> Actual
getActual weather =
    let beginWeather = (drop 3 (tail $ dropWhile ((/=) ":*SALT") weather))
		       in ( (read (head beginWeather)),
			    (read (head (drop 2 beginWeather))),
			    (head (drop 4 beginWeather)) )

getActualTests =
    TestLabel "getActualTest"
	      (TestList [getActual [":*SALT", "LAKE", "CITY", ":", "35", "21", "0.00"] ~?= (35, 21, "0.00")])

-- gets the predictions from the forecast
getPredictions :: [String] -> [Prediction]
getPredictions forecast =
    zipPredictions (drop 7 (tail $ dropWhile ((/=) "CITY") forecast))


-- combines all predictions together
zipPredictions :: [String] -> [Prediction]
zipPredictions preds =
    let highLows = (map splitOnSlash (take 7 preds))
	precs = (filter ((/=) "POP") (take 14 (drop 7 preds)))
		in map (\((a, b), c) -> (a, b, c)) (zip highLows precs)

zipPredictionsTests =
    TestLabel "zipPredictionsTest"
	      (TestList [zipPredictions [] ~?= [],
			 zipPredictions ["/37", "28/40", "24/43", "23/34", "15/30", "22/32", "22/36",
					 "POP 30", "POP 20", "POP 30", "POP 10", "POP 0", "POP 0", "POP 20"] ~?=
			 [("NA", "37", "30"), ("28", "40", "20"), ("24", "43", "30"),
			  ("23", "34", "10"), ("15", "30", "0"), ("22", "32", "0"),
			  ("22", "36", "20")]])

pairListToTripleList :: [(a, b)] -> [c] -> [(a, b, c)]
pairListToTripleList []            _       = []
pairListToTripleList ((fe, se):rf) (fs:rs) = (fe, se, fs):(pairListToTripleList rf rs)

pairListToTripleListTests =
    TestLabel "pairListToTrileListTest"
	      (TestList [pairListToTripleList [] [] ~?= ([]::[(String, String, String)]),
			 pairListToTripleList [(1, 2)] [3] ~?= [(1, 2, 3)],
			 pairListToTripleList [(1, 2), (3, 4), (5, 6)] [3, 5, 7] ~?= [(1, 2, 3), (3, 4, 5), (5, 6, 7)]])
				    
-- splits a string with a slash in it into two strings, before and after the (first) slash - if the part before the first slash is empty, it becomes "NA"
splitOnSlash :: String -> (String, String)
splitOnSlash stringToSplit =
    let before = (takeWhile ((/=) '/') stringToSplit) in
		 ( if ("" == before) then "NA" else before, 
		   (tail $ dropWhile ((/=) '/') stringToSplit) )

splitOnSlashTests =
    TestLabel "splitOnSlashTest"
	      (TestList [--splitOnSlash "" ~?= ("NA", exception),
			 --splitOnSlash "foo" --> ("foo", exception)
			 splitOnSlash "/a" ~?= ("NA", "a"),
			 splitOnSlash "a/b"~?= ("a", "b"),
			 splitOnSlash "a/b/c" ~?= ("a", "b/c")])

-- connect to web page and get its contents
getWebPage :: String -> String -> PortNumber -> IO String
getWebPage site page port = 
    withSocketsDo $ do
		       socketHandle <- connectTo site (PortNumber port)
		       hSetBuffering socketHandle LineBuffering
--		       putStr "/Saltlake/forecast/SFT.shtml HTTP/1.1\r\n\r\n"
		       hPutStr socketHandle ("GET http:" ++ site ++ page ++ "HTTP/1.1\n\n")
--		       return ()
		       results <- hGetContents  socketHandle
		       return results

getWeatherTests = TestList [getDateTests, stripExtraneousTests, getDatesTests, unpairTests, getActualTests, zipPredictionsTests, pairListToTripleListTests, splitOnSlashTests]
