-- We refer to otherwise unused modules in documentation.
{-# OPTIONS_GHC -fno-warn-unused-imports #-}

module System.Win32.Services.Accept
    ( ServiceAccept (..)
    , pokeServiceAccept
    , peekServiceAccept
    ) where

import Data.Bits
import Data.Maybe
import Text.Printf

import Import

-- Imported for haddocks
import qualified System.Win32.Services.Control as C

-- | The control codes the service accepts and processes in its handler
--   function (See 'HandlerFunction'). By default, all services accept the
--   'C.Interrogate' value. To accept the 'DEVICEEVENT' value, the service must
--   register to receive device events by using the
--   'registerDeviceNotification' function.
data ServiceAccept
    -- | The service is a network component that can accept changes in its
    --   binding without being stopped and restarted. This control code allows
    --   the service to receive 'C.NetBindAdd', 'C.NetBindRemove',
    --   'C.NetBindEnable', and 'C.NetBindDisable' notifications.
    = AcceptNetBindChange
    -- | The service can reread its startup parameters without being stopped
    --   and restarted. This control code allows the service to receive
    --   'C.ParamChange' notifications.
    | AcceptParamChange
    -- | The service can be paused and continued. This control code allows the
    --   service to receive 'C.Pause' and 'C.Continue' notifications.
    | AcceptPauseContinue
    -- | MSDN documentation says that this function is not supported on
    --   Windows Server 2003 or Windows XP/2000. The support status on other
    --   versions is unknown to me.
    --
    --   The service can perform preshutdown tasks. This control code enables
    --   the service to receive 'C.Preshutdown' notifications.
    --   Note that only the system can send it.
    | AcceptPreshutdown
    -- | The service is notified when system shutdown occurs. This control
    --   code allows the service to receive 'C.Shutdown' notifications. Note
    --   that only the system can send it.
    | AcceptShutdown
    -- | The service can be stopped. This control code allows the service to
    --   receive 'C.Stop' notifications.
    | AcceptStop
    deriving (Show)

peekServiceAccept :: Ptr DWORD -> IO [ServiceAccept]
peekServiceAccept ptr = unflag <$> peek ptr

pokeServiceAccept :: Ptr DWORD -> [ServiceAccept] -> IO ()
pokeServiceAccept ptr sas = poke ptr . flag $ sas

-- | Marshal a ServiceAccept "out" to be used in C-land
marshOut :: ServiceAccept -> DWORD
marshOut AcceptNetBindChange = 0x00000010
marshOut AcceptParamChange   = 0x00000008
marshOut AcceptPauseContinue = 0x00000002
marshOut AcceptPreshutdown   = 0x00000100
marshOut AcceptShutdown      = 0x00000004
marshOut AcceptStop          = 0x00000001

-- | Marshall a DWORD "in" to be used in Haskell-land as a ServiceAccept
marshIn :: DWORD -> Either String ServiceAccept
marshIn 0x00000010 = Right AcceptNetBindChange
marshIn 0x00000008 = Right AcceptParamChange
marshIn 0x00000002 = Right AcceptPauseContinue
marshIn 0x00000100 = Right AcceptPreshutdown
marshIn 0x00000004 = Right AcceptShutdown
marshIn 0x00000001 = Right AcceptStop
marshIn 0x00000020 = unsupported "SERVICE_ACCEPT_HARDWAREPROFILECHANGE"
marshIn 0x00000040 = unsupported "SERVICE_ACCEPT_POWEREVENT"
marshIn 0x00000080 = unsupported "SERVICE_ACCEPT_SESSIONCHANGE"
marshIn 0x00000200 = unsupported "SERVICE_ACCEPT_TIMECHANGE"
marshIn 0x00000400 = unsupported "SERVICE_ACCEPT_TRIGGEREVENT"
marshIn 0x00000800 = unsupported "SERVICE_ACCEPT_USERMODEREBOOT"
marshIn x = Left $ "The " ++ printf "%x" x ++ " control code is undocumented."

unsupported :: String -> Either String a
unsupported name = Left $ "The " ++ name ++ " control code is unsupported by this binding."

-- | This function takes a 'DWORD' and assumes it is a flagfield. Each bit
--   is masked off and converted into a value. Any failures are silently
--   discarded.
unflag :: DWORD -> [ServiceAccept]
unflag f = mapMaybe (hush . marshIn . (.&. f)) masks
  where
    masks = take 32 $ iterate (`shiftL` 1) 1

flag :: [ServiceAccept] -> DWORD
flag fs = foldl (\flag' f -> flag' .|. marshOut f) 0 fs