{-# LINE 1 "Data/Text/ICU/NumberFormatter.hsc" #-}
{-# LANGUAGE EmptyDataDecls, RankNTypes, BangPatterns, ForeignFunctionInterface, RecordWildCards #-}
-- |
-- Module      : Data.Text.ICU.NumberFormatter
-- Copyright   : (c) 2021 Torsten Kemps-Benedix
--
-- License     : BSD-style
-- Maintainer  : bos@serpentine.com
-- Stability   : experimental
-- Portability : GHC
--
-- Number formatter implemented as bindings to
-- the International Components for Unicode (ICU) libraries.

module Data.Text.ICU.NumberFormatter
    (
      -- * Data
      NumberFormatter,
      -- * Formatter
      numberFormatter,
      -- $skeleton
      -- * Formatting functions
      formatIntegral, formatIntegral', formatDouble, formatDouble'
    ) where



import Data.Int (Int32, Int64)
import Data.Text (Text)
import Data.Text.ICU.Error.Internal (UErrorCode, handleError, handleOverflowError)
import Data.Text.ICU.Internal (LocaleName(..), UChar, withLocaleName, newICUPtr, fromUCharPtr, useAsUCharPtr)
import Foreign.C.String (CString)
import Foreign.C.Types (CDouble(..))
import Foreign.ForeignPtr (withForeignPtr, ForeignPtr)
import Foreign.Ptr (FunPtr, Ptr)
import Prelude hiding (last)
import System.IO.Unsafe (unsafePerformIO)

-- $skeleton
--
-- Here are some examples for number skeletons, see
-- https://unicode-org.github.io/icu/userguide/format_parse/numbers/skeletons.html#examples for more:
--
-- +----------------------------+-----------------+--------+--------------+-------------------------------------------------------------+
-- | Long Skeleton              | Concise Skeleton | Input | en-US Output | Comments                                                    |
-- +============================+==================+=======+==============+=============================================================+
-- | percent                    | %                | 25    | 25%          |                                                             |
-- | .00                        |.00               | 25    | 25.00        | Equivalent to Precision::fixedFraction(2)                   |
-- | percent .00                | % .00            | 25    | 25.00%       |                                                             |
-- | scale/100                  | scale/100        | 0.3   | 30           | Multiply by 100 before formatting                           |
-- | percent scale/100          | %x100            | 0.3   | 30%          |                                                             |
-- | measure-unit/length-meter  | unit/meter       | 5     | 5 m          | UnitWidth defaults to Short                                 |
-- | unit-width-full-name       | unit/meter       | 5     | 5 meters     |                                                             |
-- | compact-short              | K                | 5000  | 5K           |                                                             |
-- | compact-long               | KK               | 5000  | 5 thousand   |                                                             |
-- | group-min2                 | ,?               | 5000  | 5000         | Require 2 digits in group for separator                     |
-- | group-min2                 | ,?               | 15000 | 15,000       |                                                             |
-- | sign-always                | +!               | 60    | +60          | Show sign on all numbers                                    |
-- | sign-always                | +!               | 0     | +0           |                                                             |
-- | sign-except-zero           | +?               | 60    | +60          | Show sign on all numbers except 0                           |
-- | sign-except-zero           | +?               | 0     | 0            |                                                             |
-- +----------------------------+-----------------+--------+--------------+-------------------------------------------------------------+

data UNumberFormatter
data UFormattedNumber

newtype NumberFormatter = NumberFormatter (ForeignPtr UNumberFormatter)

-- | Create a new 'NumberFormatter'.
--
-- See https://unicode-org.github.io/icu/userguide/format_parse/numbers/skeletons.html for how to specify
-- the number skeletons. And use 'availableLocales' in order to find the allowed locale names. These
-- usuallly look like "en", "de", "de_AT" etc. See 'formatIntegral' and 'formatDouble' for some examples.
numberFormatter :: Text -> LocaleName -> IO NumberFormatter
numberFormatter skel loc =
  withLocaleName loc $ \locale ->
    useAsUCharPtr skel $ \skelPtr skelLen ->
      newICUPtr NumberFormatter unumf_close $
        handleError $ unumf_openForSkeletonAndLocale skelPtr (fromIntegral skelLen) locale

-- | Format an integral number.
--
-- See https://unicode-org.github.io/icu/userguide/format_parse/numbers/skeletons.html for how to specify
-- the number skeletons.
--
-- >>> import Data.Text
-- >>> nf <- numberFormatter (pack "precision-integer") (Locale "de")
-- >>> formatIntegral nf 12345
-- "12.345"
-- >>> nf2 <- numberFormatter (pack "precision-integer") (Locale "fr")
-- >>> formatIntegral nf2 12345
-- "12\8239\&345"
formatIntegral :: Integral a => NumberFormatter -> a -> Text
formatIntegral (NumberFormatter nf) x = unsafePerformIO $ do
  withForeignPtr nf $ \nfPtr -> do
    resultPtr <- newResult
    withForeignPtr resultPtr $ \resPtr -> do
      handleError $ unumf_formatInt nfPtr (fromIntegral x) resPtr
      t <- handleOverflowError (fromIntegral (64 :: Int))
        (\dptr dlen -> unumf_resultToString resPtr dptr dlen)
        (\dptr dlen -> fromUCharPtr dptr (fromIntegral dlen))
      pure t

-- | Create a number formatter and apply it to an integral number.
formatIntegral' :: (Integral a) => Text -> LocaleName -> a -> Text
formatIntegral' skel loc x = unsafePerformIO $ do
  nf <- numberFormatter skel loc
  pure $ formatIntegral nf x

-- | Format a Double.
--
-- See https://unicode-org.github.io/icu/userguide/format_parse/numbers/skeletons.html for how to specify
-- the number skeletons.
--
-- >>> import Data.Text
-- >>> nf3 <- numberFormatter (pack "precision-currency-cash") (Locale "it")
-- >>> formatDouble nf3 12345.6789
-- "12.345,68"
formatDouble :: NumberFormatter -> Double -> Text
formatDouble (NumberFormatter nf) x = unsafePerformIO $ do
  withForeignPtr nf $ \nfPtr -> do
    resultPtr <- newResult
    withForeignPtr resultPtr $ \resPtr -> do
      handleError $ unumf_formatDouble nfPtr (CDouble x) resPtr
      t <- handleOverflowError (fromIntegral (64 :: Int))
        (\dptr dlen -> unumf_resultToString resPtr dptr dlen)
        (\dptr dlen -> fromUCharPtr dptr (fromIntegral dlen))
      pure t

-- | Create a number formatter and apply it to a Double.
formatDouble' :: Text -> LocaleName -> Double -> Text
formatDouble' skel loc x = unsafePerformIO $ do
  nf <- numberFormatter skel loc
  pure $ formatDouble nf x

newResult :: IO (ForeignPtr UFormattedNumber)
newResult = newICUPtr id unumf_closeResult $ handleError unumf_openResult

foreign import ccall unsafe "hs_text_icu.h __hs_unumf_openForSkeletonAndLocale" unumf_openForSkeletonAndLocale
    :: Ptr UChar -> Int32 -> CString -> Ptr UErrorCode -> IO (Ptr UNumberFormatter)
foreign import ccall unsafe "hs_text_icu.h &__hs_unumf_close" unumf_close
    :: FunPtr (Ptr UNumberFormatter -> IO ())
foreign import ccall unsafe "hs_text_icu.h __hs_unumf_openResult" unumf_openResult
    :: Ptr UErrorCode -> IO (Ptr UFormattedNumber)
foreign import ccall unsafe "hs_text_icu.h &__hs_unumf_closeResult" unumf_closeResult
    :: FunPtr (Ptr UFormattedNumber -> IO ())
foreign import ccall unsafe "hs_text_icu.h __hs_unumf_formatInt" unumf_formatInt
    :: Ptr UNumberFormatter -> Int64 -> Ptr UFormattedNumber -> Ptr UErrorCode -> IO ()
foreign import ccall unsafe "hs_text_icu.h __hs_unumf_formatDouble" unumf_formatDouble
    :: Ptr UNumberFormatter -> CDouble -> Ptr UFormattedNumber -> Ptr UErrorCode -> IO ()
foreign import ccall unsafe "hs_text_icu.h __hs_unumf_resultToString" unumf_resultToString
    :: Ptr UFormattedNumber -> Ptr UChar -> Int32 -> Ptr UErrorCode -> IO Int32