{-# LANGUAGE ForeignFunctionInterface, OverloadedStrings #-}

import Criterion.Main
import qualified Data.ByteString as BSS
import qualified Data.ByteString.Builder as BB
import qualified Data.ByteString.Builder.Extra as BBE
import qualified Data.ByteString.Lazy as BSL
import qualified Data.Double.Conversion.Convertable as BL
import Data.List (foldr)
import Data.Monoid ((<>))
import qualified Data.Text as T
import qualified Data.Text.Internal.Builder as BT
import qualified Data.Text.Lazy as TL
import Foreign.C.Types (CDouble(CDouble), CInt(CInt))
import qualified Text.Show.ByteString as BS

testList = [2.08345919 .. 20002.08345919] :: [Double]
testFloatList = [2.08345919 .. 20002.08345919] :: [Float]

testListBSBuilder :: (Double -> BB.Builder) -> [Double] -> BSS.ByteString
testListBSBuilder func list = BSL.toStrict $ BBE.toLazyByteStringWith (BBE.safeStrategy 128 128) BSL.empty $ foldr (\x y -> (func x <> ", ") <> y) mempty list

testListBSBuilderFloat :: (Float -> BB.Builder) -> [Float] -> BSS.ByteString
testListBSBuilderFloat func list = BSL.toStrict $ BBE.toLazyByteStringWith (BBE.safeStrategy 128 128) BSL.empty $ foldr (\x y -> (func x <> ", ") <> y) mempty list

testListByteString :: (Double -> BSS.ByteString) -> [Double] -> BSS.ByteString
testListByteString func = foldr (\x y -> (func x <> ", ") <> y) mempty

testListByteStringFloat :: (Float -> BSS.ByteString) -> [Float] -> BSS.ByteString
testListByteStringFloat func = foldr (\x y -> (func x <> ", ") <> y) mempty

testListText :: (Double -> T.Text) -> [Double] -> T.Text
testListText func = foldr (\x y -> (func x <> ", ") <> y) mempty

testListTextFloat :: (Float -> T.Text) -> [Float] -> T.Text
testListTextFloat func = foldr (\x y -> (func x <> ", ") <> y) mempty

testListTextBuilder :: (Double -> BT.Builder) -> [Double] -> T.Text
testListTextBuilder func list = TL.toStrict $ BT.toLazyText $ foldr (\x y -> (func x <> ", ") <> y) mempty list

testListTextBuilderFloat :: (Float -> BT.Builder) -> [Float] -> T.Text
testListTextBuilderFloat func list = TL.toStrict $ BT.toLazyText $ foldr (\x y -> (func x <> ", ") <> y) mempty list

testFuncBuilderDefault :: [Double] -> BSS.ByteString
testFuncBuilderDefault = \list -> BSL.toStrict $ BB.toLazyByteString $ foldr (\x y -> (BB.doubleDec x <> ", ") <> y) mempty list

testFuncBuilderDefaultFloat :: [Float] -> BSS.ByteString
testFuncBuilderDefaultFloat = \list -> BSL.toStrict $ BB.toLazyByteString $ foldr (\x y -> (BB.floatDec x <> ", ") <> y) mempty list


main = defaultMain [
         bgroup "haskell-single" [
           bench "show" $ nf show (pi::Double)
         , bench "bytestring-show" $ whnf BS.show (pi::Double)
         , bgroup "text" [
             bench "toShortest" $ whnf (BL.toShortest :: Double -> T.Text) pi
           , bench "toExponential" $ whnf ((BL.toExponential 3) :: Double -> T.Text) pi
           , bench "toPrecision" $ whnf ((BL.toExponential 8) :: Double -> T.Text) pi
           , bench "toFixed" $ whnf ((BL.toFixed 8) :: Double -> T.Text) pi
           ]
         , bgroup "bytestring" [
             bench "toShortest" $ whnf (BL.toShortest :: Double -> BSS.ByteString) pi
           , bench "toExponential" $ whnf ((BL.toExponential 3) :: Double -> BSS.ByteString) pi
           , bench "toPrecision" $ whnf ((BL.toExponential 8) :: Double -> BSS.ByteString) pi
           , bench "toFixed" $ whnf ((BL.toFixed 8) :: Double -> BSS.ByteString) pi
           ]
         , bgroup "bytestringBuilder" [
             bench "toShortest" $ nf (BB.toLazyByteString . BL.toShortest) (pi::Double)
           , bench "toPrecision" $ nf (BB.toLazyByteString . (BL.toExponential 8)) (pi::Double)
           , bench "toPrecision" $ nf (BB.toLazyByteString . (BL.toPrecision 8)) (pi::Double)
           , bench "toFixed" $ nf (BB.toLazyByteString . (BL.toFixed 8)) (pi::Double)
           ]
         ]
       , bgroup "Haskell-list" [
           bgroup "bytestring" [
             bench "toShortest" $ nf (testListByteString BL.toShortest) testList
           , bench "toExponential" $ nf (testListByteString $ BL.toExponential 8) testList
           , bench "toPrecision" $ nf (testListByteString $ BL.toPrecision 8) testList
           , bench "toFixed" $ nf (testListByteString $ BL.toFixed 8) testList]
       , bgroup "bytestring-float" [
             bench "toShortest" $ nf (testListByteStringFloat BL.toShortest) testFloatList
           , bench "toExponential" $ nf (testListByteStringFloat $ BL.toExponential 8) testFloatList
           , bench "toPrecision" $ nf (testListByteStringFloat $ BL.toPrecision 8) testFloatList
           , bench "toFixed" $ nf (testListByteStringFloat $ BL.toFixed 8) testFloatList]
        ,  bgroup "bytestring-builder" [
             bench "toShortest" $ nf (testListBSBuilder BL.toShortest) testList
           , bench "toExponential" $ nf (testListBSBuilder $ BL.toExponential 8) testList
           , bench "toPrecision" $ nf (testListBSBuilder $ BL.toPrecision 8) testList
           , bench "toFixed" $ nf (testListBSBuilder $ BL.toFixed 8) testList ]
        ,  bgroup "bytestring-builder-float" [
             bench "toShortest" $ nf (testListBSBuilderFloat BL.toShortest) testFloatList
           , bench "toExponential" $ nf (testListBSBuilderFloat $ BL.toExponential 8) testFloatList
           , bench "toPrecision" $ nf (testListBSBuilderFloat $ BL.toPrecision 8) testFloatList
           , bench "toFixed" $ nf (testListBSBuilderFloat $ BL.toFixed 8) testFloatList ]
        ,  bgroup "text" [
             bench "toShortest" $ nf (testListText BL.toShortest) testList
           , bench "toExponential" $ nf (testListText $ BL.toExponential 8) testList
           , bench "toPrecision" $ nf (testListText $ BL.toPrecision 8) testList
           , bench "toFixed" $ nf (testListText $ BL.toFixed 8) testList ]
        ,  bgroup "text-float" [
             bench "toShortest" $ nf (testListTextFloat BL.toShortest) testFloatList
           , bench "toExponential" $ nf (testListTextFloat $ BL.toExponential 8) testFloatList
           , bench "toPrecision" $ nf (testListTextFloat $ BL.toPrecision 8) testFloatList
           , bench "toFixed" $ nf (testListTextFloat $ BL.toFixed 8) testFloatList ]
        ,  bgroup "text-builder" [
             bench "toShortest" $ nf (testListTextBuilder BL.toShortest) testList
           , bench "toExponential" $ nf (testListTextBuilder $ BL.toExponential 8) testList
           , bench "toPrecision" $ nf (testListTextBuilder$ BL.toPrecision 8) testList
           , bench "toFixed" $ nf (testListTextBuilder $ BL.toFixed 8) testList ]
        ,  bgroup "text-builder-float" [
             bench "toShortest" $ nf (testListTextBuilderFloat BL.toShortest) testFloatList
           , bench "toExponential" $ nf (testListTextBuilderFloat $ BL.toExponential 8) testFloatList
           , bench "toPrecision" $ nf (testListTextBuilderFloat $ BL.toPrecision 8) testFloatList
           , bench "toFixed" $ nf (testListTextBuilderFloat $ BL.toFixed 8) testFloatList ]
        ,  bgroup "bytestring-builder-default" [
             bench "Double" $ nf (testFuncBuilderDefault) testList
        ,    bench "Float" $ nf (testFuncBuilderDefaultFloat) testFloatList ]
         ]
       , bgroup "sprintf" [
           bench "exact" $ whnf sprintf_exact pi
         , bench "exponential" $ whnf (sprintf_exponential 3) pi
         , bench "fixed" $ whnf (sprintf_fixed 8) pi
         , bench "generic" $ whnf (sprintf_generic 6) pi
         , bench "generic_default" $ whnf sprintf_generic_default pi
         ]
       ]

foreign import ccall unsafe sprintf_exact :: CDouble -> ()
foreign import ccall unsafe sprintf_exponential :: CInt -> CDouble -> ()
foreign import ccall unsafe sprintf_fixed :: CInt -> CDouble -> ()
foreign import ccall unsafe sprintf_generic :: CInt -> CDouble -> ()
foreign import ccall unsafe sprintf_generic_default :: CDouble -> ()
