{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}

module MyLib where

import Data.Aeson
import Data.Aeson.Text
import qualified Data.ByteString as BS
import Data.Either
import Data.Int (Int64)
import qualified Data.Text.Lazy as T
import Data.Time
import GHC.Generics
import System.AtomicWrite.Writer.LazyText
import System.Directory (
    XdgDirectory (XdgData),
    createDirectoryIfMissing,
    doesFileExist,
    getXdgDirectory,
 )

someFunc :: IO ()
someFunc :: IO ()
someFunc = FilePath -> IO ()
putStrLn FilePath
"someFunc"

-- datatype
data Run = Run
    { Run -> UTCTime
tstamp :: !UTCTime -- when was this run?
    , Run -> Text
cmdline :: !T.Text -- what was the command?
    , Run -> Text
cwd :: !T.Text
    , Run -> Double
runTime :: !Double -- seconds of wall clock time
    , Run -> Double
cpuTime :: !Double -- cpu seconds
    , Run -> Int64
cpuCycles :: !Int64 -- cpu cycles from rdtsc instruction
    }
    deriving (Int -> Run -> ShowS
[Run] -> ShowS
Run -> FilePath
(Int -> Run -> ShowS)
-> (Run -> FilePath) -> ([Run] -> ShowS) -> Show Run
forall a.
(Int -> a -> ShowS) -> (a -> FilePath) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Run -> ShowS
showsPrec :: Int -> Run -> ShowS
$cshow :: Run -> FilePath
show :: Run -> FilePath
$cshowList :: [Run] -> ShowS
showList :: [Run] -> ShowS
Show, Run -> Run -> Bool
(Run -> Run -> Bool) -> (Run -> Run -> Bool) -> Eq Run
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Run -> Run -> Bool
== :: Run -> Run -> Bool
$c/= :: Run -> Run -> Bool
/= :: Run -> Run -> Bool
Eq, Eq Run
Eq Run =>
(Run -> Run -> Ordering)
-> (Run -> Run -> Bool)
-> (Run -> Run -> Bool)
-> (Run -> Run -> Bool)
-> (Run -> Run -> Bool)
-> (Run -> Run -> Run)
-> (Run -> Run -> Run)
-> Ord Run
Run -> Run -> Bool
Run -> Run -> Ordering
Run -> Run -> Run
forall a.
Eq a =>
(a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
$ccompare :: Run -> Run -> Ordering
compare :: Run -> Run -> Ordering
$c< :: Run -> Run -> Bool
< :: Run -> Run -> Bool
$c<= :: Run -> Run -> Bool
<= :: Run -> Run -> Bool
$c> :: Run -> Run -> Bool
> :: Run -> Run -> Bool
$c>= :: Run -> Run -> Bool
>= :: Run -> Run -> Bool
$cmax :: Run -> Run -> Run
max :: Run -> Run -> Run
$cmin :: Run -> Run -> Run
min :: Run -> Run -> Run
Ord, (forall x. Run -> Rep Run x)
-> (forall x. Rep Run x -> Run) -> Generic Run
forall x. Rep Run x -> Run
forall x. Run -> Rep Run x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cfrom :: forall x. Run -> Rep Run x
from :: forall x. Run -> Rep Run x
$cto :: forall x. Rep Run x -> Run
to :: forall x. Rep Run x -> Run
Generic, [Run] -> Value
[Run] -> Encoding
Run -> Value
Run -> Encoding
(Run -> Value)
-> (Run -> Encoding)
-> ([Run] -> Value)
-> ([Run] -> Encoding)
-> ToJSON Run
forall a.
(a -> Value)
-> (a -> Encoding)
-> ([a] -> Value)
-> ([a] -> Encoding)
-> ToJSON a
$ctoJSON :: Run -> Value
toJSON :: Run -> Value
$ctoEncoding :: Run -> Encoding
toEncoding :: Run -> Encoding
$ctoJSONList :: [Run] -> Value
toJSONList :: [Run] -> Value
$ctoEncodingList :: [Run] -> Encoding
toEncodingList :: [Run] -> Encoding
ToJSON, Value -> Parser [Run]
Value -> Parser Run
(Value -> Parser Run) -> (Value -> Parser [Run]) -> FromJSON Run
forall a.
(Value -> Parser a) -> (Value -> Parser [a]) -> FromJSON a
$cparseJSON :: Value -> Parser Run
parseJSON :: Value -> Parser Run
$cparseJSONList :: Value -> Parser [Run]
parseJSONList :: Value -> Parser [Run]
FromJSON)

-- ugh, I wish there was an easier way, but whatever
data Runs = Runs
    { Runs -> [Run]
rs :: ![Run]
    }
    deriving (Int -> Runs -> ShowS
[Runs] -> ShowS
Runs -> FilePath
(Int -> Runs -> ShowS)
-> (Runs -> FilePath) -> ([Runs] -> ShowS) -> Show Runs
forall a.
(Int -> a -> ShowS) -> (a -> FilePath) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Runs -> ShowS
showsPrec :: Int -> Runs -> ShowS
$cshow :: Runs -> FilePath
show :: Runs -> FilePath
$cshowList :: [Runs] -> ShowS
showList :: [Runs] -> ShowS
Show, Runs -> Runs -> Bool
(Runs -> Runs -> Bool) -> (Runs -> Runs -> Bool) -> Eq Runs
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Runs -> Runs -> Bool
== :: Runs -> Runs -> Bool
$c/= :: Runs -> Runs -> Bool
/= :: Runs -> Runs -> Bool
Eq, Eq Runs
Eq Runs =>
(Runs -> Runs -> Ordering)
-> (Runs -> Runs -> Bool)
-> (Runs -> Runs -> Bool)
-> (Runs -> Runs -> Bool)
-> (Runs -> Runs -> Bool)
-> (Runs -> Runs -> Runs)
-> (Runs -> Runs -> Runs)
-> Ord Runs
Runs -> Runs -> Bool
Runs -> Runs -> Ordering
Runs -> Runs -> Runs
forall a.
Eq a =>
(a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
$ccompare :: Runs -> Runs -> Ordering
compare :: Runs -> Runs -> Ordering
$c< :: Runs -> Runs -> Bool
< :: Runs -> Runs -> Bool
$c<= :: Runs -> Runs -> Bool
<= :: Runs -> Runs -> Bool
$c> :: Runs -> Runs -> Bool
> :: Runs -> Runs -> Bool
$c>= :: Runs -> Runs -> Bool
>= :: Runs -> Runs -> Bool
$cmax :: Runs -> Runs -> Runs
max :: Runs -> Runs -> Runs
$cmin :: Runs -> Runs -> Runs
min :: Runs -> Runs -> Runs
Ord, (forall x. Runs -> Rep Runs x)
-> (forall x. Rep Runs x -> Runs) -> Generic Runs
forall x. Rep Runs x -> Runs
forall x. Runs -> Rep Runs x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cfrom :: forall x. Runs -> Rep Runs x
from :: forall x. Runs -> Rep Runs x
$cto :: forall x. Rep Runs x -> Runs
to :: forall x. Rep Runs x -> Runs
Generic, [Runs] -> Value
[Runs] -> Encoding
Runs -> Value
Runs -> Encoding
(Runs -> Value)
-> (Runs -> Encoding)
-> ([Runs] -> Value)
-> ([Runs] -> Encoding)
-> ToJSON Runs
forall a.
(a -> Value)
-> (a -> Encoding)
-> ([a] -> Value)
-> ([a] -> Encoding)
-> ToJSON a
$ctoJSON :: Runs -> Value
toJSON :: Runs -> Value
$ctoEncoding :: Runs -> Encoding
toEncoding :: Runs -> Encoding
$ctoJSONList :: [Runs] -> Value
toJSONList :: [Runs] -> Value
$ctoEncodingList :: [Runs] -> Encoding
toEncodingList :: [Runs] -> Encoding
ToJSON, Value -> Parser [Runs]
Value -> Parser Runs
(Value -> Parser Runs) -> (Value -> Parser [Runs]) -> FromJSON Runs
forall a.
(Value -> Parser a) -> (Value -> Parser [a]) -> FromJSON a
$cparseJSON :: Value -> Parser Runs
parseJSON :: Value -> Parser Runs
$cparseJSONList :: Value -> Parser [Runs]
parseJSONList :: Value -> Parser [Runs]
FromJSON)

-- read and write data
readSandWatchData :: IO Runs
readSandWatchData :: IO Runs
readSandWatchData = do
    FilePath
sandWatchDataFile <- IO FilePath
getSandWatchFilePath
    Bool
dataExists <- FilePath -> IO Bool
doesFileExist FilePath
sandWatchDataFile
    if Bool -> Bool
not Bool
dataExists
        then Runs -> IO Runs
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Runs -> IO Runs) -> Runs -> IO Runs
forall a b. (a -> b) -> a -> b
$ [Run] -> Runs
Runs []
        else do
            ByteString
contents <- FilePath -> IO ByteString
BS.readFile FilePath
sandWatchDataFile
            Runs -> IO Runs
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Runs -> IO Runs) -> Runs -> IO Runs
forall a b. (a -> b) -> a -> b
$ Runs -> Either FilePath Runs -> Runs
forall b a. b -> Either a b -> b
fromRight ([Run] -> Runs
Runs []) (ByteString -> Either FilePath Runs
forall a. FromJSON a => ByteString -> Either FilePath a
eitherDecodeStrict ByteString
contents) -- failed to parse json? you get an empty history!

writeSandWatchData :: Runs -> IO ()
writeSandWatchData :: Runs -> IO ()
writeSandWatchData Runs
rs' = do
    FilePath
sandWatchDataFile <- IO FilePath
getSandWatchFilePath
    FilePath -> Text -> IO ()
atomicWriteFile FilePath
sandWatchDataFile (Runs -> Text
forall a. ToJSON a => a -> Text
encodeToLazyText Runs
rs')

getSandWatchFilePath :: IO FilePath
getSandWatchFilePath :: IO FilePath
getSandWatchFilePath = do
    FilePath
sandWatchDir <- XdgDirectory -> FilePath -> IO FilePath
getXdgDirectory XdgDirectory
XdgData FilePath
"sandwatch"
    Bool -> FilePath -> IO ()
createDirectoryIfMissing Bool
True FilePath
sandWatchDir -- is this a bad place to do this?
    FilePath -> IO FilePath
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (FilePath -> IO FilePath) -> FilePath -> IO FilePath
forall a b. (a -> b) -> a -> b
$ FilePath
sandWatchDir FilePath -> ShowS
forall a. Semigroup a => a -> a -> a
<> FilePath
"/runs"

-- analyze

{-
prefix match for estimate?
1. filter on cwd,
2. prefix match up to the first space
3. average the past matching runtimes
4. divide by five minutes
5. write out number of sandwiches estimated before running the command
-}
-- need to strip /home/shae/ and /Users/shae/ and whatever shows up on Windows?
-- whatever, do the thing and come back and clean up
filterByCWD :: Runs -> T.Text -> Runs
filterByCWD :: Runs -> Text -> Runs
filterByCWD Runs
runs Text
thiscwd = [Run] -> Runs
Runs ([Run] -> Runs) -> [Run] -> Runs
forall a b. (a -> b) -> a -> b
$ (Run -> Bool) -> [Run] -> [Run]
forall a. (a -> Bool) -> [a] -> [a]
filter (\Run
r -> Text -> Text
T.strip Text
thiscwd Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Run -> Text
cwd Run
r) (Runs -> [Run]
rs Runs
runs)

filterByFirstWord :: Runs -> T.Text -> Runs
filterByFirstWord :: Runs -> Text -> Runs
filterByFirstWord Runs
runs Text
thiscmdline = [Run] -> Runs
Runs ([Run] -> Runs) -> [Run] -> Runs
forall a b. (a -> b) -> a -> b
$ (Run -> Bool) -> [Run] -> [Run]
forall a. (a -> Bool) -> [a] -> [a]
filter (\Run
r -> Text -> Text
fstWord Text
thiscmdline Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text -> Text
fstWord (Run -> Text
cmdline Run
r)) (Runs -> [Run]
rs Runs
runs)
  where
    fstWord :: Text -> Text
fstWord = (Text, Text) -> Text
forall a b. (a, b) -> a
fst ((Text, Text) -> Text) -> (Text -> (Text, Text)) -> Text -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. HasCallStack => Text -> Text -> (Text, Text)
Text -> Text -> (Text, Text)
T.breakOn Text
" "

-- "cabal build" for example
filterByFirstNWords :: Runs -> Int -> T.Text -> Runs
filterByFirstNWords :: Runs -> Int -> Text -> Runs
filterByFirstNWords Runs
runs Int
n Text
thiscmdline = [Run] -> Runs
Runs ([Run] -> Runs) -> [Run] -> Runs
forall a b. (a -> b) -> a -> b
$ (Run -> Bool) -> [Run] -> [Run]
forall a. (a -> Bool) -> [a] -> [a]
filter (\Run
r -> Int -> Text -> [Text]
fstWords Int
n Text
thiscmdline [Text] -> [Text] -> Bool
forall a. Eq a => a -> a -> Bool
== Int -> Text -> [Text]
fstWords Int
n (Run -> Text
cmdline Run
r)) (Runs -> [Run]
rs Runs
runs)
  where
    fstWords :: Int -> Text -> [Text]
fstWords Int
n' Text
c = Int -> [Text] -> [Text]
forall a. Int -> [a] -> [a]
take Int
n' ([Text] -> [Text]) -> [Text] -> [Text]
forall a b. (a -> b) -> a -> b
$ HasCallStack => Text -> Text -> [Text]
Text -> Text -> [Text]
T.splitOn Text
" " Text
c

-- | expects historical runs, number of words to match, a Run that has cmdline and cwd populated
guessTime :: Int -> Runs -> Run -> Double
guessTime :: Int -> Runs -> Run -> Double
guessTime Int
n Runs
pastruns Run
thisrun = Double
avgWallSeconds
  where
    runsInThisDir :: Runs
runsInThisDir = Runs -> Text -> Runs
filterByCWD Runs
pastruns (Run -> Text
cwd Run
thisrun)
    runsMatchingNWords :: Runs
runsMatchingNWords = Runs -> Int -> Text -> Runs
filterByFirstNWords Runs
runsInThisDir Int
n (Run -> Text
cmdline Run
thisrun)
    wallTimes :: [Double]
wallTimes = Run -> Double
runTime (Run -> Double) -> [Run] -> [Double]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Runs -> [Run]
rs Runs
runsMatchingNWords
    avgWallSeconds :: Double
avgWallSeconds = [Double] -> Double
forall a. Num a => [a] -> a
forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
sum [Double]
wallTimes Double -> Double -> Double
forall a. Fractional a => a -> a -> a
/ Int -> Double
forall a b. (Integral a, Num b) => a -> b
fromIntegral ([Double] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [Double]
wallTimes)

-- cycles = cpuCycles <$> (rs runsMatchingNWords)
-- avgCycles = sum cycles /
-- would need to promote this Int64 to Integer for the math to work out?