{-# LANGUAGE TypeFamilies, FlexibleContexts, PackageImports #-}

module SaslClient (SaslState(..), sasl) where

import "monads-tf" Control.Monad.State
import "monads-tf" Control.Monad.Error
import Data.Maybe
import Data.List
import Data.Pipe

import qualified Data.ByteString as BS

import Network.Sasl
import qualified Network.Sasl.Plain.Client as Pln
import qualified Network.Sasl.External.Client as Ext
import qualified Network.Sasl.DigestMd5.Client as DM5
import qualified Network.Sasl.ScramSha1.Client as SS1
import XmppType

saslClients :: (
	MonadState m, SaslState (StateType m),
	MonadError m, Error (ErrorType m) ) => [(
	BS.ByteString,
	(Bool, Pipe (Either Success BS.ByteString) BS.ByteString m ()) )]
saslClients = [DM5.sasl, SS1.sasl, Pln.sasl, Ext.sasl]

sasl :: (Monad m,
		MonadState m, SaslState (StateType m),
		MonadError m, Error (ErrorType m)
	) => BS.ByteString -> Pipe Xmpp Xmpp m ()
sasl n = saslPipe . fromJust $ find ((== n) . fst) saslClients

saslPipe :: (Monad m, MonadState m, SaslState (StateType m)) => (
		BS.ByteString,
		(Bool, Pipe (Either Success BS.ByteString) BS.ByteString m ())
	) -> Pipe Xmpp Xmpp m ()
saslPipe m =
	inputScramSha1 =$= snd (snd m) =$= outputScramSha1 (fst (snd m)) (fst m)

inputScramSha1 :: Monad m => Pipe Xmpp (Either Success BS.ByteString) m ()
inputScramSha1 = await >>= \mc -> case mc of
	Just (SRChallenge c) -> yield (Right c) >> inputScramSha1
	Just (XCSaslSuccess d) -> yield . Left $ Network.Sasl.Success d
	_ -> error "inputScramSha1: bad"

outputScramSha1 :: Monad m =>
	Bool -> BS.ByteString -> Pipe BS.ByteString Xmpp m ()
outputScramSha1 ci mn = do
	if ci
	then await >>= maybe (return ()) (yield . XCAuth mn . Just)
	else yield $ XCAuth mn Nothing
	convert SRResponse
