{-# LINE 1 "System/HIDAPI.hsc" #-}
{-# LANGUAGE ForeignFunctionInterface, DeriveDataTypeable, DeriveGeneric #-}



module System.HIDAPI
  ( System.HIDAPI.init, exit, withHIDAPI
  , enumerate, enumerateAll
  , open, openPath, openDeviceInfo
  , close
  , System.HIDAPI.read
  , System.HIDAPI.write
  , getSerialNumberString
  , System.HIDAPI.error
  , HIDAPIException(HIDAPIException)
  , Device()
  , DeviceInfo (..)
  , DevicePath
  , VendorID
  , ProductID
  , ReleaseNumber
  , SerialNumber
  , InterfaceNumber
  ) where

import Control.Applicative
import Control.DeepSeq
import Control.DeepSeq.Generics
import Control.Exception
import Control.Monad
import Data.ByteString
import Data.Maybe
import Data.Typeable
import Data.Data
import Foreign
import Foreign.C.Types
import Foreign.C.String
import GHC.Generics (Generic)

type Hid_Device_Ptr = Ptr () -- used where hid_device* is used on C side
newtype Device = Device Hid_Device_Ptr

data DeviceInfoInternal = DeviceInfoInternal
  { _path :: CString
  , _vendorId :: CUShort
  , _productId :: CUShort
  , _serialNumber :: CWString
  , _releaseNumber :: CUShort
  , _manufacturerString :: CWString
  , _productString :: CWString
  , _usagePage :: CUShort
  , _usage :: CUShort
  , _interfaceNumber :: CInt
  , _next :: Ptr DeviceInfoInternal
  } deriving (Show)



-- | Note: This is currently a read-only instance. `poke` is not yet implemented.
instance Storable DeviceInfoInternal where
  alignment _ = 8
{-# LINE 61 "System/HIDAPI.hsc" #-}
  sizeOf _ = (64)
{-# LINE 62 "System/HIDAPI.hsc" #-}
  peek p = DeviceInfoInternal <$>
    (\hsc_ptr -> peekByteOff hsc_ptr 0) p <*>
{-# LINE 64 "System/HIDAPI.hsc" #-}
    (\hsc_ptr -> peekByteOff hsc_ptr 8) p <*>
{-# LINE 65 "System/HIDAPI.hsc" #-}
    (\hsc_ptr -> peekByteOff hsc_ptr 10) p <*>
{-# LINE 66 "System/HIDAPI.hsc" #-}
    (\hsc_ptr -> peekByteOff hsc_ptr 16) p <*>
{-# LINE 67 "System/HIDAPI.hsc" #-}
    (\hsc_ptr -> peekByteOff hsc_ptr 24) p <*>
{-# LINE 68 "System/HIDAPI.hsc" #-}
    (\hsc_ptr -> peekByteOff hsc_ptr 32) p <*>
{-# LINE 69 "System/HIDAPI.hsc" #-}
    (\hsc_ptr -> peekByteOff hsc_ptr 40) p <*>
{-# LINE 70 "System/HIDAPI.hsc" #-}
    (\hsc_ptr -> peekByteOff hsc_ptr 48) p <*>
{-# LINE 71 "System/HIDAPI.hsc" #-}
    (\hsc_ptr -> peekByteOff hsc_ptr 50) p <*>
{-# LINE 72 "System/HIDAPI.hsc" #-}
    (\hsc_ptr -> peekByteOff hsc_ptr 52) p <*>
{-# LINE 73 "System/HIDAPI.hsc" #-}
    (\hsc_ptr -> peekByteOff hsc_ptr 56) p
{-# LINE 74 "System/HIDAPI.hsc" #-}

-- Aliases for writing better type signatures and haddocks.
type DevicePath = String
type VendorID = Word16
type ProductID = Word16
type ReleaseNumber = Word16
type SerialNumber = String
type InterfaceNumber = Int

data DeviceInfo = DeviceInfo
  { path :: DevicePath
  , vendorId :: VendorID
  , productId :: ProductID
  , serialNumber :: Maybe SerialNumber
  , releaseNumber :: ReleaseNumber
  , manufacturerString :: Maybe String
  , productString :: Maybe String
  , usagePage :: Word16
  , usage :: Word16
  , interfaceNumber :: InterfaceNumber
  } deriving (Show, Generic)

instance NFData DeviceInfo where rnf = genericRnf

peekOptString :: Ptr CWchar -> IO (Maybe String)
peekOptString p
  | p == nullPtr = return Nothing
  | otherwise = Just <$> peekCWString p

fromInternalDeviceInfo :: DeviceInfoInternal -> IO DeviceInfo
fromInternalDeviceInfo di = DeviceInfo <$>
  peekCString (_path di) <*>
  pure (fromIntegral $ _vendorId di) <*>
  pure (fromIntegral $ _productId di) <*>
  peekOptString (_serialNumber di) <*>
  pure (fromIntegral $ _releaseNumber di) <*>
  peekOptString (_manufacturerString di) <*>
  peekOptString (_productString di) <*>
  pure (fromIntegral $ _usagePage di) <*>
  pure (fromIntegral $ _usage di) <*>
  pure (fromIntegral $ _interfaceNumber di)

foreign import ccall unsafe "hidapi/hidapi.h hid_error"
  hid_error :: Hid_Device_Ptr -> IO CWString

data HIDAPIException = HIDAPIException String String
  deriving (Data, Typeable, Generic)

instance Show HIDAPIException where
  showsPrec _ (HIDAPIException a c) = showString a . showString ": " . showString c

instance Exception HIDAPIException

instance NFData HIDAPIException where rnf = genericRnf

-- TODO As of https://github.com/signal11/hidapi/issues/123 it is unclear
--      whether the passed in pointer may be NULL, and if hid_error ever
--      returns something useful.
error :: Device -> IO (Maybe String)
error (Device devicePtr) = do
  e <- hid_error devicePtr
  if e == nullPtr
    then return Nothing
    else do
      es <- peekCWString e
      free e
      return (Just es)

check :: Bool -> String -> String -> IO ()
check c msg reason = unless c $ throwIO $ HIDAPIException msg reason

-- Device is only used to obtain a more detailed error if the condition is
-- false and device is not a NULL pointer.
checkWithHidError :: Bool -> Device -> String -> String -> IO ()
checkWithHidError c dev@(Device devPtr) msg defaultReason = unless c $ do
  reason <- if devPtr /= nullPtr
              then fromMaybe defaultReason <$> System.HIDAPI.error dev
              else return defaultReason
  throwIO $ HIDAPIException msg reason

foreign import ccall unsafe "hidapi/hidapi.h hid_init"
  hid_init :: IO CInt

init :: IO ()
init = do
  r <- hid_init
  check (r == 0) "HIDAPI initialization failed" "hid_init /= 0"

foreign import ccall unsafe "hidapi/hidapi.h hid_exit"
  hid_exit :: IO CInt

exit :: IO ()
exit = do
  r <- hid_exit
  check (r == 0) "HIDAPI shutdown failed" "hid_exit /= 0"

withHIDAPI :: IO a -> IO a
withHIDAPI = bracket_ System.HIDAPI.init exit

foreign import ccall unsafe "hidapi/hidapi.h hid_enumerate"
  hid_enumerate :: CUShort -> CUShort -> IO (Ptr DeviceInfoInternal)

foreign import ccall unsafe "hidapi/hidapi.h hid_free_enumeration"
  hid_free_enumeration :: Ptr DeviceInfoInternal -> IO ()

parseEnumeration :: Ptr DeviceInfoInternal -> IO [ DeviceInfo ]
parseEnumeration dip
  | dip == nullPtr = return []
  | otherwise = do
    idi <- peek dip
    di <- fromInternalDeviceInfo idi
    dis <- parseEnumeration (_next idi)
    return (di : dis)

enumerate :: Maybe VendorID -> Maybe ProductID -> IO [ DeviceInfo ]
enumerate m'vendorId m'productId = do
  dip <- hid_enumerate (maybe 0 fromIntegral m'vendorId) (maybe 0 fromIntegral m'productId)
  -- Docs say hid_enumerate also returns "NULL in the case of failure", but
  -- this is indistinguishable from "no devices". :/
  if dip == nullPtr
    then return []
    else do
      dis <- parseEnumeration dip
      hid_free_enumeration dip
      return dis

enumerateAll :: IO [ DeviceInfo ]
enumerateAll = enumerate Nothing Nothing

foreign import ccall unsafe "hidapi/hidapi.h hid_open"
  hid_open :: CUShort -> CUShort -> CWString -> IO Hid_Device_Ptr

open :: VendorID -> ProductID -> Maybe SerialNumber -> IO Device
open vendor_id product_id serial = do
  let vid = fromIntegral vendor_id
  let pid = fromIntegral product_id
  dev@(Device dp) <- Device <$> case serial of
    Nothing -> hid_open vid pid nullPtr
    Just sn -> withCWString sn (hid_open vid pid)
  checkWithHidError (dp /= nullPtr) dev "Device open (by vendor/product id) failed" "hid_open returned NULL"
  return dev

foreign import ccall unsafe "hidapi/hidapi.h hid_open_path"
  hid_open_path :: CString -> IO Hid_Device_Ptr

openPath :: DevicePath -> IO Device
openPath p = do
  dev@(Device dp) <- Device <$> withCString p hid_open_path
  checkWithHidError (dp /= nullPtr) dev "Device open (by path) failed" "hid_open returned NULL"
  return dev

openDeviceInfo :: DeviceInfo -> IO Device
openDeviceInfo
  = openPath . path

foreign import ccall unsafe "hidapi/hidapi.h hid_close"
  close :: Device -> IO ()

foreign import ccall unsafe "hidapi/hidapi.h hid_read"
  hid_read :: Device -> Ptr CChar -> CSize -> IO CInt

foreign import ccall unsafe "hidapi/hidapi.h hid_write"
  hid_write :: Device -> Ptr CChar -> CSize -> IO CInt

read :: Device -> Int -> IO ByteString
read dev n = allocaBytes n $ \b -> do
  n' <- hid_read dev b (fromIntegral n)
  checkWithHidError (n' /= -1) dev "Read failed" "hid_read returned -1"
  packCStringLen ( b, fromIntegral n' )

write :: Device -> ByteString -> IO Int
write dev b = do
  n' <- useAsCStringLen b $ \(cs, csLen) -> hid_write dev cs (fromIntegral csLen)
  checkWithHidError (n' /= -1) dev "Write failed" "hid_write returned -1"
  return $ fromIntegral n'

foreign import ccall unsafe "hidapi/hidapi.h hid_get_serial_number_string"
  hid_get_serial_number_string :: Device -> CWString -> CSize -> IO CInt

_SERIAL_NUMBER_MAX_LENGTH :: Int
_SERIAL_NUMBER_MAX_LENGTH = 32768

getSerialNumberString :: Device -> IO SerialNumber
getSerialNumberString dev = do
  let bs = _SERIAL_NUMBER_MAX_LENGTH * sizeOf (undefined :: CWchar)
  bracket (mallocBytes bs) free $ \b -> do
    n' <- hid_get_serial_number_string dev b (fromIntegral _SERIAL_NUMBER_MAX_LENGTH)
    checkWithHidError (n' /= -1) dev "Getting serial number failed" "hid_get_serial_number_string returned -1"
    peekCWString b