-- | Utility functions for manipulating Windows paths related to the
-- Haskell Platform.
module System.Win32.HPPath
  ( winPath
  , unWinPath
  , mapWinPath
  , clearPath
  , addHPPath
  , isVersionOpt
  , WinPath(getWinPath)
  )
 where

import Data.List (intercalate, isInfixOf)
import Data.List.Split (splitOn)
import Data.Maybe (fromMaybe)

-- | A Windows path that has been split into its components.
newtype WinPath = WinPath { getWinPath :: [FilePath] }

-- | A directory which can contain command line executables related to
-- Haskell. It always exists near the beginning of the Windows PATH,
-- so the executables here override other executables with the same
-- name.
hpMaster :: FilePath
hpMaster = "C:\\Program Files\\Haskell\\bin"

-- | Subpaths of a Haskell Platform installation that are placed near
-- the beginning of the Windows PATH.
hpInitialSubpaths :: [FilePath]
hpInitialSubpaths =
  [ "lib\\extralibs\\bin"
  , "bin"
  ]

-- | Subpaths of a Haskell Platform installation that are placed near
-- the end of the Windows PATH.
hpFinalSubpaths :: [FilePath]
hpFinalSubpaths = ["mingw\\bin"]

-- | Split a Windows PATH 'String' into a 'WinPath'.
winPath :: String -> WinPath
winPath = WinPath . splitOn ";"

-- | Join the paths of a 'WinPath' into a Windows PATH 'String'.
unWinPath :: WinPath -> String
unWinPath = intercalate ";" . getWinPath

-- | Apply a function on 'WithPath' to a list of 'FilePaths'.
mapWinPath :: (WinPath -> WinPath) -> String -> String
mapWinPath f = unWinPath . f . winPath

-- | Remove all Haskell-related paths from a 'WinPath'.
clearPath :: WinPath -> WinPath
clearPath = WinPath . filter (not . ("Haskell" `isInfixOf`)) . getWinPath

-- | Install all required paths for the Haskell Platform based at the
-- given 'FilePath' into a 'WinPath'. The initial 'WinPath' must not
-- contain any Haskell-related paths.
addHPPath :: FilePath -> WinPath -> WinPath
addHPPath hp =
    WinPath
    . (hpMaster :)
    . (map prependHp hpInitialSubpaths ++)
    . (++ map prependHp hpFinalSubpaths)
    . getWinPath
  where
    prependHp = (hp' ++)
    hp'
     | endsWith '\\' hp = unquote hp
     | otherwise        = unquote hp ++ "\\"

-- | Remove double quotes, if they exist, from around a 'String'.
unquote :: String -> String
unquote = unbracket '"' '"'

-- | Remove the specified left and right brackets from around a list.
unbracket :: Eq a => a -> a -> [a] -> [a]
unbracket l r xs@(x:xs') | x == l = fromMaybe xs $ dropFromEnd r xs'
unbracket _ _ xs                  = xs

-- | Remove the specified value from the end of the list, if possible.
dropFromEnd :: Eq a => a -> [a] -> Maybe [a]
dropFromEnd x [y] | x == y = Just []
dropFromEnd x (y:ys)       = fmap (y:) $ dropFromEnd x ys
dropFromEnd _ _            = Nothing

-- | Test whether the last element of a list exists and matches the
-- given value.
endsWith :: Eq a => a -> [a] -> Bool
endsWith x [y]    = x == y
endsWith x (_:ys) = endsWith x ys
endsWith _ _      = False

-- | Check whether the given argument to a command on Windows looks
-- like a request either for the version number or for command syntax
-- help.
isVersionOpt :: String -> Bool
isVersionOpt =
  (`elem`
    [ "/?"
    , "/v"
    , "/version"
    , "/h"
    , "/help"
    , "-v"
    , "--version"
    , "-h"
    , "--help"
    ]
  )