{-# LANGUAGE DeriveDataTypeable         #-}
{-# LANGUAGE DeriveFunctor              #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase                 #-}

-- | Aeson bridge for MessagePack

module Data.MessagePack.Aeson (
  -- * Conversion functions
  toAeson, fromAeson,

  -- * MessagePack instance for Aeson.Value
  -- $msgpackInstance

  -- * ToJSON and FromJSON instance for MessagePack.Object
  -- $aesonInstances

  -- * Wrapper instances
  AsMessagePack(..),
  AsAeson(..),

  -- * Utility functions
  packAeson, unpackAeson,
  decodeMessagePack, encodeMessagePack,
  ) where

import           Control.Applicative
import           Control.Arrow
import           Control.DeepSeq
import           Data.Aeson           as A
import qualified Data.ByteString.Lazy as L
import           Data.Data
import qualified Data.HashMap.Strict  as HM
import           Data.Maybe
import           Data.MessagePack     as MP
import           Data.Scientific
import qualified Data.Text.Encoding   as T
import qualified Data.Vector          as V

-- | Convert MessagePack Object to Aeson Value.
-- If the value unable to convert, it returns Nothing
toAeson :: MP.Object -> Maybe Value
toAeson = \case
  ObjectNil      -> Just Null
  ObjectBool b   -> Just $ Bool b
  ObjectInt n    -> Just $ Number $ fromIntegral n
  ObjectFloat f  -> Just $ Number $ realToFrac f
  ObjectDouble d -> Just $ Number $ realToFrac d
  ObjectStr t    -> Just $ String t
  ObjectBin b    -> String <$> either (const Nothing) Just (T.decodeUtf8' b)
  ObjectArray v  -> Array <$> V.mapM toAeson v
  ObjectMap m    ->
    A.Object . HM.fromList . V.toList
      <$> V.mapM (\(k, v) -> (,) <$> fromObject k <*> toAeson v) m
  ObjectExt _ _  -> Nothing

-- | Convert Aeson Value to MessagePack Object
fromAeson :: Value -> MP.Object
fromAeson = \case
  Null        -> ObjectNil
  Bool b      -> ObjectBool b
  Number s ->
    case floatingOrInteger s of
      Left f  -> ObjectDouble f
      Right n -> ObjectInt n
  String t    -> ObjectStr t
  Array v     -> ObjectArray $ V.map fromAeson v
  A.Object o  -> ObjectMap $ V.fromList $ map (toObject *** fromAeson) $ HM.toList o

-- $msgpackInstance
-- > instance MessagePack Value
instance MessagePack Value where
  fromObject = toAeson
  toObject = fromAeson

-- $aesonInstances
-- > instance ToJSON Object
-- > instance FromJSON Object
instance ToJSON MP.Object where
  -- When fail to convert, it returns `Null`
  toJSON = fromMaybe Null .toAeson

instance FromJSON MP.Object where
  parseJSON = return . fromAeson

-- | Wrapper for using Aeson values as MessagePack value.
newtype AsMessagePack a = AsMessagePack { getAsMessagePack :: a }
  deriving (Eq, Ord, Show, Read, Functor, Data, Typeable, NFData)

instance (FromJSON a, ToJSON a) => MessagePack (AsMessagePack a) where
  fromObject o = AsMessagePack <$> (fromJSON' =<< toAeson o)
  toObject = fromAeson . toJSON . getAsMessagePack

-- | Wrapper for using MessagePack values as Aeson value.
newtype AsAeson a = AsAeson { getAsAeson :: a }
  deriving (Eq, Ord, Show, Read, Functor, Data, Typeable, NFData)

instance MessagePack a => ToJSON (AsAeson a) where
  toJSON = fromMaybe Null . toAeson . toObject . getAsAeson

instance MessagePack a => FromJSON (AsAeson a) where
  parseJSON = maybe empty (return . AsAeson) . fromObject . fromAeson

-- | Pack Aeson value to MessagePack binary
packAeson :: ToJSON a => a -> L.ByteString
packAeson = pack . toJSON

-- | Unpack Aeson value from MessagePack binary
unpackAeson :: FromJSON a => L.ByteString -> Maybe a
unpackAeson b = fromJSON' =<< unpack b

-- | Encode MessagePack value to JSON
encodeMessagePack :: MessagePack a => a -> L.ByteString
encodeMessagePack = encode . toJSON . AsAeson

-- | Decode MessagePack value from JSON
decodeMessagePack :: MessagePack a => L.ByteString -> Maybe a
decodeMessagePack b = getAsAeson <$> (fromJSON' =<< decode b)

fromJSON' :: FromJSON a => Value -> Maybe a
fromJSON' = resultToMaybe . fromJSON

resultToMaybe :: Result a -> Maybe a
resultToMaybe = \case
  Success a -> Just a
  _         -> Nothing