{-# LANGUAGE FlexibleContexts      #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables   #-}
{-# LANGUAGE TypeFamilies          #-}
{-# LANGUAGE UndecidableInstances  #-}

module Metro.SocketServer
  ( SocketServer
  , socketServer
  , SSSID (..)
  ) where

import           Data.List       (isPrefixOf)
import           Metro.Class     (Servable (..), Transport (..),
                                  TransportConfig)
import           Metro.TCPServer
import qualified Metro.TP.Socket as T (Socket, mapTCPSocket, mapUDPSocket)
import           Metro.UDPServer
import           Network.Socket  (SockAddr, Socket)

data SocketServer = TCP TCPServer
    | UDP UDPServer

data SSSID = TCPSID Socket
    | UDPSID SockAddr

instance Servable SocketServer where
  data ServerConfig SocketServer = SSConfig String
  type SID SocketServer = SSSID
  type STP SocketServer = T.Socket
  newServer (SSConfig hostPort) =
    if "udp" `isPrefixOf` hostPort then UDP <$> newServer (udpServer hostPort)
    else TCP <$> newServer (tcpServer hostPort)
  servOnce (TCP s) done = servOnce s $ mapTCPServOnceDone done
  servOnce (UDP s) done = servOnce s $ mapUDPServOnceDone done
  onConnEnter (TCP s) (TCPSID sid) = onConnEnter s sid
  onConnEnter (UDP s) (UDPSID sid) = onConnEnter s sid
  onConnEnter _ _                  = error "onConnEnter invalid type"
  onConnLeave (TCP s) (TCPSID sid) = onConnLeave s sid
  onConnLeave (UDP s) (UDPSID sid) = onConnLeave s sid
  onConnLeave _ _                  = error "onConnLeave invalid type"
  servClose (TCP s) = servClose s
  servClose (UDP s) = servClose s

mapTCPServOnceDone
  :: (Maybe (SID SocketServer, TransportConfig (STP SocketServer)) -> m ())
  -> Maybe (SID TCPServer, TransportConfig (STP TCPServer)) -> m ()
mapTCPServOnceDone done Nothing = done Nothing
mapTCPServOnceDone done (Just (sid, stp)) = done (Just (TCPSID sid, T.mapTCPSocket stp))

mapUDPServOnceDone
  :: (Maybe (SID SocketServer, TransportConfig (STP SocketServer)) -> m ())
  -> Maybe (SID UDPServer, TransportConfig (STP UDPServer)) -> m ()
mapUDPServOnceDone done Nothing = done Nothing
mapUDPServOnceDone done (Just (sid, stp)) = done (Just (UDPSID sid, T.mapUDPSocket stp))

socketServer :: String -> ServerConfig SocketServer
socketServer = SSConfig
