#if __GLASGOW_HASKELL__ >= 703
#endif
module System.Console.Terminfo.Base(
                            
                            Terminal(),
                            setupTerm,
                            setupTermFromEnv,
                            SetupTermError,
                            
                            Capability,
                            getCapability,
                            tiGetFlag,
                            tiGuardFlag,
                            tiGetNum,
                            tiGetStr,
                            
                            
                            tiGetOutput1,
                            OutputCap,
                            TermStr,
                            
                            TermOutput(),
                            runTermOutput,
                            hRunTermOutput,
                            termText,
                            tiGetOutput,
                            LinesAffected,
                            
                            Monoid(..),
                            (<#>),
                            ) where
import Control.Applicative
import Control.Monad
import Data.Monoid
import Foreign.C
import Foreign.ForeignPtr
import Foreign.Ptr
import Foreign.Marshal
import Foreign.Storable (peek,poke)
import System.Environment (getEnv)
import System.IO.Unsafe (unsafePerformIO)
import System.IO
import Control.Exception
import Data.Typeable
data TERMINAL
newtype Terminal = Terminal (ForeignPtr TERMINAL)
foreign import ccall "&" cur_term :: Ptr (Ptr TERMINAL)
foreign import ccall set_curterm :: Ptr TERMINAL -> IO (Ptr TERMINAL)
foreign import ccall "&" del_curterm :: FunPtr (Ptr TERMINAL -> IO ())
foreign import ccall setupterm :: CString -> CInt -> Ptr CInt -> IO ()
setupTerm :: String -> IO Terminal
setupTerm term = bracket (peek cur_term) (poke cur_term) $ \_ -> 
    withCString term $ \c_term ->
    with 0 $ \ret_ptr -> do
        
        
        
        let stdOutput = 1
        
        poke cur_term nullPtr
        
        setupterm c_term stdOutput ret_ptr
        ret <- peek ret_ptr
        if (ret /=1)
            then throwIO $ SetupTermError
                $ "Couldn't look up terminfo entry " ++ show term
            else do
                cterm <- peek cur_term
                fmap Terminal $ newForeignPtr del_curterm cterm
data SetupTermError = SetupTermError String
                        deriving Typeable
instance Show SetupTermError where
    show (SetupTermError str) = "setupTerm: " ++ str
instance Exception SetupTermError where
setupTermFromEnv :: IO Terminal
setupTermFromEnv = do
    env_term <- handle handleBadEnv $ getEnv "TERM" 
    let term = if null env_term then "dumb" else env_term
    setupTerm term
  where
    handleBadEnv :: IOException -> IO String
    handleBadEnv _ = return ""
withCurTerm :: Terminal -> IO a -> IO a
withCurTerm (Terminal term) f = withForeignPtr term $ \cterm -> do
        old_term <- peek cur_term
        if old_term /= cterm
            then do
                    _ <- set_curterm cterm
                    x <- f
                    _ <- set_curterm old_term
                    return x
            else f
strHasPadding :: String -> Bool
strHasPadding [] = False
strHasPadding ('$':'<':_) = True
strHasPadding (_:cs) = strHasPadding cs
newtype TermOutput = TermOutput ([TermOutputType] -> [TermOutputType])
data TermOutputType = TOCmd LinesAffected String
                    | TOStr String
instance Monoid TermOutput where
    mempty = TermOutput id
    TermOutput xs `mappend` TermOutput ys = TermOutput (xs . ys)
termText :: String -> TermOutput 
termText str = TermOutput (TOStr str :)
runTermOutput :: Terminal -> TermOutput -> IO ()
runTermOutput = hRunTermOutput stdout
hRunTermOutput :: Handle -> Terminal -> TermOutput -> IO ()
hRunTermOutput h term (TermOutput to) = do
    putc_ptr <- mkCallback putc
    withCurTerm term $ mapM_ (writeToTerm putc_ptr h) (to [])
    freeHaskellFunPtr putc_ptr
    hFlush h
  where
    putc c = let c' = toEnum $ fromEnum c
             in hPutChar h c' >> hFlush h >> return c
writeToTerm :: FunPtr CharOutput -> Handle -> TermOutputType -> IO ()
writeToTerm putc h (TOCmd numLines s)
    | strHasPadding s = tPuts s numLines putc
    | otherwise = hPutStr h s
writeToTerm _ h (TOStr s) = hPutStr h s
infixl 2 <#>
(<#>) :: Monoid m => m -> m -> m
(<#>) = mappend
newtype Capability a = Capability (Terminal -> IO (Maybe a))
getCapability :: Terminal -> Capability a -> Maybe a
getCapability term (Capability f) = unsafePerformIO $ withCurTerm term (f term)
instance Functor Capability where
    fmap f (Capability g) = Capability $ \t -> fmap (fmap f) (g t)
instance Applicative Capability where
    pure  = return
    (<*>) = ap
instance Monad Capability where
    return = Capability . const . return . Just
    Capability f >>= g = Capability $ \t -> do
        mx <- f t
        case mx of
            Nothing -> return Nothing
            Just x -> let Capability g' = g x in g' t
instance Alternative Capability where
    (<|>) = mplus
    empty = mzero
instance MonadPlus Capability where
    mzero = Capability (const $ return Nothing)
    Capability f `mplus` Capability g = Capability $ \t -> do
        mx <- f t
        case mx of
            Nothing -> g t
            _ -> return mx
foreign import ccall tigetnum :: CString -> IO CInt
tiGetNum :: String -> Capability Int 
tiGetNum cap = Capability $ const $ do
                n <- fmap fromEnum (withCString cap tigetnum)
                if n >= 0
                    then return (Just n)
                    else return Nothing
foreign import ccall tigetflag :: CString -> IO CInt
tiGetFlag :: String -> Capability Bool
tiGetFlag cap = Capability $ const $ fmap (Just . (>0)) $
                        withCString cap tigetflag
                
tiGuardFlag :: String -> Capability ()
tiGuardFlag cap = tiGetFlag cap >>= guard
                
foreign import ccall tigetstr :: CString -> IO CString
 
tiGetStr :: String -> Capability String
tiGetStr cap = Capability $ const $ do
                result <- withCString cap tigetstr 
                if result == nullPtr || result == neg1Ptr
                    then return Nothing
                    else fmap Just (peekCString result)
    where
        
        neg1Ptr = nullPtr `plusPtr` (1)
                    
foreign import ccall tparm ::
    CString -> CLong -> CLong -> CLong -> CLong -> CLong -> CLong 
    -> CLong -> CLong -> CLong 
    -> IO CString
tParm :: String -> Capability ([Int] -> String)
tParm cap = Capability $ \t -> return $ Just 
        $ \ps -> unsafePerformIO $ withCurTerm t $
                    tparm' (map toEnum ps ++ repeat 0)
    where tparm' (p1:p2:p3:p4:p5:p6:p7:p8:p9:_)
            = withCString cap $ \c_cap -> do
                result <- tparm c_cap p1 p2 p3 p4 p5 p6 p7 p8 p9
                if result == nullPtr
                    then return ""
                    else peekCString result
          tparm' _ = fail "tParm: List too short"
tiGetOutput :: String -> Capability ([Int] -> LinesAffected -> TermOutput)
tiGetOutput cap = do
    str <- tiGetStr cap
    f <- tParm str
    return $ \ps la -> fromStr la $ f ps
fromStr :: LinesAffected -> String -> TermOutput
fromStr la s = TermOutput (TOCmd la s :)
type CharOutput = CInt -> IO CInt
foreign import ccall "wrapper" mkCallback :: CharOutput -> IO (FunPtr CharOutput)
foreign import ccall tputs :: CString -> CInt -> FunPtr CharOutput -> IO ()
type LinesAffected = Int
tPuts :: String -> LinesAffected -> FunPtr CharOutput -> IO ()
tPuts s n putc = withCString s $ \c_str -> tputs c_str (toEnum n) putc
tiGetOutput1 :: forall f . OutputCap f => String -> Capability f
tiGetOutput1 str = do
    cap <- tiGetStr str
    guard (hasOkPadding (undefined :: f) cap)
    f <- tParm cap
    return $ outputCap f []
class OutputCap f where
    hasOkPadding :: f -> String -> Bool
    outputCap :: ([Int] -> String) -> [Int] -> f
instance OutputCap [Char] where
    hasOkPadding _ = not . strHasPadding 
    outputCap f xs = f (reverse xs)
instance OutputCap TermOutput where
    hasOkPadding _ = const True
    outputCap f xs = fromStr 1 $ f $ reverse xs
instance (Enum p, OutputCap f) => OutputCap (p -> f) where
    outputCap f xs = \x -> outputCap f (fromEnum x:xs)
    hasOkPadding _ = hasOkPadding (undefined :: f)
class (Monoid s, OutputCap s) => TermStr s
instance TermStr [Char]
instance TermStr TermOutput