module BusTracker (Query(..), Result(..), getBusTimes) where

import Network.Curl
import qualified Network.Curl.Code as CC
import Text.HTML.TagSoup
import Data.Char (isDigit, isLetter)
import System.Time
import System.Time.Parse (parseCalendarTime)
import System.Locale
import Data.List (partition, break)
import Control.Arrow (first)

data Query = Q { queryBusStop   :: String
               , queryBusTime   :: Maybe String
               , queryBusNumber :: Maybe String } deriving Show

ppQuery q = queryBusStop q ++ time ++ service
    where time = maybe [] ("?" ++) (queryBusTime q)
          service = maybe [] ("!" ++) (queryBusNumber q)

data Result a = R { resultBusNumber  :: String
                 , resultDestination :: String
                 , resultDisabled    :: Bool
                 , resultArrivalTime :: a
                 , resultEstimated   :: Bool } deriving (Show, Eq)

instance Ord a => Ord (Result a) where
    r1 <= r2 = (resultArrivalTime r1) <= (resultArrivalTime r2)
instance Functor Result where
    fmap f r = r { resultArrivalTime = f (resultArrivalTime r)}

emptyResult = R "" "" False "DUE" False

getBusTimes qry = do
    (statcode, body) <- curlGetString (stopcode_url ++ ppQuery qry) []
    time <- (getClockTime >>= toCalendarTime)
    let result = if statcode == CC.CurlOK
                    then extractTimetable $ parseTags body
                    else []
    return $ if null result then Nothing else Just $ convertStringToTimeDiff time result

convertStringToTimeDiff :: CalendarTime -> [Result String] -> [Result TimeDiff]
convertStringToTimeDiff cal rs = map (fmap (normalizeTimeDiff . strToTimeDiff cal)) rs

extractTimetable = map (parseLine emptyResult) . getTableRows . getTable
  where getTable = takeWhile (~/= "</div>") . dropWhile (~/= "<div id=\"displayDepartures\">")
        getTableRows = map (takeWhile (~/= "</pre>")) . partitions (~== "<pre>")

parseLine r ts = r { resultBusNumber = head txt
                   , resultArrivalTime = time
                   , resultDisabled = any (~== "<span class=\"handicap\">") ts
                   , resultDestination = unwords $ tail $ init txt
                   , resultEstimated = est
                   }
    where txt = words $ innerText ts
          (est, time) = first (not . null) $ partition (=='*') $ last txt

calendarToDiff newcal curcal = if diff < noTimeDiff then diff { tdDay = 1 } else diff
    where diff = toClockTime newcal `diffClockTimes` toClockTime curcal

strToTimeDiff _ "DUE" = noTimeDiff
strToTimeDiff t str = case splitOn ':' str of
                            [mins]      -> noTimeDiff {tdMin = read mins}
                            [hrs,mins]  -> calendarToDiff t { ctHour = read hrs, ctMin = read mins} t

splitOn v ls = case break (==v) ls of
                ([], l:ls') -> splitOn v ls'
                (ls', []) -> ls':[]
                (ls',l:ls'') -> ls' : splitOn v ls''

-- Queryable URLs to append with data in comments
stopcode_url = "http://mybustracker.co.uk/display.php?clientType=b&busStopCodeQuick=" -- 8 digit code
postcode_url = "http://mybustracker.co.uk/map.html?codePostal=" -- post code (no spaces)
busnumber_url = "http://mybustracker.co.uk/map.html?serviceMnemo=" -- bus number
