-- | Keep track of pin-mode changes
registerPinMode :: IPin -> PinMode -> Arduino [Request]
registerPinMode p m = do
        -- first check that the requested mode is supported for this pin
        BoardCapabilities caps <- gets capabilities
        case p `M.lookup` caps of
          Nothing
             -> die ("Invalid access to unsupported pin: " ++ show p)
                    ("Available pins are: " : ["  " ++ show k | (k, _) <- M.toAscList caps])
          Just PinCapabilities{allowedModes}
            | m `notElem` map fst allowedModes
            -> die ("Invalid mode " ++ show m ++ " set for " ++ show p)
                   ["Supported modes for this pin are: " ++ unwords (if null allowedModes then ["NONE"] else map show allowedModes)]
          _ -> return ()
        -- see if there was a mode already set for this pin
        bs  <- gets boardState
        mbOldMode <- liftIO $ withMVar bs $ \bst ->
                                case p `M.lookup` pinStates bst of
                                  Nothing -> return Nothing -- completely new, register
                                  Just pd -> return $ Just $ pinMode pd
        -- depending on old/new mode, determine what actions to take
        let registerNewMode = modifyMVar_ bs $ \bst -> return bst{pinStates = M.insert p PinData{pinMode = m, pinValue = Nothing} (pinStates bst) }
        case mbOldMode of
          Nothing -> do liftIO registerNewMode
                        getModeActions p m
          Just m' | m == m' -> return []  -- no mode change, nothing to do
                  | True    -> do liftIO registerNewMode
                                  remActs <- getRemovalActions p m'
                                  addActs <- getModeActions p m
                                  return $ remActs ++ addActs

-- | A mode was removed from this pin, update internal state and determine any necessary actions to remove it
registerPinMode :: IPin -> PinMode -> Arduino [Request]
registerPinMode p m = do
        -- first check that the requested mode is supported for this pin
        BoardCapabilities caps <- gets capabilities
        case p `M.lookup` caps of
          Nothing
             -> die ("Invalid access to unsupported pin: " ++ show p)
                    ("Available pins are: " : ["  " ++ show k | (k, _) <- M.toAscList caps])
          Just PinCapabilities{allowedModes}
            | m `notElem` map fst allowedModes
            -> die ("Invalid mode " ++ show m ++ " set for " ++ show p)
                   ["Supported modes for this pin are: " ++ unwords (if null allowedModes then ["NONE"] else map show allowedModes)]
          _ -> return ()
        -- see if there was a mode already set for this pin
        bs  <- gets boardState
        mbOldMode <- liftIO $ withMVar bs $ \bst ->
                                case p `M.lookup` pinStates bst of
                                  Nothing -> return Nothing -- completely new, register
                                  Just pd -> return $ Just $ pinMode pd
        -- depending on old/new mode, determine what actions to take
        let registerNewMode = modifyMVar_ bs $ \bst -> return bst{pinStates = M.insert p PinData{pinMode = m, pinValue = Nothing} (pinStates bst) }
        case mbOldMode of
          Nothing -> do liftIO registerNewMode
                        getModeActions p m
          Just m' | m == m' -> return []  -- no mode change, nothing to do
                  | True    -> do liftIO registerNewMode
                                  remActs <- getRemovalActions p m'
                                  addActs <- getModeActions p m
                                  return $ remActs ++ addActs

-- | A mode was removed from this pin, update internal state and determine any necessary actions to remove it
registerPinMode p m = do
        -- first check that the requested mode is supported for this pin
        BoardCapabilities caps <- gets capabilities
        case p `M.lookup` caps of
          Nothing
             -> die ("Invalid access to unsupported pin: " ++ show p)
                    ("Available pins are: " : ["  " ++ show k | (k, _) <- M.toAscList caps])
          Just PinCapabilities{allowedModes}
            | m `notElem` map fst allowedModes
            -> die ("Invalid mode " ++ show m ++ " set for " ++ show p)
                   ["Supported modes for this pin are: " ++ unwords (if null allowedModes then ["NONE"] else map show allowedModes)]
          _ -> return ()
        -- see if there was a mode already set for this pin
        bs  <- gets boardState
        mbOldMode <- liftIO $ withMVar bs $ \bst ->
                                case p `M.lookup` pinStates bst of
                                  Nothing -> return Nothing -- completely new, register
                                  Just pd -> return $ Just $ pinMode pd
        -- depending on old/new mode, determine what actions to take
        let registerNewMode = modifyMVar_ bs $ \bst -> return bst{pinStates = M.insert p PinData{pinMode = m, pinValue = Nothing} (pinStates bst) }
        case mbOldMode of
          Nothing -> do liftIO registerNewMode
                        getModeActions p m
          Just m' | m == m' -> return []  -- no mode change, nothing to do
                  | True    -> do liftIO registerNewMode
                                  remActs <- getRemovalActions p m'
                                  addActs <- getModeActions p m
                                  return $ remActs ++ addActs

-- | A mode was removed from this pin, update internal state and determine any necessary actions to remove it