{-# LANGUAGE OverloadedStrings #-}
module Data where

import Data.Time
import Text.ParserCombinators.ReadPrec (readP_to_Prec)
import Text.ParserCombinators.ReadP hiding (get)
import qualified Text.ParserCombinators.ReadP as ReadP
import Text.Read (readPrec)
import Data.Binary
import Data.Binary.Put
import Data.Binary.Get
import Data.Binary.StringRef
import qualified Data.MyText as T
import Data.MyText (Text)
import Control.Applicative
import Control.Monad
import Control.DeepSeq

type TimeLog a = [TimeLogEntry a]

data TimeLogEntry a = TimeLogEntry
        { tlTime :: UTCTime
        , tlRate :: Integer -- ^ in milli-seconds
        , tlData :: a }
  deriving (Show, Read)

instance Functor TimeLogEntry where
        fmap f tl = tl { tlData = f (tlData tl) }

instance NFData a => NFData (TimeLogEntry a) where
    rnf (TimeLogEntry a b c) = a `deepseq` b `deepseq` c `deepseq` ()

data CaptureData = CaptureData
        { cWindows :: [ (Bool, Text, Text) ]
                -- ^ Active window, window title, programm name
        , cLastActivity :: Integer -- ^ in milli-seconds
        , cDesktop :: Text
                -- ^ Current desktop name
        }
  deriving (Show, Read)

instance NFData CaptureData where
    rnf (CaptureData a b c) = a `deepseq` b `deepseq` c `deepseq` ()

type ActivityData = [Activity]

data Activity = Activity 
        { activityCategory :: Maybe Category
        , activityName :: Text
        }
  deriving (Ord, Eq)

instance NFData Activity where
    rnf (Activity a b) = a `deepseq` b `deepseq` ()

-- | An activity with special meaning: ignored by default (i.e. for idle times)
inactiveActivity = Activity Nothing "inactive"

instance Show Activity where
 show (Activity mbC t) = maybe "" ((++":").T.unpack) mbC ++ (T.unpack t)

instance Read Activity where
 readPrec = readP_to_Prec $ \_ ->
                   (do cat <- munch1 (/= ':')
                       char ':'
                       tag <- many1 ReadP.get
                       return $ Activity (Just (T.pack cat)) (T.pack tag))
                   <++ (Activity Nothing . T.pack <$> many1 ReadP.get)

type Category = Text

isCategory :: Category -> Activity -> Bool
isCategory cat (Activity (Just cat') _) = cat == cat'
isCategory _   _                        = False


-- Data.Binary instances

instance StringReferencingBinary a => StringReferencingBinary (TimeLogEntry a) where
 ls_put strs tle = do
        -- A version tag
        putWord8 1
        put (tlTime tle)
        put (tlRate tle)
        ls_put strs (tlData tle)
 ls_get strs = do
        v <- getWord8
        case v of
         1 -> TimeLogEntry <$> get <*> get <*> ls_get strs
         _ -> error $ "Unsupported TimeLogEntry version tag " ++ show v

instance Binary UTCTime where
 put (UTCTime (ModifiedJulianDay d) t) = do
        put d
        put (toRational t)
 get = do
        d <- get
        t <- get
        return $ UTCTime (ModifiedJulianDay d) ({-# SCC diffTimeFromRational #-} fromRational t)

instance ListOfStringable CaptureData where
  listOfStrings = concatMap (\(b,t,p) -> [t,p]) . cWindows

instance StringReferencingBinary CaptureData where
-- Versions:
-- 1 First version
-- 2 Using ListOfStringable
 ls_put strs cd = do
        -- A version tag
        putWord8 3
        ls_put strs (cWindows cd)
        ls_put strs (cLastActivity cd)
        ls_put strs (cDesktop cd)
 ls_get strs = do
        v <- getWord8
        case v of
         1 -> CaptureData <$> get <*> get <*> pure ""
         2 -> CaptureData <$> ls_get strs <*> ls_get strs <*> pure ""
         3 -> CaptureData <$> ls_get strs <*> ls_get strs <*> ls_get strs
         _ -> error $ "Unsupported CaptureData version tag " ++ show v

  -- | 'getMany n' get 'n' elements in order, without blowing the stack.
  --   From Data.Binary
getMany :: Binary a => Int -> Get [a]
getMany n = go [] n
 where
    go xs 0 = return $! reverse xs
    go xs i = do x <- get
                 -- we must seq x to avoid stack overflows due to laziness in
                 -- (>>=)
                 x `seq` go (x:xs) (i-1)
{-# INLINE getMany #-}
