-- Copyright (c) 2004 Scott Owens

module GetWeather (getWeather, processForecast, processSummary, getWeatherTests) where

import System.Cmd
import IO
import Text.Regex
import Data.List
import Time
import HUnit
import SimpleHTTP

type Forecast = (String, [(String, String, (Integer, Integer), Integer)])
type Summary = (String, Double, (Integer, Integer), String)
type Weather a = (CalendarTime, Either a String)

-- makes a function that matches reStr against its input
reMatch :: String -> String -> Maybe (String, String, String, [String])
reMatch reStr = matchRegexAll (mkRegex reStr)


-- select does a regexp match for searchFor in searchIn and applies pick to the match
select :: String -> ((String, String, String, [String]) -> a)
                 -> Maybe String 
                 -> Maybe a
select _ _ Nothing = Nothing
select searchFor pick (Just searchIn) =
  case reMatch searchFor searchIn of
    Nothing -> Nothing
    Just r -> Just (pick r)


-- returns the text before searchFor
trunc :: String -> Maybe String -> Maybe String
trunc searchFor = select searchFor (\(a, _, _, _) -> a)

truncTests =
  TestLabel "truncTest" (TestList [
    trunc "2" Nothing ~?= Nothing,
    trunc "2" (Just "") ~?= Nothing,
    trunc "2" (Just "44444") ~?= Nothing,
    trunc "2" (Just "4444255555") ~?= Just "4444"])


-- returns the text after searchFor
skipTo :: String -> Maybe String -> Maybe String
skipTo searchFor = select searchFor (\(_, _, a, _) -> a)

skipToTests =
  TestLabel "skipToTest" (TestList [
    skipTo "2" Nothing ~?= Nothing,
    skipTo "2" (Just "") ~?= Nothing,
    skipTo "2" (Just "44444") ~?= Nothing,
    skipTo "2" (Just "4444255555") ~?= Just "55555"])


-- returns the lines of searchIn from the first line that includes searchFor
skipToLines :: String -> [String] -> [String]
skipToLines searchFor searchIn =
 dropWhile (\s -> (Nothing == (matchRegex (mkRegex searchFor) s))) searchIn

skipToLinesTests =
  TestLabel "skipToLinesTest" (TestList [
    skipToLines "2" ["13", "552", "444"] ~?= ["552", "444"],
    skipToLines "2" [] ~?= []])


-- splits str into a list of the substrings that match re
splitString :: Regex -> String -> [String]
splitString re str =
  case matchRegexAll re str of
    Nothing -> []
    Just (_, match, after, _) -> match : (splitString re after)
    
splitStringTests = 
  TestLabel "splitString" (TestList [
    splitString (mkRegex "2") "14249222" ~?= ["2", "2", "2", "2"],
    splitString (mkRegex "2") "" ~?= []])


-- finds the first part of w that is surrounded by <pre> and </pre>
findData :: Maybe String -> Maybe String
findData w = 
  trunc "</pre>" (skipTo "<pre>" w)

findDataTests =
 TestLabel "findData" (TestList [
    findData (Just "123<pre>45<pre</pre26</pre>789<pre>333</pre>") ~?= (Just "45<pre</pre26"),
    findData (Just "<pre>asdf3</pre") ~?= Nothing,
    findData (Just "") ~?= Nothing,
    findData Nothing ~?= Nothing])


-- splits "m/n" into numbers m and n
processTemp :: String -> (Integer, Integer)
processTemp s =
 let x = splitString (mkRegex "[0-9-]+") s
 in
   (read (x !! 0), read (x !! 1))

processTempTests =
 TestLabel "processTemp" (TestList [
    processTemp "-40/100" ~?= (-40, 100),
    processTemp "-234/2356" ~?= (-234, 2356),
    processTemp "3/2" ~?= (3, 2)])


-- gets the number from "POP n"
processPop :: String -> Integer
processPop s =
  read ((splitString (mkRegex "[0-9]+") s) !! 0)

processPopTests =
 TestLabel "processPop" (TestList [
    processPop "POP 0" ~?= 0,
    processPop "POP 100" ~?= 100,
    processPop "POP 50" ~?= 50])


-- extracts the forecase from weather
processForecast :: String -> Either Forecast String
processForecast weather = 
  case findData (Just weather) of
    Nothing -> Right weather
    Just w -> let wlines = lines w
                  timestamp = (skipToLines "SALT LAKE CITY" wlines) !! 1
                  header = splitString (mkRegex "[A-Z]+ [0-9]+")
                                       ((skipToLines "FCST" wlines) !! 2)
                  wdata = (map (splitString (mkRegex "[^ ]+|POP [0-9]+"))
                               (take 3 (tail (skipToLines "^[ ]*SALT LAKE CITY$" wlines))))
              in
                case wdata of
                  [a, b, c] -> Left (timestamp,
                                     (zip4 header a (map processTemp b)
                                                    (map processPop c)))
                  _ -> Right weather
processForecastTests = 
  TestLabel "processForecast"
            (TestCase (do s <- readFile "testForecast.html"
                          assertEqual "" (processForecast s)
                                      (Left (" 620 PM MST TUE JAN 27 2004",
                                             [("JAN 28", "MOCLDY", (26, 38), 30),
                                              ("JAN 29", "MOCLDY", (26, 39), 30),
                                              ("JAN 30", "MOCLDY", (24, 43), 30),
                                              ("JAN 31", "MOCLDY", (23, 34), 10),
                                              ("FEB 01", "PTCLDY", (15, 30), 10),
                                              ("FEB 02", "PTCLDY", (22, 32), 0),
                                              ("FEB 03", "MOCLDY", (22, 36), 10)]))))

-- extracts the daily summary
processSummary :: String -> Either Summary String
processSummary weather =
  case findData (Just weather) of
    Nothing -> Right weather
    Just w -> let wlines = lines w
                  timestamp = (skipToLines "SALT LAKE CITY" wlines) !! 1
                  cloudsLine = (skipToLines "AVERAGE SKY COVER" wlines) !! 0
                  maxTempLine = (skipToLines "MAXIMUM" wlines) !! 0
                  minTempLine = (skipToLines "MINIMUM" wlines) !! 0
                  rainLine = (skipToLines "TODAY" (skipToLines "PRECIPITATION" wlines)) !! 0
              in
                Left (timestamp, 
                      read (last (words cloudsLine)),
                      (read (words minTempLine !! 1),
                       read (words maxTempLine !! 1)),
                      words rainLine !! 1)
processSummaryTests = 
  TestLabel "processSummary"
            (TestCase (do s <- readFile "testSummary.html"
                          assertEqual "" (processSummary s)
                                      (Left (" 518 PM MST TUE JAN 27 2004", 0.7, (18, 37), "0.00"))))
                      

-- gets the weather
getWeather :: (Show a) => (String -> a) -> String -> String -> IO ()
getWeather process file url =
  do
    weather <- getPage url
    time <- getClockTime
    appendFile file (show (toUTCTime time, process weather))
    
    
    
getWeatherTests = TestList [processPopTests, processTempTests, findDataTests,
                            splitStringTests, skipToLinesTests, skipToTests,
                            truncTests, processForecastTests, processSummaryTests]
