module Network.SimpleServer.Examples.ChatServer(main, run) where
import Data.Char
import Data.List
import System.Environment

import qualified Network.SimpleServer as S

-- Constants --
-- A Welcome message to send to clients when they connect.
welcomeMessage = "Welcome to the Simple Chat Server.\n" ++
                 "Commands:\n" ++
                 "/message [message] - Broadcast a message to all users.\n" ++
                 "/name [newname] - Change your name to newname.\n" ++
                 "/ping - Let's the server know you're still there.\n" ++ 
                 "/who - Displays a list of users connected to the server.\n" ++
                 "/disconnect - Disconnect from Simple Chat Server.\n"

-- Keys
-- The display name for a ClientConn
username = "username"

-- Handlers

-- The Connection Handler sets a newly connected
-- users username to "user #{cid}" broadcasts
-- that they have connected and then sends them the welcome message
connHandler :: S.ConnectionHandler
connHandler server client = do
  let name = "user #" ++ (show (S.cid client))
      msg = name ++ " connected."
  S.modify client username name
  S.broadcast server msg
  S.respond client welcomeMessage

-- The Disconnection Handler responds to the client "disconnected"
-- Then broadcasts to the room that the user has disconnected
dissHandler :: S.DisconnectHandler
dissHandler server client = do
  name <- S.lookup client username
  let msg = name ++ " disconnected."
  S.respond client "disconnected"
  S.broadcast server msg

-- Commands
  
-- The name command sets the username to the value specified
-- by the user as long as it is not the empty string. If
-- the name is the empty string, the client is notified that
-- the name is not valid. Otherwise, a message is broadcast
-- stating the user changed their name
nameCmd = "/name"
nameHandler :: S.CmdHandler
nameHandler (_:msg) server client = do
  case msg of
    [] -> S.respond client "You did not provide a name to change to."
    msg -> do
      before <- S.lookup client username
      let name = intercalate " " msg
          message = before ++ " is now known as " ++ name
      S.modify client username (intercalate " " msg)
      S.broadcast server message

-- The ping command notifies the server that the user is still connected
-- so they don't time out. If the /ping command is followed by "silent"
-- the server does not respond. Otherwise, the server responds with
-- a received message.
pingCmd = "/ping"
pingHandler :: S.CmdHandler
pingHandler (_:flag) _ client = do
  case flag of
    ("silent":_) -> return ()
    _ -> S.respond client "Ping received."

-- If the message command is received, any text following it is
-- broadcast to all users.
msgCmd = "/message"
msgHandler :: S.CmdHandler
msgHandler (cmd:msg) server client = do
  name <- S.lookup client username
  S.broadcast server $ name ++ "> " ++ (unwords msg)

-- The disconnect command causes the message "Goodbye!" to be sent to
-- the client. Then they are disconnected from the server.
disCmd = "/disconnect"
disHandler :: S.CmdHandler
disHandler _ server client = do
  S.respond client "Goodbye!"
  S.disconnect client
  
-- The who command responds to the client with
-- a list of usernames
whoCmd = "/who"
whoHandler :: S.CmdHandler
whoHandler _ server client = do
  clients <- S.clientList server
  usernames <- mapM (flip S.lookup username) clients
  let message = "Users:\n" ++ (intercalate "\n" usernames)
  S.respond client message

-- Builds a server on the given port, adds the commands
-- starts the server, and waits for the word stop to be entered
run :: Int -> IO ()
run port = do
  server <- S.new connHandler dissHandler port
  S.addCommand server whoCmd whoHandler
  S.addCommand server nameCmd nameHandler
  S.addCommand server disCmd disHandler
  S.addCommand server msgCmd msgHandler
  S.addCommand server pingCmd pingHandler
  S.start server
  putStrLn $ "Chat Server Started on Port: " ++ (show port)
  putStrLn $ "Type 'stop' to stop the server."
  waitStop server
  S.stop server
  putStrLn "Server Stopped"

-- Waits for the word 'stop' to be entered
waitStop :: S.Server -> IO ()
waitStop server = do
  string <- getLine
  case string of
    "stop" -> return ()
    _ -> waitStop server
  
-- Starts a server on the specified port or prints the usage message
main = do
  args <- getArgs
  case args of
    [] -> printUsage
    (x:_) -> if isInt x then (return (read x)) >>= run else printUsage

printUsage :: IO ()
printUsage = putStrLn "Usage ./ChatServer [port]"

isInt :: [Char] -> Bool
isInt = (== []) . (filter (not . isDigit))