initPhone :: Config -> IO ()
initPhone Config{..} = liftPJ . evalContT $ do
    lift . FFI.check CreateLib
        $ FFI.createPjSua
    lift . FFI.check Initialization
        =<< FFI.initializePjSua <$> withPj <*> withLog <*> withMedia
    lift . FFI.check Transport
        =<< FFI.createTransport FFI.udpTransport <$> withTransport <*> pure nullPtr
    lift . FFI.check Start
        $ FFI.pjsuaStart
    lift $ setCodecs
  where
    Handlers{..} = handlers
    Logging{..} = logging

    withTransport = ContT FFI.withTransportConfig

    withPj = do
        pjCfg <- ContT FFI.withPjConfig
        lift $ do
            whenJust onCallStateChange
                $ liftIO . FFI.toOnCallState . onCallState
                >=> FFI.setOnCallStateCallback pjCfg
            whenJust onCallTransactionStateChange
                $ liftIO . FFI.toOnCallTransactionState . onCallTransactionState
                >=> FFI.setOnCallTransactionStateCallback pjCfg
            whenJust onIncomingCall
                $ liftIO . FFI.toOnIncomingCall . onIncCall
                >=> FFI.setOnIncomingCallCallback pjCfg
            whenJust onRegistrationStateChange
                $ liftIO . FFI.toOnRegistrationState
                >=> FFI.setOnRegistrationStateCallback pjCfg
            whenJust onRegistrationStarted
                $ liftIO . FFI.toOnRegistrationStarted . onRegStarted
                >=> FFI.setOnRegistrationStartedCallback pjCfg
            whenJust onMediaStateChange
                $ liftIO . FFI.toOnMediaState
                >=> FFI.setOnMediaStateCallback pjCfg
        pure pjCfg

    withLog = do
        logFile <- ContT $ withMaybePjString logFilename
        logCfg <- ContT $ FFI.withLoggingConfig
        lift $ do
            whenJust logMsgLogging $ FFI.setMsgLogging logCfg . fromBool
            whenJust logLevel $ FFI.setLevel logCfg . fromIntegral
            whenJust logConsoleLevel $ FFI.setConsoleLevel logCfg . fromIntegral
            whenJust logFile $ FFI.setLogFilename logCfg
        pure logCfg

    withMedia = do
        mediaCfg <- ContT FFI.withMediaConfig
        -- When pjproject is built without resampling (as it happens to be
        -- in Debian), we want to fix the rate and codecs so that we don't
        -- crash in resampler creation.
        lift $ FFI.setMediaConfigClockRate mediaCfg 8000
        pure mediaCfg

    setCodecs = do
        FFI.withPjStringPtr "PCMU" (`FFI.codecSetPriority` 255)
        FFI.withPjStringPtr "PCMA" (`FFI.codecSetPriority` 255)

    onCallState f callId event =
        toEvent event >>= f callId

    onCallTransactionState f callId _ event =
        toEvent event >>= f callId

    onIncCall f acc callId _ = f acc callId

    onRegStarted f acc p = f acc $ fromIntegral p

    whenJust m op = maybe (pure ()) op m

    withMaybePjString = maybe ($ Nothing) ((. (. Just)) . FFI.withPjString)
initPhone :: Config -> IO ()
initPhone Config{..} = liftPJ . evalContT $ do
    lift . FFI.check CreateLib
        $ FFI.createPjSua
    lift . FFI.check Initialization
        =<< FFI.initializePjSua <$> withPj <*> withLog <*> withMedia
    lift . FFI.check Transport
        =<< FFI.createTransport FFI.udpTransport <$> withTransport <*> pure nullPtr
    lift . FFI.check Start
        $ FFI.pjsuaStart
    lift $ setCodecs
  where
    Handlers{..} = handlers
    Logging{..} = logging

    withTransport = ContT FFI.withTransportConfig

    withPj = do
        pjCfg <- ContT FFI.withPjConfig
        lift $ do
            whenJust onCallStateChange
                $ liftIO . FFI.toOnCallState . onCallState
                >=> FFI.setOnCallStateCallback pjCfg
            whenJust onCallTransactionStateChange
                $ liftIO . FFI.toOnCallTransactionState . onCallTransactionState
                >=> FFI.setOnCallTransactionStateCallback pjCfg
            whenJust onIncomingCall
                $ liftIO . FFI.toOnIncomingCall . onIncCall
                >=> FFI.setOnIncomingCallCallback pjCfg
            whenJust onRegistrationStateChange
                $ liftIO . FFI.toOnRegistrationState
                >=> FFI.setOnRegistrationStateCallback pjCfg
            whenJust onRegistrationStarted
                $ liftIO . FFI.toOnRegistrationStarted . onRegStarted
                >=> FFI.setOnRegistrationStartedCallback pjCfg
            whenJust onMediaStateChange
                $ liftIO . FFI.toOnMediaState
                >=> FFI.setOnMediaStateCallback pjCfg
        pure pjCfg

    withLog = do
        logFile <- ContT $ withMaybePjString logFilename
        logCfg <- ContT $ FFI.withLoggingConfig
        lift $ do
            whenJust logMsgLogging $ FFI.setMsgLogging logCfg . fromBool
            whenJust logLevel $ FFI.setLevel logCfg . fromIntegral
            whenJust logConsoleLevel $ FFI.setConsoleLevel logCfg . fromIntegral
            whenJust logFile $ FFI.setLogFilename logCfg
        pure logCfg

    withMedia = do
        mediaCfg <- ContT FFI.withMediaConfig
        -- When pjproject is built without resampling (as it happens to be
        -- in Debian), we want to fix the rate and codecs so that we don't
        -- crash in resampler creation.
        lift $ FFI.setMediaConfigClockRate mediaCfg 8000
        pure mediaCfg

    setCodecs = do
        FFI.withPjStringPtr "PCMU" (`FFI.codecSetPriority` 255)
        FFI.withPjStringPtr "PCMA" (`FFI.codecSetPriority` 255)

    onCallState f callId event =
        toEvent event >>= f callId

    onCallTransactionState f callId _ event =
        toEvent event >>= f callId

    onIncCall f acc callId _ = f acc callId

    onRegStarted f acc p = f acc $ fromIntegral p

    whenJust m op = maybe (pure ()) op m

    withMaybePjString = maybe ($ Nothing) ((. (. Just)) . FFI.withPjString)
initPhone Config{..} = liftPJ . evalContT $ do
    lift . FFI.check CreateLib
        $ FFI.createPjSua
    lift . FFI.check Initialization
        =<< FFI.initializePjSua <$> withPj <*> withLog <*> withMedia
    lift . FFI.check Transport
        =<< FFI.createTransport FFI.udpTransport <$> withTransport <*> pure nullPtr
    lift . FFI.check Start
        $ FFI.pjsuaStart
    lift $ setCodecs
  where
    Handlers{..} = handlers
    Logging{..} = logging

    withTransport = ContT FFI.withTransportConfig

    withPj = do
        pjCfg <- ContT FFI.withPjConfig
        lift $ do
            whenJust onCallStateChange
                $ liftIO . FFI.toOnCallState . onCallState
                >=> FFI.setOnCallStateCallback pjCfg
            whenJust onCallTransactionStateChange
                $ liftIO . FFI.toOnCallTransactionState . onCallTransactionState
                >=> FFI.setOnCallTransactionStateCallback pjCfg
            whenJust onIncomingCall
                $ liftIO . FFI.toOnIncomingCall . onIncCall
                >=> FFI.setOnIncomingCallCallback pjCfg
            whenJust onRegistrationStateChange
                $ liftIO . FFI.toOnRegistrationState
                >=> FFI.setOnRegistrationStateCallback pjCfg
            whenJust onRegistrationStarted
                $ liftIO . FFI.toOnRegistrationStarted . onRegStarted
                >=> FFI.setOnRegistrationStartedCallback pjCfg
            whenJust onMediaStateChange
                $ liftIO . FFI.toOnMediaState
                >=> FFI.setOnMediaStateCallback pjCfg
        pure pjCfg

    withLog = do
        logFile <- ContT $ withMaybePjString logFilename
        logCfg <- ContT $ FFI.withLoggingConfig
        lift $ do
            whenJust logMsgLogging $ FFI.setMsgLogging logCfg . fromBool
            whenJust logLevel $ FFI.setLevel logCfg . fromIntegral
            whenJust logConsoleLevel $ FFI.setConsoleLevel logCfg . fromIntegral
            whenJust logFile $ FFI.setLogFilename logCfg
        pure logCfg

    withMedia = do
        mediaCfg <- ContT FFI.withMediaConfig
        -- When pjproject is built without resampling (as it happens to be
        -- in Debian), we want to fix the rate and codecs so that we don't
        -- crash in resampler creation.
        lift $ FFI.setMediaConfigClockRate mediaCfg 8000
        pure mediaCfg

    setCodecs = do
        FFI.withPjStringPtr "PCMU" (`FFI.codecSetPriority` 255)
        FFI.withPjStringPtr "PCMA" (`FFI.codecSetPriority` 255)

    onCallState f callId event =
        toEvent event >>= f callId

    onCallTransactionState f callId _ event =
        toEvent event >>= f callId

    onIncCall f acc callId _ = f acc callId

    onRegStarted f acc p = f acc $ fromIntegral p

    whenJust m op = maybe (pure ()) op m

    withMaybePjString = maybe ($ Nothing) ((. (. Just)) . FFI.withPjString)