{-# LANGUAGE OverloadedStrings #-}

module MOO.Builtins.Network ( builtins ) where

import Control.Applicative ((<$>))
import Control.Concurrent.STM (readTVar)
import Control.Monad (unless, (<=<))
import Control.Monad.State (gets)
import Data.Time (UTCTime, diffUTCTime)

import qualified Data.Map as M

import MOO.Builtins.Common
import MOO.Network
import MOO.Connection
import MOO.Object (systemObject)
import MOO.Task
import MOO.Types

import qualified MOO.String as Str

{-# ANN module ("HLint: ignore Use camelCase" :: String) #-}

-- | ยง 4.4.4 Operations on Network Connections
builtins :: [Builtin]
builtins = [
    bf_connected_players
  , bf_connected_seconds
  , bf_idle_seconds
  , bf_notify
  , bf_buffered_output_length
  , bf_read
  , bf_force_input
  , bf_flush_input
  , bf_output_delimiters
  , bf_boot_player
  , bf_connection_name
  , bf_set_connection_option
  , bf_connection_options
  , bf_connection_option
  , bf_open_network_connection
  , bf_listen
  , bf_unlisten
  , bf_listeners
  ]

bf_connected_players = Builtin "connected_players" 0 (Just 1)
                       [TAny] TLst $ \optional -> do
  let [include_all] = booleanDefaults optional [False]

  objects <- withConnections $ return . M.keys
  return $ objectList $ if include_all then objects else filter (>= 0) objects

secondsSince :: UTCTime -> MOO Value
secondsSince utcTime = Int . floor . (`diffUTCTime` utcTime) <$> gets startTime

bf_connected_seconds = Builtin "connected_seconds" 1 (Just 1)
                       [TObj] TInt $ \[Obj player] ->
  withConnection player $ maybe (raise E_INVARG) secondsSince <=<
    liftSTM . readTVar . connectionConnectedTime

bf_idle_seconds = Builtin "idle_seconds" 1 (Just 1)
                  [TObj] TInt $ \[Obj player] ->
  withConnection player $ secondsSince <=<
    liftSTM . readTVar . connectionActivityTime

bf_notify = Builtin "notify" 2 (Just 3)
            [TObj, TStr, TAny] TAny $ \(Obj conn : Str string : optional) -> do
  let [no_flush] = booleanDefaults optional [False]

  checkPermission conn
  truthValue <$> notify' no_flush conn string

bf_buffered_output_length = Builtin "buffered_output_length" 0 (Just 1)
                            [TObj] TInt $ \optional -> do
  let (conn : _) = maybeDefaults optional

  len <- case conn of
    Just (Obj oid) -> do
      checkPermission oid
      withConnection oid $ liftSTM . bufferedOutputLength . Just
    Nothing -> liftSTM $ bufferedOutputLength Nothing

  return (Int $ fromIntegral len)

bf_read = Builtin "read" 0 (Just 2) [TObj, TAny] TAny $ \optional -> do
  (conn, optional) <- case optional of
    Obj conn : optional -> checkPermission conn >> return (conn, optional)
    [] -> checkWizard >> getPlayer >>= \player -> return (player, [])
  let [non_blocking] = booleanDefaults optional [False]

  readFromConnection conn non_blocking

bf_force_input = Builtin "force_input" 2 (Just 3) [TObj, TStr, TAny]
                 TAny $ \(Obj conn : Str line : optional) -> do
  let [at_front] = booleanDefaults optional [False]

  checkPermission conn
  forceInput at_front conn line
  return zero

bf_flush_input = Builtin "flush_input" 1 (Just 2)
                 [TObj, TAny] TAny $ \(Obj conn : optional) -> do
  let [show_messages] = booleanDefaults optional [False]

  checkPermission conn
  withMaybeConnection conn $ maybe (return ()) $
    liftSTM . flushInput show_messages
  return zero

bf_output_delimiters = Builtin "output_delimiters" 1 (Just 1)
                       [TObj] TLst $ \[Obj player] -> do
  checkPermission player
  (prefix, suffix) <- withConnection player $
                      liftSTM . readTVar . connectionOutputDelimiters
  return $ stringList [Str.fromText prefix, Str.fromText suffix]

bf_boot_player = Builtin "boot_player" 1 (Just 1) [TObj] TAny $ \[Obj player] ->
  checkPermission player >> bootPlayer player >> return zero

bf_connection_name = Builtin "connection_name" 1 (Just 1)
                     [TObj] TStr $ \[Obj player] -> do
  checkPermission player
  Str . Str.fromString <$> withConnection player (liftSTM . connectionName)

bf_set_connection_option = Builtin "set_connection_option" 3 (Just 3)
                           [TObj, TStr, TAny]
                           TAny $ \[Obj conn, Str option, value] -> do
  checkPermission conn
  setConnectionOption conn (toId option) value
  return zero

bf_connection_options = Builtin "connection_options" 1 (Just 1)
                        [TObj] TLst $ \[Obj conn] -> do
  checkPermission conn
  fromListBy pair . M.toList <$> getConnectionOptions conn

  where pair (k, v) = fromList [Str $ fromId k, v]

bf_connection_option = Builtin "connection_option" 2 (Just 2)
                       [TObj, TStr] TAny $ \[Obj conn, Str name] -> do
  checkPermission conn
  getConnectionOptions conn >>=
    maybe (raise E_INVARG) return . M.lookup (toId name)

bf_open_network_connection = Builtin "open_network_connection" 2 (Just 3)
                             [TStr, TInt, TObj]
                             TObj $ \(Str host : Int port : optional) -> do
  let [Obj listener] = defaults optional [Obj systemObject]

  checkWizard
  world <- getWorld
  unless (outboundNetwork world) $ raise E_PERM

  notyet "open_network_connection"

{-
  checkWizard
  connId <- openNetworkConnection
            (Str.toString host) (fromIntegral port) listener
  return (Obj connId)
-}

bf_listen = Builtin "listen" 2 (Just 3)
            [TObj, TAny, TAny] TAny $ \(Obj object : point : optional) -> do
  let [print_messages] = booleanDefaults optional [False]

  checkWizard
  checkValid object

  point <- value2point point
  point2value <$> listen object point print_messages

bf_unlisten = Builtin "unlisten" 1 (Just 1) [TAny] TAny $ \[canon] -> do
  checkWizard

  unlisten =<< value2point canon
  return zero

bf_listeners = Builtin "listeners" 0 (Just 0) [] TLst $ \[] ->
  fromListBy formatListener . M.elems . listeners <$> getWorld

  where formatListener Listener { listenerObject        = object
                                , listenerPoint         = point
                                , listenerPrintMessages = printMessages } =
          fromList [Obj object, point2value point, truthValue printMessages]