-- |
--   Copyright   :  (c) Sam Truzjan 2013
--   License     :  BSD3
--   Maintainer  :  pxqr.sta@gmail.com
--   Stability   :  stable
--   Portability :  portable
--
--   Lookup devices in the sys filesystem, filter devices by
--   properties, and return a sorted list of devices.
--
{-# LANGUAGE ForeignFunctionInterface #-}
module System.UDev.Enumerate
       ( Enumerate

       , newEnumerate

         -- * Match
       , Subsystem
       , addMatchSubsystem
       , addNoMatchSubsystem

       , SysAttr
       , SysValue
       , addMatchSysattr
       , addNoMatchSysattr

       , addMatchProperty
       , addMatchTag
       , addMatchParent
       , addMatchIsInitialized
       , addMatchSysname
       , addSyspath

         -- * Scan
       , scanDevices
       , scanSubsystems

         -- * Query
       , getListEntry
       ) where

import Data.ByteString as BS
import Foreign
import Foreign.C.Error
import Foreign.C.String
import Foreign.C.Types
import System.Posix.FilePath

import System.UDev.Context
import System.UDev.Device
import System.UDev.List
import System.UDev.Types


foreign import ccall unsafe "udev_enumerate_new"
  c_new :: UDev -> IO Enumerate

-- | Create an enumeration context to scan /sys.
newEnumerate :: UDev -> IO Enumerate
newEnumerate = c_new
{-# INLINE newEnumerate #-}

{-----------------------------------------------------------------------
--  Match
-----------------------------------------------------------------------}

foreign import ccall unsafe "udev_enumerate_add_match_subsystem"
  c_addMatchSubsystem :: Enumerate -> CString -> IO CInt

-- | Kernel subsystem string.
type Subsystem = ByteString

-- | Match only devices belonging to a certain kernel subsystem.
addMatchSubsystem :: Enumerate -- ^ context
                  -> Subsystem -- ^ filter for a subsystem of the
                               -- device to include in the list
                  -> IO ()     -- ^ can throw exception
addMatchSubsystem enumerate subsystem = do
  throwErrnoIfMinus1_ "addMatchSubsystem" $ do
    useAsCString subsystem $
      c_addMatchSubsystem enumerate

foreign import ccall unsafe "udev_enumerate_add_nomatch_subsystem"
  c_addNoMatchSubsystem :: Enumerate -> CString -> IO CInt

-- | Match only devices not belonging to a certain kernel subsystem.
addNoMatchSubsystem :: Enumerate -- ^ context
                    -> Subsystem -- ^ filter for a subsystem of the
                                 -- device to exclude from the list
                    -> IO ()     -- ^ can throw exception
addNoMatchSubsystem enumerate subsystem = do
  throwErrnoIfMinus1_ "addNoMatchSubsystem" $ do
    useAsCString subsystem $
      c_addNoMatchSubsystem enumerate

-- | \/sys attribute string.
type SysAttr  = ByteString

-- | Attribute specific \/sys value string. Can be an int or
-- identifier depending on attribute.
type SysValue = ByteString

foreign import ccall unsafe "udev_enumerate_add_match_sysattr"
  c_addMatchSysattr :: Enumerate -> CString -> CString -> IO CInt

-- | Match only devices with a certain \/sys device attribute.
addMatchSysattr :: Enumerate      -- ^ context
                -> SysAttr        -- ^ filter for a sys attribute at
                                  -- the device to include in the list
                -> Maybe SysValue -- ^ optional value of the sys attribute
                -> IO ()          -- ^ can throw exception
addMatchSysattr enumerate sysattr mvalue = do
  throwErrnoIf_ (< 0) "addMatchSysattr" $  do
    useAsCString sysattr $ \ c_sysattr ->  do
      case mvalue of
        Nothing    -> c_addMatchSysattr enumerate c_sysattr nullPtr
        Just value -> do
          useAsCString value $ \ c_value -> do
            c_addMatchSysattr enumerate c_sysattr c_value

foreign import ccall unsafe  "udev_enumerate_add_nomatch_sysattr"
  c_addNoMatchSysattr :: Enumerate -> CString -> CString -> IO CInt

-- | Match only devices not having a certain /sys device attribute.
addNoMatchSysattr :: Enumerate  -- ^ context
                  -> ByteString -- ^ filter for a sys attribute at the
                                -- device to exclude from the list
                  -> Maybe ByteString -- ^ optional value of the sys
                                      -- attribute
                  -> IO ()
addNoMatchSysattr enumerate sysattr mvalue = do
  throwErrnoIf_ (< 0) "addNoMatchSysattr" $  do
    useAsCString sysattr $ \ c_sysattr ->    do
      case mvalue of
        Nothing    -> c_addNoMatchSysattr enumerate c_sysattr nullPtr
        Just value -> do
          useAsCString value $ \ c_value -> do
            c_addNoMatchSysattr enumerate c_sysattr c_value

foreign import ccall unsafe "udev_enumerate_add_match_property"
  c_addMatchProperty :: Enumerate -> CString -> CString -> IO CInt

-- | Match only devices with a certain property.
addMatchProperty :: Enumerate  -- ^ context
                 -> ByteString -- ^ filter for a property of the
                               -- device to include in the list
                 -> ByteString -- ^ value of the property
                 -> IO ()
addMatchProperty enumerate prop value = do
  throwErrnoIf_ (< 0) "addMatchProperty" $ do
    useAsCString prop $ \ c_prop -> do
      useAsCString value $ \ c_value -> do
        c_addMatchProperty enumerate c_prop c_value

foreign import ccall unsafe "udev_enumerate_add_match_tag"
  c_addMatchTag :: Enumerate -> CString -> IO CInt


-- | Match only devices with a certain tag.
addMatchTag :: Enumerate  -- ^ context
            -> ByteString -- ^ filter for a tag of the device to
                          -- include in the list
            -> IO ()
addMatchTag enumerate tag = do
  throwErrnoIf_ (< 0) "addMatchTag" $ do
    useAsCString tag $ \ c_tag -> do
      c_addMatchTag enumerate c_tag

foreign import ccall unsafe "udev_enumerate_add_match_parent"
  c_addMatchParent :: Enumerate -> Device -> IO CInt

-- | Return the devices on the subtree of one given device. The parent
-- itself is included in the list.
--
-- A reference for the device is held until the udev_enumerate context
-- is cleaned up.
--
addMatchParent :: Enumerate -- ^ context
               -> Device    -- ^ parent device where to start searching
               -> IO ()     -- ^ can throw exception
addMatchParent enumerate dev = do
  throwErrnoIf_ (< 0) "addMatchParent" $ do
    c_addMatchParent enumerate dev

foreign import ccall unsafe "udev_enumerate_add_match_is_initialized"
  c_addMatchIsInitialized :: Enumerate -> IO CInt

-- | Match only devices which udev has set up already.
addMatchIsInitialized :: Enumerate -> IO ()
addMatchIsInitialized enumerate = do
  throwErrnoIfMinus1_ "addMatchIsInitialized" $ do
    c_addMatchIsInitialized enumerate

foreign import ccall unsafe "udev_enumerate_add_match_sysname"
  c_addMatchSysname :: Enumerate -> CString -> IO CInt

-- | Match only devices with a given \/sys device name.
addMatchSysname :: Enumerate  -- ^ context
                -> ByteString -- ^ filter for the name of the device
                              -- to include in the list
                -> IO ()      -- ^ can throw exception
addMatchSysname enumerate sysName = do
  throwErrnoIfMinus1_ "addMatchSysname" $ do
    useAsCString sysName $
      c_addMatchSysname enumerate

foreign import ccall unsafe "udev_enumerate_add_syspath"
  c_addSyspath :: Enumerate -> CString -> IO CInt

-- | Add a device to the list of devices, to retrieve it back sorted
-- in dependency order.
--
addSyspath :: Enumerate   -- ^ context
           -> RawFilePath -- ^ path of a device
           -> IO ()       -- ^ can throw exception
addSyspath enumerate syspath = do
  throwErrnoIf_ (< 0) "addSyspath" $ do
    useAsCString syspath $ \ c_syspath -> do
      c_addSyspath enumerate c_syspath

{-----------------------------------------------------------------------
--  Scan
-----------------------------------------------------------------------}

foreign import ccall unsafe "udev_enumerate_scan_devices"
  c_scanDevices :: Enumerate -> IO CInt

-- | Scan \/sys for all devices which match the given filters.
scanDevices :: Enumerate -> IO ()
scanDevices = throwErrnoIfMinus1_ "scanDevices" . c_scanDevices

foreign import ccall unsafe "udev_enumerate_scan_subsystems"
  c_scanSubsystems :: Enumerate -> IO CInt

-- | Scan \/sys for all devices which match the given filters.
scanSubsystems :: Enumerate -> IO ()
scanSubsystems = throwErrnoIfMinus1_ "scanSubsystems" . c_scanSubsystems

{-----------------------------------------------------------------------
--  Query
-----------------------------------------------------------------------}

foreign import ccall unsafe "udev_enumerate_get_list_entry"
  c_getListEntry :: Enumerate -> IO List

-- | Get the first entry of the sorted list of device paths.
getListEntry :: Enumerate -> IO List
getListEntry = c_getListEntry
{-# INLINE getListEntry #-}