{-# LANGUAGE DataKinds         #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs             #-}
{-# LANGUAGE TypeFamilies      #-}


{- |
Module      : Database.Groundhog.Converters
Description : Common Converters for groundhog.
Copyright   : Plow Technologies LLC
License     : MIT License
Maintainer  : Scott Murphy


This is a library for creating composable converters between datatypes for the purpose
of inserting into a groundhog database.

The idea is to get from a representation a, which does not have a type class 'PersistEntity a'
to one 'b', which has that type class defined.

This allows lightweight types libraries to coexist with nice database representations.

-}


module Database.Groundhog.Converters ( makeConverter
                                     , flipConverter
                                     , composeConverter
                                     , fmapConverter
                                     , bicomposeConverter
                                     , firstConverter
                                     , secondConverter
                                     , jsonConverter
                                     , integerConverter
                                     , mapConverter
                                     , bimapConverter
                                     , intMapConverter
                                     , Converter
                                     ) where
import           Control.Arrow             (first, second, (***))
import Data.Aeson (ToJSON,FromJSON,encode,eitherDecode)
import Data.Int (Int64)
import   Data.Map.Strict (Map)
import  Data.IntMap  (IntMap)
import qualified Data.IntMap  as IntMap
import qualified  Data.Map.Strict  as Map
import qualified Data.ByteString.Lazy as BL
import           Data.Bimap                (Bimap)
import qualified Data.Bimap                as Bimap

-- | The type of a converter from a newtype or opaque type to a DB-serializable type
type Converter a b = (a -> b, b -> a)

-- | Make a converter
--
-- There are preconditions on the input functions:
--
-- > makeConverter f g
--
-- prop> f (g x) = x
--
-- prop> g (f x) = x
makeConverter :: (a -> b) -- ^ Convert developer-facing type to database-storable type
              -> (b -> a) -- ^ Convert database-storable type to developer-facing type
              -> Converter a b
makeConverter ab ba = (ab, ba)

-- | Reverse the direction of a converter
flipConverter :: Converter a b -> Converter b a
flipConverter (ba, ab) = (ab, ba)

-- | Compose two converters
composeConverter :: Converter a b -> Converter b c -> Converter a c
composeConverter (ab, ba) (bc, cb) = (bc . ab, ba . cb)



-- | Map a converter over a functor
fmapConverter :: Functor f => Converter a b -> Converter (f a) (f b)
fmapConverter (ba, ab) = (fmap ba, fmap ab)


-- | compose a First and Second Converter 
bicomposeConverter  :: Converter a b -> Converter c d -> Converter (a,c) (b,d)
bicomposeConverter (ba,ab) (dc,cd) = (ba *** dc, ab *** cd)

-- | Convert only the first element of a pair
firstConverter :: Converter a b -> Converter (a, c) (b, c)
firstConverter (ba, ab) = (first ba, first ab)

-- | Convert only the second element of a pair
secondConverter :: Converter a b -> Converter (c, a) (c, b)
secondConverter (ba, ab) = (second ba, second ab)

-- | Convert via to and from JSON
jsonConverter :: (ToJSON a, FromJSON a) => Converter a BL.ByteString
jsonConverter = makeConverter encode (either error id . eitherDecode)

-- | Convert an 'Integer' (which doesn't have a 'PersistField' instance) to an 'Int64' (which does)
integerConverter :: Converter Integer Int64
integerConverter = makeConverter fromInteger fromIntegral

-- | Convert a 'Map' to a list of key-value pairs
mapConverter :: (Ord k) => Converter (Map k v) [(k, v)]
mapConverter = makeConverter Map.toList Map.fromList

-- | Convert a 'Bimap' to a list of key-value pairs
bimapConverter :: (Ord a, Ord b) => Converter (Bimap a b) [(a, b)]
bimapConverter = makeConverter Bimap.toList Bimap.fromList

-- | Convert an 'IntMap' to a list of 'Int'-value pairs
intMapConverter :: Converter (IntMap a) [(Int, a)]
intMapConverter = makeConverter IntMap.toList IntMap.fromList