{-# LANGUAGE DeriveGeneric #-}

module Hedgehog.Extras.Stock.IO.Network.Sprocket
  ( Sprocket(..)
  , doesSprocketExist
  , sprocketArgumentName
  , sprocketSystemName
  , maxSprocketArgumentNameLength
  ) where

import           Data.Bool (Bool)
import           Data.Char (Char)
import           Data.Eq (Eq ((==)))
import           Data.Functor (Functor (fmap))
import           Data.Int (Int)
import           Data.Semigroup (Semigroup ((<>)))
import           Data.String (String)
import           GHC.Generics (Generic)
import           Hedgehog.Extras.Stock.OS (isWin32)
import           System.FilePath ((</>))
import           System.IO (FilePath, IO)
import           Text.Show (Show)

import qualified Hedgehog.Extras.Stock.IO.Network.NamedPipe as IO
import qualified Hedgehog.Extras.Stock.IO.Network.Socket as IO

-- | Socket emulation.  On Posix it represents a socket.  On Windows it represents a named pipe.
data Sprocket = Sprocket
  { Sprocket -> FilePath
sprocketBase :: String
  , Sprocket -> FilePath
sprocketName :: String
  } deriving ((forall x. Sprocket -> Rep Sprocket x)
-> (forall x. Rep Sprocket x -> Sprocket) -> Generic Sprocket
forall x. Rep Sprocket x -> Sprocket
forall x. Sprocket -> Rep Sprocket x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cfrom :: forall x. Sprocket -> Rep Sprocket x
from :: forall x. Sprocket -> Rep Sprocket x
$cto :: forall x. Rep Sprocket x -> Sprocket
to :: forall x. Rep Sprocket x -> Sprocket
Generic, Sprocket -> Sprocket -> Bool
(Sprocket -> Sprocket -> Bool)
-> (Sprocket -> Sprocket -> Bool) -> Eq Sprocket
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Sprocket -> Sprocket -> Bool
== :: Sprocket -> Sprocket -> Bool
$c/= :: Sprocket -> Sprocket -> Bool
/= :: Sprocket -> Sprocket -> Bool
Eq, Int -> Sprocket -> ShowS
[Sprocket] -> ShowS
Sprocket -> FilePath
(Int -> Sprocket -> ShowS)
-> (Sprocket -> FilePath) -> ([Sprocket] -> ShowS) -> Show Sprocket
forall a.
(Int -> a -> ShowS) -> (a -> FilePath) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Sprocket -> ShowS
showsPrec :: Int -> Sprocket -> ShowS
$cshow :: Sprocket -> FilePath
show :: Sprocket -> FilePath
$cshowList :: [Sprocket] -> ShowS
showList :: [Sprocket] -> ShowS
Show)

-- | Test if the sprocket exists
doesSprocketExist :: Sprocket -> IO Bool
doesSprocketExist :: Sprocket -> IO Bool
doesSprocketExist Sprocket
socket = if Bool
isWin32
  then FilePath -> IO Bool
IO.doesNamedPipeExist (Sprocket -> FilePath
sprocketSystemName Sprocket
socket)
  else FilePath -> IO Bool
IO.doesSocketExist (Sprocket -> FilePath
sprocketSystemName Sprocket
socket)

-- | Use this to query the OS about the sprocket
sprocketSystemName :: Sprocket -> FilePath
sprocketSystemName :: Sprocket -> FilePath
sprocketSystemName sprocket :: Sprocket
sprocket@(Sprocket FilePath
base FilePath
name) = if Bool
isWin32
  then Sprocket -> FilePath
sprocketNamedPipeName Sprocket
sprocket
  else FilePath
base FilePath -> ShowS
</> FilePath
name

-- | Use this when needing to pass a sprocket into a command line argument.
sprocketArgumentName :: Sprocket -> FilePath
sprocketArgumentName :: Sprocket -> FilePath
sprocketArgumentName sprocket :: Sprocket
sprocket@(Sprocket FilePath
_ FilePath
name) = if Bool
isWin32
  then Sprocket -> FilePath
sprocketNamedPipeName Sprocket
sprocket
  else FilePath
name

maxSprocketArgumentNameLength :: Int
maxSprocketArgumentNameLength :: Int
maxSprocketArgumentNameLength = if Bool
isWin32
  then Int
256
  else Int
104

-- | The named pipe name of the sprocket on Win32 systems
sprocketNamedPipeName :: Sprocket -> FilePath
sprocketNamedPipeName :: Sprocket -> FilePath
sprocketNamedPipeName (Sprocket FilePath
_ FilePath
name) = FilePath
"\\\\.\\pipe" FilePath -> ShowS
forall a. Semigroup a => a -> a -> a
<> ShowS
dedupBackslash (FilePath
"\\" FilePath -> ShowS
forall a. Semigroup a => a -> a -> a
<> (Char -> Char) -> ShowS
forall a b. (a -> b) -> [a] -> [b]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Char -> Char
slackToBack FilePath
name)
  where slackToBack :: Char -> Char
        slackToBack :: Char -> Char
slackToBack Char
c = if Char
c Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'/' then Char
'\\' else Char
c
        dedupBackslash :: String -> String
        dedupBackslash :: ShowS
dedupBackslash (Char
'\\':Char
'\\':FilePath
xs) = ShowS
dedupBackslash (Char
'\\'Char -> ShowS
forall a. a -> [a] -> [a]
:FilePath
xs)
        dedupBackslash (Char
x:FilePath
xs) = Char
xChar -> ShowS
forall a. a -> [a] -> [a]
:ShowS
dedupBackslash FilePath
xs
        dedupBackslash [] = []