module Accuracy 
 where

import ListUtils
import WeatherData

--Data
data Accuracy = Accurate TempAccuracy PercipAccuracy OutlookAccuracy

data TempAccuracy = TempAcc WithinRange WithinRange
data WithinRange = Exact | Within6 Int | Within10 Int | Within20 Int | Off Int

--PercipAcc rainedWhenPredicted howMuchToAmountPredict
data PercipAccuracy = PercipAcc Bool AmountPredict
--If amont A was predicted, did it happen
data AmountPredict = Low Bool | Moderate Bool | High Bool | Average Bool

data OutlookAccuracy = OutlookAcc Bool

--Printers for data
instance Show Accuracy where
 show (Accurate t p o) = formatShow ["Accurate",(show t),(show p),(show o)]
instance Show TempAccuracy where
 show (TempAcc min max) = formatShow ["TempAcc",(show min),(show max)]
instance Show WithinRange where
 show Exact = "Exact"
 show (Within6 n) = formatShow ["Within6",(show n)]
 show (Within10 n) = formatShow ["Within10",(show n)]
 show (Within20 n) = formatShow ["Within20",(show n)]
 show (Off n) = formatShow ["Off",(show n)]
instance Show PercipAccuracy where
 show (PercipAcc p a) = formatShow ["PercipAcc",(show p),(show a)]
instance Show AmountPredict where
 show (Low b) = formatShow ["Low",(show b)]
 show (Moderate b) = formatShow ["Moderate",(show b)]
 show (High b) = formatShow ["High",(show b)]
 show (Average b) = formatShow ["Average",(show b)]
instance Show OutlookAccuracy where
 show (OutlookAcc b) = formatShow ["OutlookAcc",(show b)]

--Functions
--determine the accuracy of the forcasts for daysAhead days before the
--conditions, create an average of these accuracys
forcastAccuracy :: Int -> [Actual] -> [Prediction] -> ([Accuracy],Accuracy)
forcastAccuracy daysAhead conditions forcasts =
 let forcastCondition = getNthDayForcastAndCondition daysAhead forcasts conditions
     dailyAccuracy = map determineAccuracy forcastCondition in
  (dailyAccuracy,(generalizeAccuracy dailyAccuracy)) 
--Tests are from .storedConditions and .storedAccuracy

--To average accs into one summation of their accuracy
generalizeAccuracy :: [Accuracy] -> Accuracy
generalizeAccuracy accs =
 Accurate (generalizeTemps (map (\(Accurate t p o) -> t) accs))
          (generalizePercip (map (\(Accurate t p o) -> p) accs))
          (generalizeOutlook (map (\(Accurate t p o) -> o) accs))
--tests
{-generalizeAccuracy([(Accurate (TempAcc (Within10 5) (Within10 4)) 
                                (PercipAcc False (Low True))
                                (OutlookAcc True)),
                      (Accurate (TempAcc (Within20 9) (Within6 3))
                                (PercipAcc True (Low True))
                                (OutlookAcc False)),
                      (Accurate (TempAcc (Within10 -4) (Within6 1))
                                (PercipAcc True (Low True))
                                (OutlookAcc True))]) ->
 Accuracte(TempAcc (Within20 6) (Within6 2)) 
          (PercipAcc True (Average True))
          (OutlookAcc True)
-}

--To create an average temperature accuracy from a list of tempAcc
generalizeTemps :: [TempAccuracy] -> TempAccuracy
generalizeTemps [] = TempAcc Exact Exact
generalizeTemps n = TempAcc (determineRange (average (map (\(TempAcc m x) -> (toInt m)) n)))
                            (determineRange (average (map (\(TempAcc m x) -> (toInt x)) n)))
--Tests
--generalizeTemps [] => TempAcc Exact Exact
--generalizeTemps [(TempAcc Exact (Within6 1)),(TempAcc (Within20 (-10)) (Within10 (-5)))] =>
-- TempAcc (Within10 -5) (Within6 -2)

--To create an average percipitation accuracy from a list of PercipAcc
generalizePercip :: [PercipAccuracy] -> PercipAccuracy
generalizePercip [] = PercipAcc True (Low True) 
generalizePercip p = PercipAcc (whichBoolMost (map (\(PercipAcc g s) -> g) p))
                               (Average 
                                (whichBoolMost (map (\(PercipAcc g s) -> (toBool s)) p)))
--generalizePercip [] => PercipAcc True (Low True)
--generalizePercip [(PercipAcc True (Low True)),(PercipAcc True (Low True)),
--                    (PercipAcc False (Moderate False))] => PercipAcc True Average True

--To create an average outlook correct assessment from a list of outlook
generalizeOutlook :: [OutlookAccuracy] -> OutlookAccuracy
generalizeOutlook [] = OutlookAcc True
generalizeOutlook p = OutlookAcc (whichBoolMost (map (\(OutlookAcc b) -> b) p))
--generalizeOutlook [] => OutlookAcc True
--generalizeOutlook [(OutlookAcc True), (OutlookAcc False),(OutlookAcc True)] => 
--OutlookAcc True

determineAccuracy :: (Prediction,Actual) -> Accuracy
determineAccuracy ((Predict _ _ o r c),(Actual _ t p s w)) =
 Accurate (determineTemp t r) (determinePercip p c) (determineOutlook o s w p t)

--To determine if the outlook was correct
determineOutlook :: Outlook -> SkyCover -> Wind -> Percip -> Temp -> OutlookAccuracy
determineOutlook (Cloudy a) (SkyCover n) _ _ _ =
 case a of
  Full -> OutlookAcc (n > 0.8)
  Mostly -> OutlookAcc (n > 0.5)
  Partly -> OutlookAcc (n > 0.2)
determineOutlook Snow  _ _ p (Temp avg _ _) =
  outlookHelp (\avg -> (avg <= 32)) p avg
determineOutlook Rain _ _ p (Temp avg _ _) =
  outlookHelp (\avg -> (avg > 32)) p avg
determineOutlook Sunny (SkyCover n) _ p _ =
  let rainOk = case p of
                (Amount 0.0) -> True
                Trace -> True
                NoData -> True
                n -> False in
   OutlookAcc ((n < 0.4) && rainOk)
--Tests
--determineOutlook (Cloudy Full) (SkyCover 0.7) (Wind 0.0) Trace (Temp 1 2 3) =>
-- OutlookAcc False
--determineOutlook Snow (SkyCover 0.7) (Wind 0.0) Trace (Temp 25 20 30) => Outlook True
--determineOutlook Rain (SkyCover 0.7) (Wind 0.0) Trace (Temp 35 30 40) => Outlook True
--determineOutlook Sunny (SkyCover 0.39) (Wind 0.0) Trace (Temp 25 20 30) => Outlook True

outlookHelp :: (Int -> Bool) -> Percip -> Int -> OutlookAccuracy
outlookHelp comp p avg =
 case p of
   (Amount 0.0) -> OutlookAcc False
   n -> OutlookAcc (comp avg)


--To determine if the percipitation was well predicted
determinePercip :: Percip -> CoP -> PercipAccuracy
determinePercip p (Chance n) = PercipAcc (anyRain p n) (enoughRain p n)
--Tests 
--determinePercip Trace (Chance 20) => PercipAcc True (Low True)
--determinePercip (Amount 0.0) (Chance 30) => PercipAcc True (Low True)
--determinePercip (Amount 0.0) (Chance 50) => PercipAcc False (Moderate False)

--To determine if any rain occured if some was predicted
anyRain :: Percip -> Int -> Bool
anyRain NoData c = True
anyRain Trace c = (c /= 0)
anyRain (Amount rec) c = 
 case c of
  0 -> rec == 0
  n | n <= 10 -> rec < 2
  n | n <= 30 -> True
  n -> rec > 0
{-Tests
anyRain NoData 10 => True
anyRain Trace 0 => False
anyRain Trace 1 => True
anyRain (Amount 0) 0 => True
anyRain (Amount 0) 1 => False
anyRain (Amount 1.9) 10 => True
anyRain (Amount 2) 10 => False
anyRain (Amount 0) 30 => True
anyRain (Amount 0.0) 40 => False
anyRain (Amount 3) 40 => True -}

--To see if enough rain as expected fell
enoughRain :: Percip -> Int -> AmountPredict
enoughRain NoData c = 
 let help = (\x y -> True) in
 enoughRainHelp help help help c 0
enoughRain Trace c =
 let help = (\x y -> False) in
 enoughRainHelp (\x y->True) help help c 0
enoughRain (Amount rec) c =
 enoughRainHelp (\c rec-> (rec < 0.2))
                (\c rec-> (((c < 40) && (rec < 0.2)) || 
                          ((rec > 0) && (rec < 0.5))))
                (\c rec-> (rec > 0.1)) c rec
--Tests
--enoughRain NoData 1 => Low True
--enoughRain Trace 1 => Low True
--enoughRain (Amount 0.2) 40 => Moderate False

enoughRainHelp :: (Int->Float->Bool)->(Int->Float->Bool)->(Int->Float->Bool)->Int->Float->AmountPredict
enoughRainHelp forLow forMod forHigh c rec =
 case c of
  n | n <=30 -> Low (forLow c rec)
  n | n <=70 -> Moderate (forMod c rec)
  n -> High (forHigh c rec)

--To determine the accuracty of the temp with the forcast
determineTemp :: Temp -> Range -> TempAccuracy
determineTemp (Temp avg min max) (Range pmin pmax) =
 TempAcc (determineRange (min - pmin)) (determineRange (max - pmax))
--Tests
--determineTemp (Temp 25 20 30) (Range 20 29) => TempAcc Exact (Within6 1)
--determineTemp (Temp 25 20 30) (Range 30 35) => TempAcc (Within20 -10) (Within10 -5)

--To determine the range of difference in the temperatures
determineRange :: Int -> WithinRange
determineRange 0 = Exact
determineRange n | (comp 3 n) = Within6 n
determineRange n | (comp 5 n) = Within10 n
determineRange n | (comp 10 n) = Within20 n
determineRange n = Off n
--Tests
--determineRange 0 => Exact
--determineRange (-3) => Within6 -3
--determineRange 5 => Within10 5
--determineRange (-9) => Within20 -9
--determineRange 11 => Off 11

--To test whether n is between c and -c inclusive
comp :: Int -> Int -> Bool
comp c n = (n <= c) && (n >= (- c))
-- comp 2 1 => True                             

toInt :: WithinRange -> Int
toInt Exact = 0
toInt (Within6 n) = n
toInt (Within10 n) = n
toInt (Within20 n) = n
toInt (Off n) = n

--averages a list of integers
average :: [Int] -> Int
average [] = 0
average n = quot (foldr (\x y -> ((abs x) + y)) 0 n) (length n)
--tests
--average [1,(-2),3] => 2
--average [0,0,0] => 0
--average [] => 0

toBool :: AmountPredict -> Bool
toBool (Low b) = b
toBool (Moderate b) = b
toBool (High b) = b
toBool (Average b) = b

--Determines whether true or false occur more often, and returns the one that does
whichBoolMost :: [Bool] -> Bool
whichBoolMost b =
 let numTrue = (length (filter id b))
     numFalse = ((length b) - numTrue) in
  (numTrue >= numFalse)
--tests
--whichBoolMost [] -> True
--whichBoolMost [True,True,False] -> True
--whichBoolMost [False,False,True] -> False
