{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveAnyClass #-}

module Config where

import Prelude ()
import BasicPrelude
import Network.HostAndPort (maybeHostAndPort)
import System.IO.Unsafe (unsafePerformIO)
import Control.Error (headZ)

import qualified Network.Socket as Socket
import qualified Database.Redis as Redis
import qualified Dhall
import qualified Dhall.Core as Dhall
import qualified Network.Protocol.XMPP as XMPP

import Util
import qualified RedisURL

data ServerConfig = ServerConfig { host :: Socket.HostName, port :: Socket.PortNumber } deriving (Dhall.Generic, Dhall.FromDhall, Show)

data Redis = Redis {
	presence :: Redis.ConnectInfo,
	state :: Redis.ConnectInfo
} deriving (Dhall.Generic, Dhall.FromDhall, Show)

data Config = Config {
	componentJid :: XMPP.JID,
	server :: ServerConfig,
	secret :: Text,
	backend :: Text,
	did :: Text,
	registrationJid :: XMPP.JID,
	conferenceServers :: [Text],
	s5bListenOn :: [Socket.SockAddr],
	s5bAdvertise :: ServerConfig,
	jingleStore :: FilePath,
	jingleStoreURL :: Text,
	redis :: Redis,
	statsd :: ServerConfig,
	avatar :: Maybe FilePath
} deriving (Dhall.Generic, Dhall.FromDhall, Show)

instance Dhall.FromDhall XMPP.JID where
	autoWith _ = Dhall.Decoder {
			Dhall.extract = \(Dhall.TextLit (Dhall.Chunks _ txt)) ->
				maybe (Dhall.extractError $ s"Invalid JID") pure $ XMPP.parseJID txt,
			Dhall.expected = pure Dhall.Text
		}

instance Dhall.FromDhall Socket.PortNumber where
	autoWith _ = Dhall.Decoder {
			Dhall.extract = \(Dhall.NaturalLit nat) -> pure $ fromIntegral nat,
			Dhall.expected = pure Dhall.Natural
		}

instance Dhall.FromDhall Socket.SockAddr where
	autoWith _ = Dhall.Decoder {
			Dhall.extract = (\(Dhall.TextLit (Dhall.Chunks _ txt)) -> maybe (Dhall.extractError $ s"Invalid Socket Address") pure $ do
				Just (host, Just port) <- return $ maybeHostAndPort (textToString txt)
				-- This is not a great idea, but I'm lazy today and I really just want to parse IP addresses, which is a pure operation
				unsafePerformIO $ fmap (fmap Socket.addrAddress . headZ) $ Socket.getAddrInfo Nothing (Just host) (Just port)
			),
			Dhall.expected = pure Dhall.Text
		}

instance Dhall.FromDhall Redis.ConnectInfo where
	autoWith _ = Dhall.Decoder {
			Dhall.extract = (\(Dhall.TextLit (Dhall.Chunks _ txt)) ->
				either (Dhall.extractError . tshow) pure $ RedisURL.parseConnectInfo $ textToString txt
			),
			Dhall.expected = pure Dhall.Text
		}

