-- | This library provides basic internationalization capabilities

module Text.I18N.GetText (
                          getText,
                          nGetText,
                          dGetText,
                          dnGetText,
                          dcGetText,
                          dcnGetText,
                          bindTextDomain,
                          textDomain
                         ) where

import Foreign.C.Types
import Foreign.C.String
import Foreign.C.Error
import Foreign.Ptr
import Data.Maybe (fromMaybe)
import System.Locale.SetLocale


foreign import ccall unsafe "libintl.h gettext" c_gettext
    :: CString -> IO CString

foreign import ccall unsafe "libintl.h dgettext" c_dgettext
    :: CString -> CString -> IO CString

foreign import ccall unsafe "libintl.h dcgettext" c_dcgettext 
    :: CString -> CString -> CInt -> IO CString

foreign import ccall unsafe "libintl.h ngettext" c_ngettext
    :: CString -> CString -> CULong -> IO CString

foreign import ccall unsafe "libintl.h dngettext" c_dngettext
    :: CString -> CString -> CString -> CULong -> IO CString

foreign import ccall unsafe "libintl.h dcngettext" c_dcngettext
    :: CString -> CString -> CString -> CULong -> CInt -> IO CString

foreign import ccall unsafe "libintl.h bindtextdomain" c_bindtextdomain
    :: CString -> CString -> IO CString

foreign import ccall unsafe "libintl.h textdomain" c_textdomain
    :: CString -> IO CString

fromCString :: CString -> IO (Maybe String)
fromCString x | x == nullPtr = return Nothing
              | otherwise = peekCString x >>= return . Just

fromCStringError :: String -> CString -> IO String
fromCStringError err x | x == nullPtr = throwErrno err
                       | otherwise = peekCString x

fromCStringDefault :: String -> CString -> IO String
fromCStringDefault d x = fromCString x >>= \r -> return (fromMaybe d r)

fromCStringPluralDefault :: (Eq a, Num a) => String -> String -> a -> CString -> IO String
fromCStringPluralDefault def def_plural n s
    | n == 1 = fromCStringDefault def s
    | otherwise = fromCStringDefault def_plural s
    

withCStringMaybe :: Maybe String -> (CString -> IO a) -> IO a
withCStringMaybe Nothing f = f nullPtr
withCStringMaybe (Just str) f = withCString str f

-- |getText wraps GNU gettext function. It returns translated string for the
-- input messages. If translated string not found the input string will be
-- returned.
--
-- The most common usage of this function is to declare function __:
-- 
-- > __ = unsafePerformIO . getText
--
-- and wrap all text strings into this function, e.g.
-- 
-- > printHello = putStrLn (__ "Hello")
-- 
getText :: String -> IO String
getText s = 
    withCString s $ \s' -> 
        c_gettext s' >>= fromCStringDefault s

-- |dGetText wraps GNU dgettext function. It works similar to 'getText'
-- but also could take domain name.
--
dGetText :: Maybe String        -- ^ domain name, if 'Nothing' ---
                                -- default domain will be used
         -> String              -- ^ message id
         -> IO String           -- ^ return value
dGetText domainname msgid = 
    withCStringMaybe domainname $ \dn' ->
        withCString msgid $ \msg' ->
            c_dgettext dn' msg' >>= fromCStringDefault msgid

-- |dcGetText wraps GNU dcgettext function. It works similar to 'dGetText'
-- but also takes category id
dcGetText :: Maybe String       -- ^ domain name, if 'Nothing' ---
                                -- default domain will be used
          -> Category           -- ^ locale facet
          -> String             -- ^ message id
          -> IO String          -- ^ return value
dcGetText domainname cat msgid = 
    withCStringMaybe domainname $ \dn' ->
        withCString msgid $ \msg' ->
            c_dcgettext dn' msg' (categoryToCInt cat) >>= 
            fromCStringDefault msgid
                                
-- |nGetText wraps GNU ngettext function. It translates text string in the
-- user's native language, by lookilng up the approppiate plural form of the
-- message.
--
nGetText :: String              -- ^ msgid in singular form
         -> String              -- ^ msgid in plural form
         -> Integer             -- ^ number, used to choose appropriate form
         -> IO String           -- ^ result string, by default if number is 1 than
                                -- singular form of msgid is returned, otherwise ---
                                -- plural 
nGetText msgid msgid_plural n =
    withCString msgid $ \msgid' ->
        withCString msgid_plural $ \msgid_plural' ->
            c_ngettext msgid' msgid_plural' (fromInteger n) >>=
            fromCStringPluralDefault msgid msgid_plural n

-- |dnGetText wraps GNU dngettext function. It works similar to 'nGetText' but
-- also takes domain name
-- 
dnGetText :: Maybe String       -- ^ domain name, if 'Nothing' ---
                                -- default domain will be used
          -> String             -- ^ msgid in singular form
          -> String             -- ^ msgid in plural form
          -> Integer            -- ^ number, used to choose appropriate form
          -> IO String          -- ^ result string, by default if number is 1 than
                                -- singular form of msgid is returned, otherwise ---
                                -- plural 
dnGetText domainname msgid msgid_plural n =
    withCStringMaybe domainname $ \dn' ->
        withCString msgid $ \msgid' ->
            withCString msgid_plural $ \msgid_plural' ->
                c_dngettext dn' msgid' msgid_plural' (fromInteger n) >>=
                fromCStringPluralDefault msgid msgid_plural n

-- |dcnGetText wraps GNU dcngettext function. It works similar to 'dnGetText' but
-- also takes category id
-- 
dcnGetText :: Maybe String      -- ^ domain name, if 'Nothing' ---
                                -- default domain will be used
          -> Category           -- ^ locale facet
          -> String             -- ^ msgid in singular form
          -> String             -- ^ msgid in plural form
          -> Integer            -- ^ number, used to choose appropriate form
          -> IO String          -- ^ result string, by default if number is 1 than
                                -- singular form of msgid is returned, otherwise ---
                                -- plural 
dcnGetText domainname cat msgid msgid_plural n =
    withCStringMaybe domainname $ \dn' ->
        withCString msgid $ \msgid' ->
            withCString msgid_plural $ \msgid_plural' ->
                c_dcngettext dn' msgid' msgid_plural' 
                             (fromInteger n) (categoryToCInt cat) >>=
                fromCStringPluralDefault msgid msgid_plural n

-- |bindTextDomain sets the base directory of the hierarchy
-- containing message catalogs for a given message domain.
--
-- Throws 'IOError' if fails
--
bindTextDomain :: String        -- ^ domain name
               -> Maybe String  -- ^ path to the locale folder or 'Nothing' to return
                                -- base directory for domain
               -> IO String     -- ^ return value
bindTextDomain domainname dirname = 
  withCString domainname $ \domain -> 
      withCStringMaybe dirname $ \dir ->
          c_bindtextdomain domain dir >>= fromCStringError "bindTextDomain fails"

-- |textDomain sets domain for future 'getText' call
--
-- Throws 'IOError' if fails
-- 
textDomain :: Maybe String      -- ^ domain name, if 'Nothing' than returns
                                -- current domain name
           -> IO String         -- ^ return value
textDomain domainname = 
    withCStringMaybe domainname $ \domain ->
        c_textdomain domain >>= fromCStringError "textDomain fails"
