{-# LANGUAGE RecordWildCards #-}

module Watcher
  ( watch
  ) where

import           Data.String          (fromString)
import           Parser
import           Protolude
import           System.Directory
import           System.FilePath
import qualified System.FilePath.Glob as G
import qualified System.FSNotify      as FS
import qualified Twitch               as T

type Handler = FilePath -> IO ()

watch :: Config -> Handler -> IO FS.WatchManager
watch config handler = do
  currentDir <- getCurrentDirectory
  baseDirectories <- mapM toAbsoluteDirectory (_dirs config)
  directoriesToWatch <- getDirectoriesToWatch baseDirectories
  let ignoringHandler = handlerWithIgnore (ignoredFilePatterns currentDir config) handler
  T.runWithConfig currentDir (twitchConfig directoriesToWatch) $ registerAllHandlers config ignoringHandler

registerAllHandlers :: Config -> Handler -> T.Dep
registerAllHandlers config handler = mapM_ (registerHandler handler) (allFilePaths config)

registerHandler :: Handler -> FilePath -> T.Dep
registerHandler handler fileGlob = T.addModify handler (fromString $ toS fileGlob)

handlerWithIgnore :: [G.Pattern] -> Handler -> Handler
handlerWithIgnore ignored handler filePath =
  if any (\pattern -> G.match pattern filePath) ignored
    then return ()
    else (handler filePath)

ignoredFilePatterns :: FilePath -> Config -> [G.Pattern]
ignoredFilePatterns currentDirectory Config {..} =
  let
    absoluteIgnoreDir :: Text -> [FilePath]
    absoluteIgnoreDir dir = createFilePaths (toS (currentDirectory </> toS dir)) (concat _ignore)
    allIgnored = foldl (\acc dir -> acc ++ (absoluteIgnoreDir dir)) [] _dirs
  in map (G.compile) allIgnored

allFilePaths :: Config -> [FilePath]
allFilePaths Config {..} = foldl (\acc dir -> acc ++ createFilePaths dir _files) [] _dirs

createFilePaths :: Text -> [Text] -> [FilePath]
createFilePaths dir = map (\file -> toS dir </> toS file)

watchConfig :: FS.WatchConfig
watchConfig = FS.WatchConfig {FS.confDebounce = FS.DebounceDefault, FS.confPollInterval = 0, FS.confUsePolling = False}

twitchConfig :: [FilePath] -> T.Config
twitchConfig dirsToWatch = T.Config {logger = const $ return (), dirs = dirsToWatch, watchConfig = watchConfig}

toAbsoluteDirectory :: Text -> IO FilePath
toAbsoluteDirectory filePath = do
  currentDir <- getCurrentDirectory
  makeAbsolute $ currentDir </> toS filePath

getDirectoriesToWatch :: [FilePath] -> IO [FilePath]
getDirectoriesToWatch = foldMap getDirectories
  where
    getDirectories baseDirectory = do
      subDirs <- findAllDirs baseDirectory
      return $ baseDirectory : subDirs

findAllDirs :: FilePath -> IO [FilePath]
findAllDirs path = do
  dirs <- findImmediateDirs path
  nestedDirs <- mapM findAllDirs dirs
  return (dirs ++ concat nestedDirs)

findImmediateDirs :: FilePath -> IO [FilePath]
findImmediateDirs = getDirectoryContentsPath >=> filterM doesDirectoryExist >=> canonicalize
  where
    canonicalize :: [FilePath] -> IO [FilePath]
    canonicalize = mapM canonicalizeDirPath

getDirectoryContentsPath :: FilePath -> IO [FilePath]
getDirectoryContentsPath path = map (path </>) . filter (`notElem` [".", ".."]) <$> getDirectoryContents path

canonicalizeDirPath :: FilePath -> IO FilePath
canonicalizeDirPath path = addTrailingPathSeparator <$> canonicalizePath path