module MOO.Network.Console ( createConsoleListener ) where

import Control.Concurrent (forkIO, killThread)
import Control.Concurrent.STM (STM, TVar, atomically, readTVar)
import Control.Exception (SomeException, try)
import Control.Monad (liftM, when, unless, forever)
import Control.Monad.Trans (lift)
import Control.Monad.IO.Class (liftIO)
import Data.ByteString (ByteString)
import Data.List ((\\), sort, nub)
import Data.Maybe (mapMaybe, catMaybes)
import Data.Text (Text)
import Data.Text.Encoding (encodeUtf8)
import Pipes (Producer, Pipe, runEffect, await, yield, for, cat, (>->))
import Pipes.ByteString (stdout)
import Pipes.Concurrent (spawn, unbounded, send, fromInput, toOutput)
import System.Console.Haskeline (InputT, CompletionFunc, Completion(),
                                 runInputT, getInputLine,
                                 setComplete, defaultSettings,
                                 completeWordWithPrev, simpleCompletion)
import System.IO (hIsClosed, stdin)

import qualified Data.HashMap.Strict as HM
import qualified Data.IntSet as IS
import qualified Data.Text as T
import qualified Data.Vector as V

import MOO.Connection (ConnectionHandler)
import {-# SOURCE #-} MOO.Network (Point(Console),
                                   Listener(listenerPoint, listenerCancel))
import MOO.Object
import MOO.Task
import MOO.Types
import MOO.Verb

import qualified MOO.String as Str

createConsoleListener :: Listener -> ConnectionHandler -> IO Listener
createConsoleListener listener handler = do
  let Console world' = listenerPoint listener

  thread <- forkIO $ acceptConnection world' handler

  return listener { listenerCancel = killThread thread }

acceptConnection :: TVar World -> ConnectionHandler -> IO ()
acceptConnection worldTVar handler = do
  let connectionName :: STM String
      connectionName = return "console"

  (output, input) <- spawn unbounded

  thread <- forkIO $ runInputT defaultSettings $ runEffect $
    consoleInput >-> writeLines >-> writeUtf8 >-> toOutput output

  handler connectionName (fromInput input, stdout)
  killThread thread

  eof <- try (hIsClosed stdin) >>=
         either (\err -> let _ = err :: SomeException in return True) return
  unless eof $ acceptConnection worldTVar handler

  where writeLines :: Monad m => Pipe Text Text m ()
        writeLines = forever $ await >>= yield . flip T.snoc '\n'

        writeUtf8 :: Monad m => Pipe Text ByteString m ()
        writeUtf8 = for cat (yield . encodeUtf8)

consoleInput :: Producer Text (InputT IO) ()
consoleInput = loop
  where loop = do
          maybeLine <- lift $ getInputLine ""
          case maybeLine of
            Just line -> yield (T.pack line) >> loop
            Nothing   -> return ()

{-
  let completion = mooCompletion worldTVar (initialPlayer testFrame)
  runInputT (setComplete completion defaultSettings) $
    repLoop worldTVar $ addFrame testFrame state

mooCompletion :: TVar World -> ObjId -> CompletionFunc IO
mooCompletion world player = completeWordWithPrev Nothing sep completions
  where sep = " \t.:$"

        completions prev word =
          liftM (mkCompletions $ null prev) $
          runTask =<< newTask world player completionTask
          where completionTask =
                  getCompletions prev word `catchException` \_ -> return zero

        mkCompletions :: Bool -> Maybe Value -> [Completion]
        mkCompletions finished (Just (Lst v)) =
          mapMaybe (mkCompletion finished) (V.toList v)
        mkCompletions _ _ = []

        mkCompletion :: Bool -> Value -> Maybe Completion
        mkCompletion finished (Str str) =
          Just $ (simpleCompletion $ Str.toString str) { isFinished = finished }
        mkCompletion _ _ = Nothing

        getCompletions :: String -> String -> MOO Value
        getCompletions "" word = completeCommandVerb word
        getCompletions ('$':_) word = completeProperty word 0
        getCompletions ('.':prev) word =
          objectForCompletion prev >>= completeProperty word
        getCompletions (':':prev) word =
          objectForCompletion prev >>= completeVerb word
        getCompletions _ word = completeName word

        objectForCompletion :: String -> MOO ObjId
        objectForCompletion prev = return nothing

        completeProperty :: String -> ObjId -> MOO Value
        completeProperty word oid = do
          maybeObj <- getObject oid
          case maybeObj of
            Nothing  -> return zero
            Just obj -> do
              unless (objectPermR obj) $ checkPermission (objectOwner obj)
              properties <- liftSTM $ mapM readTVar $
                            HM.elems $ objectProperties obj
              return $ mkResults word $
                map fromId builtinProperties ++ map propertyName properties

        completeVerb :: String -> ObjId -> MOO Value
        completeVerb word oid = return zero

        completeCommandVerb :: String -> MOO Value
        completeCommandVerb word = do
          objects <- localObjects True
          verbs <- concat `liftM` mapM verbsForObject objects
          return $ mkResults word $ concatMap simpleVerbNames $
            filter isCommandVerb verbs

        simpleVerbNames :: Verb -> [StrT]
        simpleVerbNames = map removeStar . Str.words . verbNames
          where removeStar name =
                  let (before, after) = Str.break (== '*') name
                  in before `Str.append` if Str.null after
                                         then after else Str.tail after

        isCommandVerb :: Verb -> Bool
        isCommandVerb verb =
          not $ verbDirectObject   verb /= ObjNone  &&
                verbPreposition    verb == PrepNone &&
                verbIndirectObject verb /= ObjNone

        verbsForObject :: Object -> MOO [Verb]
        verbsForObject obj = do
          verbs <- liftSTM $ mapM (readTVar . snd) $ objectVerbs obj
          case objectParent obj of
            Nothing        -> return verbs
            Just parentOid -> do
              maybeParent <- getObject parentOid
              case maybeParent of
                Nothing     -> return verbs
                Just parent -> (verbs ++) `liftM` verbsForObject parent

        completeName :: String -> MOO Value
        completeName word = do
          objects <- localObjects False
          return $ mkResults word $ map objectName objects ++ ["me", "here"]

        localObjects :: Bool -> MOO [Object]
        localObjects includeRoom = do
          player <- getPlayer
          maybePlayer <- getObject player
          case maybePlayer of
            Nothing      -> return []
            Just player' -> do
              let holding   = objectContents player'
                  maybeRoom = objectLocation player'
              roomContents <-
                maybe (return IS.empty)
                (liftM (maybe IS.empty objectContents) . getObject) maybeRoom
              let oids = maybe id (:) (if includeRoom
                                       then maybeRoom else Nothing) $
                         IS.toList (holding `IS.union` roomContents)
              liftM ((player' :) . catMaybes) $
                mapM getObject (oids \\ [player])

        mkResults :: String -> [StrT] -> Value
        mkResults word = stringList . sort . nub . filter isPrefix
          where isPrefix name = word' `Str.isPrefixOf` name
                word' = Str.fromString word
-}