{-# LANGUAGE RankNTypes #-}

module System.Win32.DHCP.Reservation
    ( Mapping (..)
    , Reservation (..)
    , reservation
    ) where

import Data.Ip
import Data.Mac
import Import
import System.Win32.DHCP.CLIENT_UID
import System.Win32.DHCP.DhcpStructure
import System.Win32.DHCP.Types (ClientType)

-- | A Reservation guarantees that a device with a given Mac address will
-- always be assigned to a particular IP address. A reservation is not the
-- same thing as a lease, and there are separate calls to work with both
-- objects.
--
-- This type corresponds to MSDN's DHCP_IP_RESERVATION_V4 structure.
--
-- > typedef struct _DHCP_IP_RESERVATION_V4 {
-- >   DHCP_IP_ADDRESS ReservedIpAddress;
-- >   DHCP_CLIENT_UID *ReservedForClient;
-- >   BYTE            bAllowedClientTypes;
-- > } DHCP_IP_RESERVATION_V4, *LPDHCP_IP_RESERVATION_V4;
data Reservation = Reservation
    { reservationMapping :: !Mapping
    , reservationType    :: !ClientType
    } deriving (Eq)

-- | A mapping between an IP and a MAC address. Each IP number may map to
-- only one MAC address, and each MAC address may map to only one IP number.
--
-- This is a separate type from `Reservation` for practical reasons. When
-- writing software to work with a DHCP server, `Reservation`'s
-- `ClientType` field is often not important. Without the `Mapping` type
-- defined here it would often be necessary to define a custom type in
-- each project.
data Mapping = Mapping
    { mappingMac :: !Mac
    , mappingIp  :: !Ip
    } deriving (Eq, Ord)

reservation :: DhcpStructure Reservation
reservation = DhcpStructure
    { peekDhcp = peekReservation
    , freeDhcpChildren = freeReservation
    , withDhcp' = withReservation'
    -- I arrived at this size through experimentation. It seems like the
    -- size should be 12, but that is not the case.
    , sizeDhcp = 10
    }

peekReservation :: Ptr Reservation -> IO Reservation
peekReservation ptr = do
    pCuid <- peek $ ppCuid ptr
    mac <- macCuidDrop5 <$> peekDhcp clientUid pCuid
    addr <- peek pAddress
    clientType <- peekByteOff (castPtr ptr) 8
    return $ Reservation (Mapping mac addr) clientType
  where
    pAddress = castPtr ptr :: Ptr Ip

freeReservation :: (forall x. Ptr x -> IO ()) -> Ptr Reservation -> IO ()
freeReservation freefunc ptr = do
    freeDhcp clientUid freefunc `scrubbing_` ppCuid ptr

ppCuid :: Ptr Reservation -> Ptr (Ptr CLIENT_UID)
ppCuid p = plusPtr p 4

pClientType :: Ptr Reservation -> Ptr ClientType
pClientType p = plusPtr p 8

withReservation' :: Reservation -> Ptr Reservation
    -> IO r -> IO r
withReservation' (Reservation (Mapping mac address) clientType) ptr f =
    withMac mac $ \pCuid -> do
    poke (castPtr ptr) address
    ppCuid ptr `poke` pCuid
    pClientType ptr `poke` clientType
    f