{- git-annex assistant XMPP configuration
 -
 - Copyright 2012 Joey Hess <id@joeyh.name>
 -
 - Licensed under the GNU AGPL version 3 or higher.
 -}

{-# LANGUAGE TypeFamilies, QuasiQuotes, TemplateHaskell, OverloadedStrings, FlexibleContexts #-}
{-# LANGUAGE CPP #-}

module Assistant.WebApp.Configurators.XMPP where

import Assistant.WebApp.Common
import Assistant.WebApp.Notifications
import Utility.NotificationBroadcaster
#ifdef WITH_XMPP
import qualified Remote
import Assistant.XMPP.Client
import Assistant.XMPP.Buddies
import Assistant.Types.Buddies
import Assistant.NetMessager
import Assistant.Alert
import Assistant.DaemonStatus
import Assistant.WebApp.RepoList
import Assistant.WebApp.Configurators
import Assistant.XMPP
import qualified Git.Remote.Remove
import Remote.List
import Creds
#endif

#ifdef WITH_XMPP
import Network.Protocol.XMPP
import Network
import qualified Data.Text as T
#endif

{- When appropriate, displays an alert suggesting to configure a cloud repo
 - to suppliment an XMPP remote. -}
checkCloudRepos :: UrlRenderer -> Remote -> Assistant ()
#ifdef WITH_XMPP
checkCloudRepos urlrenderer r =
	unlessM (syncingToCloudRemote <$> getDaemonStatus) $ do
		buddyname <- getBuddyName $ Remote.uuid r
		button <- mkAlertButton True "Add a cloud repository" urlrenderer $
			NeedCloudRepoR $ Remote.uuid r
		void $ addAlert $ cloudRepoNeededAlert buddyname button
#else
checkCloudRepos _ _ = noop
#endif

#ifdef WITH_XMPP
{- Returns the name of the friend corresponding to a
 - repository's UUID, but not if it's our name. -}
getBuddyName :: UUID -> Assistant (Maybe String)
getBuddyName u = go =<< getclientjid
  where
	go Nothing = return Nothing
	go (Just myjid) = (T.unpack . buddyName <$>)
		. headMaybe 
		. filter (\j -> baseJID j /= baseJID myjid)
		. map fst
		. filter (\(_, r) -> Remote.uuid r == u)
		<$> getXMPPRemotes
	getclientjid = maybe Nothing parseJID . xmppClientID
		<$> getDaemonStatus
#endif

getNeedCloudRepoR :: UUID -> Handler Html
#ifdef WITH_XMPP
getNeedCloudRepoR for = page "Cloud repository needed" (Just Configuration) $ do
	buddyname <- liftAssistant $ getBuddyName for
	$(widgetFile "configurators/xmpp/needcloudrepo")
#else
getNeedCloudRepoR _ = xmppPage $
	$(widgetFile "configurators/xmpp/disabled")
#endif

getXMPPConfigR :: Handler Html
getXMPPConfigR = postXMPPConfigR

postXMPPConfigR :: Handler Html
postXMPPConfigR = xmppform DashboardR

getXMPPConfigForPairFriendR :: Handler Html
getXMPPConfigForPairFriendR = postXMPPConfigForPairFriendR

postXMPPConfigForPairFriendR :: Handler Html
postXMPPConfigForPairFriendR = xmppform StartXMPPPairFriendR

getXMPPConfigForPairSelfR :: Handler Html
getXMPPConfigForPairSelfR = postXMPPConfigForPairSelfR

postXMPPConfigForPairSelfR :: Handler Html
postXMPPConfigForPairSelfR = xmppform StartXMPPPairSelfR

xmppform :: Route WebApp -> Handler Html
#ifdef WITH_XMPP
xmppform next = xmppPage $ do
	((result, form), enctype) <- liftH $ do
		oldcreds <- liftAnnex getXMPPCreds
		runFormPostNoToken $ renderBootstrap3 bootstrapFormLayout $ xmppAForm $
			creds2Form <$> oldcreds
	let showform problem = $(widgetFile "configurators/xmpp")
	case result of
		FormSuccess f -> either (showform . Just) (liftH . storecreds)
			=<< liftIO (validateForm f)
		_ -> showform Nothing
  where
	storecreds creds = do
		void $ liftAnnex $ setXMPPCreds creds
		liftAssistant notifyNetMessagerRestart
		redirect next
#else
xmppform _ = xmppPage $
	$(widgetFile "configurators/xmpp/disabled")
#endif

{- Called by client to get a list of buddies.
 -
 - Returns a div, which will be inserted into the calling page.
 -}
getBuddyListR :: NotificationId -> Handler Html
getBuddyListR nid = do
	waitNotifier getBuddyListBroadcaster nid

	p <- widgetToPageContent buddyListDisplay
	withUrlRenderer $ [hamlet|^{pageBody p}|]

buddyListDisplay :: Widget
buddyListDisplay = do
	autoUpdate ident NotifierBuddyListR (10 :: Int) (10 :: Int)
#ifdef WITH_XMPP
	myjid <- liftAssistant $ xmppClientID <$> getDaemonStatus
	let isself (BuddyKey b) = Just b == myjid
	buddies <- liftAssistant $ do
		pairedwith <- map fst <$> getXMPPRemotes
		catMaybes . map (buddySummary pairedwith)
			<$> (getBuddyList <<~ buddyList)
	$(widgetFile "configurators/xmpp/buddylist")
#else
	noop
#endif
  where
	ident = "buddylist"

#ifdef WITH_XMPP

getXMPPRemotes :: Assistant [(JID, Remote)]
getXMPPRemotes = catMaybes . map pair . filter Remote.isXMPPRemote . syncGitRemotes
	<$> getDaemonStatus
  where
	pair r = maybe Nothing (\jid -> Just (jid, r)) $
		parseJID $ getXMPPClientID r

data XMPPForm = XMPPForm
	{ formJID :: Text
	, formPassword :: Text }

creds2Form :: XMPPCreds -> XMPPForm
creds2Form c = XMPPForm (xmppJID c) (xmppPassword c)

xmppAForm :: (Maybe XMPPForm) -> MkAForm XMPPForm
xmppAForm d = XMPPForm
	<$> areq jidField (bfs "Jabber address") (formJID <$> d)
	<*> areq passwordField (bfs "Password") Nothing

jidField :: MkField Text
jidField = checkBool (isJust . parseJID) bad textField
  where
	bad :: Text
	bad = "This should look like an email address.."

validateForm :: XMPPForm -> IO (Either String XMPPCreds)
validateForm f = do
	let jid = fromMaybe (error "bad JID") $ parseJID (formJID f)
	let username = fromMaybe "" (strNode <$> jidNode jid)
	testXMPP $ XMPPCreds
		{ xmppUsername = username
		, xmppPassword = formPassword f
		, xmppHostname = T.unpack $ strDomain $ jidDomain jid
		, xmppPort = 5222
		, xmppJID = formJID f
		}

testXMPP :: XMPPCreds -> IO (Either String XMPPCreds)
testXMPP creds = do
	(good, bad) <- partition (either (const False) (const True) . snd) 
		<$> connectXMPP creds (const noop)
	case good of
		(((h, PortNumber p), _):_) -> return $ Right $ creds
			{ xmppHostname = h
			, xmppPort = fromIntegral p
			}
		(((h, _), _):_) -> return $ Right $ creds
			{ xmppHostname = h
			}
		_ -> return $ Left $ intercalate "; " $ map formatlog bad
  where
	formatlog ((h, p), Left e) = "host " ++ h ++ ":" ++ showport p ++ " failed: " ++ show e
	formatlog _ = ""

	showport (PortNumber n) = show n
	showport (Service s) = s
	showport (UnixSocket s) = s
#endif

getDisconnectXMPPR :: Handler Html
getDisconnectXMPPR = do
#ifdef WITH_XMPP
	rs <- filter Remote.isXMPPRemote . syncRemotes
		<$> liftAssistant getDaemonStatus
	liftAnnex $ do
		mapM_ (inRepo . Git.Remote.Remove.remove . Remote.name) rs
		void remoteListRefresh
		removeCreds xmppCredsFile
	liftAssistant $ do
		updateSyncRemotes
		notifyNetMessagerRestart
	redirect DashboardR
#else
	xmppPage $ $(widgetFile "configurators/xmpp/disabled")
#endif

xmppPage :: Widget -> Handler Html
xmppPage = page "Jabber" (Just Configuration)
