    module System.Console.Haskeline.RunCommand (runCommandLoop) where

    import System.Console.Haskeline.Command
    import System.Console.Haskeline.Term
    import System.Console.Haskeline.LineState
    import System.Console.Haskeline.Monads
    import System.Console.Haskeline.Prefs
    import System.Console.Haskeline.Key

    import Control.Monad

    runCommandLoop :: (CommandMonad m, MonadState Layout m, LineState s)
        => TermOps -> String -> KeyCommand m s a -> s -> m a
    runCommandLoop tops@TermOps{evalTerm = e} prefix cmds initState
        = case e of -- NB: Need to separate this case out from the above pattern
                    -- in order to build on ghc-6.12.3
            EvalTerm eval liftE
                -> eval $ withGetEvent tops
                    $ runCommandLoop' liftE tops (stringToGraphemes prefix) initState
                        cmds

    runCommandLoop' :: forall m n s a . (Term n, CommandMonad n,
            MonadState Layout m, MonadReader Prefs n, LineState s)
            => (forall b . m b -> n b) -> TermOps -> Prefix -> s -> KeyCommand m s a -> n Event
            -> n a
    runCommandLoop' liftE tops prefix initState cmds getEvent = do
        let s = lineChars prefix initState
        drawLine s
        readMoreKeys s (fmap (liftM (\x -> (x,[])) . ($ initState)) cmds)
      where
        readMoreKeys :: LineChars -> KeyMap (CmdM m (a,[Key])) -> n a
        readMoreKeys s next = do
            event <- handle (\(e::SomeException) -> moveToNextLine s
                                        >> throwIO e) getEvent
            case event of
                        ErrorEvent e -> moveToNextLine s >> throwIO e
                        WindowResize -> do
                            drawReposition liftE tops s
                            readMoreKeys s next
                        KeyInput ks -> do
                            bound_ks <- mapM (asks . lookupKeyBinding) ks
                            loopCmd s $ applyKeysToMap (concat bound_ks) next

        loopCmd :: LineChars -> CmdM m (a,[Key]) -> n a
        loopCmd s (GetKey next) = readMoreKeys s next
        -- If there are multiple consecutive LineChanges, only render the diff
        -- to the last one, and skip the rest. This greatly improves speed when
        -- a large amount of text is pasted in at once.
        loopCmd s (DoEffect (LineChange _)
                    e@(DoEffect (LineChange _) _)) = loopCmd s e
        loopCmd s (DoEffect e next) = do
                                        t <- drawEffect prefix s e
                                        loopCmd t next
        loopCmd s (CmdM next) = liftE next >>= loopCmd s
        loopCmd s (Result (x,ks)) = do
                                        liftIO (saveUnusedKeys tops ks)
                                        moveToNextLine s
                                        return x

    drawReposition :: (Term n, MonadState Layout m)
        => (forall a . m a -> n a) -> TermOps -> LineChars -> n ()
    drawReposition liftE tops s = do
        oldLayout <- liftE get
        newLayout <- liftIO (getLayout tops)
        liftE (put newLayout)
        when (oldLayout /= newLayout) $ reposition oldLayout s

    drawEffect :: (Term m, MonadReader Prefs m)
        => Prefix -> LineChars -> Effect -> m LineChars
    drawEffect prefix s (LineChange ch) = do
        let t = ch prefix
        drawLineDiff s t
        return t
    drawEffect _ s ClearScreen = do
        clearLayout
        drawLine s
        return s
    drawEffect _ s (PrintLines ls) = do
        when (s /= ([],[])) $ moveToNextLine s
        printLines ls
        drawLine s
        return s
    drawEffect _ s RingBell = actBell >> return s

    actBell :: (Term m, MonadReader Prefs m) => m ()
    actBell = do
        style <- asks bellStyle
        case style of
            NoBell -> return ()
            VisualBell -> ringBell False
            AudibleBell -> ringBell True

    ---------------
    -- Traverse through the tree of keybindings, using the given keys.
    -- Remove as many GetKeys as possible.
    -- Returns any unused keys (so that they can be applied at the next getInputLine).
    applyKeysToMap :: Monad m => [Key] -> KeyMap (CmdM m (a,[Key]))
                                    -> CmdM m (a,[Key])
    applyKeysToMap [] next = GetKey next
    applyKeysToMap (k:ks) next = case lookupKM next k of
        Nothing -> DoEffect RingBell $ GetKey next
        Just (Consumed cmd) -> applyKeysToCmd ks cmd
        Just (NotConsumed cmd) -> applyKeysToCmd (k:ks) cmd

    applyKeysToCmd :: Monad m => [Key] -> CmdM m (a,[Key])
                                    -> CmdM m (a,[Key])
    applyKeysToCmd ks (GetKey next) = applyKeysToMap ks next
    applyKeysToCmd ks (DoEffect e next) = DoEffect e (applyKeysToCmd ks next)
    applyKeysToCmd ks (CmdM next) = CmdM $ liftM (applyKeysToCmd ks) next
    applyKeysToCmd ks (Result (x,ys)) = Result (x,ys++ks) -- use in the next input line