module PredictWeather (printAccuracy,
		       printAccuracyWithin,
		       predictWeatherTests
		       )
    where

import IO
import List
import System.Directory
import HUnit
import WeatherCommon

wpath = "/Users/dsg/weather"
epsilon = 3

-- prints the accuracy of forecasts d days out using the default epsilon
printAccuracy :: Int -> IO ()
printAccuracy d = printAccuracyWithin d epsilon

-- prints the accuracy of forecasts d days out where accurracy is determined by predicted temperatures being within ep of actual temperatures
printAccuracyWithin :: Int -> Int -> IO ()
printAccuracyWithin d ep =
    do
    predActs <- readAccuracyInfo d
    printPredActsAcc predActs d ep

-- readAccuracyInfo
readAccuracyInfo :: Int -> IO [(Prediction, Actual)]
readAccuracyInfo daysOut  = 
    do dirConts <- getDirectoryContents wpath
       let actualFiles = filter (\s -> ('a' == (last s))) dirConts
	   actualDates = (map readDate (map init actualFiles))
	   actualPredicts = filter (\actualDate -> 
				    (elem ((show actualDate) ++ "p") dirConts))
			           actualDates
			    in do
			       predActs <- sequence (map (uncurry makePredAct) (zip actualPredicts (replicate (length actualPredicts) daysOut)))
			       return (map (\(Just x) -> x) (filter ((/=) Nothing) predActs))
					       

printPredActsAcc :: [(Prediction, Actual)] -> Int -> Int -> IO ()
printPredActsAcc pa daysOut ep =
    do 
    putStrLn ("Out of " ++ (show (length pa)) ++ " data points")
    putStrLn ("Forecasts " ++ (show daysOut) ++ " days out are " ++ (show ((averageAccuracy pa ep) * 100.0)) ++ " percent accurate")
       
-- opens the weather info file for the given date and returns the actual weather
getAct :: MyDate -> IO Actual
getAct date = do
	      bracket (openFile (dateToFileName date 'a') ReadMode)
		      hClose
		      getActFromFile

-- reads in actual weather from a file
getActFromFile :: Handle -> IO Actual
getActFromFile h = do
		   l <- hGetLine h
		   return ( read $ head $ words l,
			    read ((!!) (words l) 1),
			    ((!!) (words l) 2) )

-- looks up weather for a given date and the forecast daysPrev days before that date
makePredAct :: MyDate -> Int -> IO (Maybe (Prediction, Actual))
makePredAct date daysPrev =
    do exists <- findPred date daysPrev
       case exists of 
		   Nothing -> return Nothing
		   Just pred -> do 
				act <- getAct date
				return (Just (pred, act))
			       

-- dateToFileName - converts a date and a type ('a' or 'p') to the appropriate filename
dateToFileName :: MyDate -> Char -> String
dateToFileName d t = 
    (wpath ++ "/" ++ (show d) ++ [t])

dateToFileNameTests =
    TestLabel "dateToFileName"
	      (TestList [dateToFileName (MyDate(JAN, 1)) 'a' ~?= (wpath ++ "/JAN01a"),
			 dateToFileName (MyDate(JUN, 30)) 'p' ~?= (wpath ++ "/JUN30p")])

-- finds the weather forecast X days previous to that date
findPred :: MyDate -> Int -> IO (Maybe Prediction)
findPred date daysPrev = 
    bracket (openFile (dateToFileName date 'p') ReadWriteMode)
	    hClose
	    (\h -> dateExists h (repeatFun prevDate date daysPrev))
	     
-- returns a prediction for the given date if it exists		     
dateExists :: Handle -> MyDate -> IO (Maybe Prediction)
dateExists h date = 
    do end <- hIsEOF h
       if end
	  then return Nothing
	  else do
	       curLine <- hGetLine h
	       if date == (readDate (head (words curLine)))
		  then let wCurLine = words curLine
				      in return (Just ( wCurLine !! 1, wCurLine !! 2, wCurLine !! 3))
		  else (dateExists h date)

-- calls a function num times - first on orig, then on the result of the function call	      
repeatFun :: (a -> a) -> a -> Int -> a
repeatFun _ orig 0 = orig
repeatFun fun orig num = repeatFun fun (fun orig) (num - 1)

repeatFunTests =
    TestLabel "repeatFunTest"
	      (TestList [repeatFun ((+) 1) 0 0 ~?= 0,
			 repeatFun ((+) 1) 0 3 ~?= 3])

-- gets the accuracy of the weather predicted d days out with epsilon
averageAccuracy :: [(Prediction, Actual)] -> Int -> Float
averageAccuracy predsActs ep =
    (foldr (\x y -> ((if x then 1.0 else 0.0) + y))
            0.0
            (map (uncurry weatherAccurate)
	         (zip predsActs (replicate (length predsActs) ep))))
    /
    (realToFrac (length predsActs))

averageAccuracyTests =
    TestLabel "averageAccuracyTest"
		  (TestList [(averageAccuracy [(("24", "32", "dummy"),
						(30, 24, "4.0")),
					       (("26", "31", "dummy"),
						(35, 29, "5.0"))]
			      2) ~?= 0.5,
			     (averageAccuracy [(("24", "32", "dummy"),
						(40, 20, "4.0")),
					       (("26", "31", "dummy"),
						(35, 29, "5.0"))]
			      2) ~?= 0.0,
			     (averageAccuracy [(("24", "32", "dummy"),
						(30, 24, "4.0")),
					       (("26", "31", "dummy"),
						(31, 27, "5.0"))]
			      2) ~?= 1.0])
		  

-- determines whether the highs and lows of a predicted and actual weather are within epsilon
weatherAccurate :: (Prediction, Actual) -> Int -> Bool
weatherAccurate ((pl, ph, _), (ah, al, _)) ep =
    (accurate (read ph) ah ep) &&
    (if (pl == "NA") then True else (accurate (read pl) al ep))

weatherAccurateTests =
    TestLabel "weatherAccurateTest"
	      (TestList [weatherAccurate (("24", "32", "dummy"), (34, 22, "dummy"))
			 2 ~?= True,
			 weatherAccurate (("24", "32", "dummy"), (35, 22, "dummy"))
			 2 ~?= False,
			 weatherAccurate (("24", "32", "dummy"), (34, 21, "dummy"))
			 2 ~?= False,
			 weatherAccurate (("NA", "32", "dummy"), (34, 22, "dummy"))
			 2 ~?= True,
			 weatherAccurate (("NA", "32", "dummy"), (38, 22, "dummy"))
			 2 ~?= False])
	       
-- returns whether p is within e of a
accurate :: Int -> Int -> Int -> Bool
accurate pred act ep = (abs (pred - act) <= ep)

accurateTests =
    TestLabel "accurateTest"
	      (TestList [(accurate 32 34 2) ~?= True,
			 (accurate 32 33 2) ~?= True,
			 (accurate 34 32 2) ~?= True,
			 (accurate 1 5 2) ~?= False,
			 (accurate 5 1 2) ~?= False])

predictWeatherTests = TestList [dateToFileNameTests, repeatFunTests, averageAccuracyTests, weatherAccurateTests, accurateTests]
