{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE UndecidableInstances #-}

{-# OPTIONS_HADDOCK hide #-}

module PopKey.Encoding where

import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as LBS
import qualified Data.Store as S
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import qualified Data.Text.Lazy as LT
import qualified Data.Text.Lazy.Encoding as LT

import PopKey.Internal2

-- for instance decls only
import Data.Functor.Const
import Data.Functor.Identity
import Data.Graph (Graph)
import Data.IntMap (IntMap)
import Data.IntSet (IntSet)
import Data.Map (Map)
import Data.Proxy
import Data.Ratio
import Data.Semigroup
import Data.Sequence (Seq)
import Data.Set (Set)
-- import Data.Tree (Tree) - no store instance available
import GHC.Generics
import GHC.Natural
import GHC.Int
import GHC.Word


-- | A simple wrapper to declare you do not want this data to be granularly partitioned by poppy.
newtype StoreBlob a = StoreBlob { unStoreBlob :: a }
  deriving (Generic,Eq,Ord,Show,Bounded)
  deriving newtype Enum


-- | Inverse law: @pkDecode . pkEncode = id@. Note that this encoding is explicitly for use with poppy - use your discretion (or better, test!) to decide the granularity with which you wish to use this encoding as opposed to the standard store encoding. Relying more on PopKeyEncoding will probably use less space, but at the cost of storing items in less contiguous memory.
class PopKeyEncoding a where
  type Shape a
  type Shape a = GShape (Rep a)
  shape :: I (Shape a)
  default shape :: (GPopKeyEncoding a (Rep a) , GShape (Rep a) ~ Shape a) => I (Shape a)
  shape = gshape @a @(Rep a)

  pkEncode :: a -> F' (Shape a) BS.ByteString
  default pkEncode :: (Generic a , GPopKeyEncoding a (Rep a) , GShape (Rep a) ~ Shape a) => a -> F' (Shape a) BS.ByteString
  pkEncode = gpkEncode @a @(Rep a) . from

  pkDecode :: F' (Shape a) BS.ByteString -> a
  default pkDecode :: (Generic a , GPopKeyEncoding a (Rep a) , GShape (Rep a) ~ Shape a) => F' (Shape a) BS.ByteString -> a
  pkDecode = to . gpkDecode @a @(Rep a)

class GPopKeyEncoding s f where
  type GShape f
  gshape :: I (GShape f)

  gpkEncode :: f a -> F' (GShape f) BS.ByteString
  gpkDecode :: F' (GShape f) BS.ByteString -> f a

instance GPopKeyEncoding s U1 where
  type GShape U1 = ()
  {-# INLINE gshape #-}
  gshape = ISingle
  {-# INLINE gpkEncode #-}
  gpkEncode = const (Single' mempty)
  {-# INLINE gpkDecode #-}
  gpkDecode = const U1


instance PopKeyEncoding a => GPopKeyEncoding s (K1 i a) where
  type GShape (K1 i a) = Shape a
  {-# INLINE gshape #-}
  gshape = shape @a
  {-# INLINE gpkEncode #-}
  gpkEncode (K1 x) = pkEncode x
  {-# INLINE gpkDecode #-}
  gpkDecode = K1 . pkDecode

instance (GPopKeyEncoding s a , GPopKeyEncoding s b) => GPopKeyEncoding s (a :*: b) where
  type GShape (a :*: b) = (GShape a , GShape b)
  {-# INLINE gshape #-}
  gshape = IProd (gshape @s @a) (gshape @s @b)
  {-# INLINE gpkEncode #-}
  gpkEncode (a :*: b) = Prod' (gpkEncode @s a) (gpkEncode @s b)
  {-# INLINE gpkDecode #-}
  gpkDecode (Prod' a b) = gpkDecode @s a :*: gpkDecode @s b

instance (GPopKeyEncoding s a , GPopKeyEncoding s b) => GPopKeyEncoding s (a :+: b) where
  type GShape (a :+: b) = Either (GShape a) (GShape b)
  {-# INLINE gshape #-}
  gshape = ISum (gshape @s @a) (gshape @s @b)
  {-# INLINE gpkEncode #-}
  gpkEncode (L1 x) = Sum' (Left (gpkEncode @s x))
  gpkEncode (R1 x) = Sum' (Right (gpkEncode @s x))
  {-# INLINE gpkDecode #-}
  gpkDecode (Sum' x) = case x of
    Left l -> L1 (gpkDecode @s l)
    Right r -> R1 (gpkDecode @s r)

instance GPopKeyEncoding s f => GPopKeyEncoding s (M1 i t f) where
  type GShape (M1 i t f) = GShape f
  {-# INLINE gshape #-}
  gshape = gshape @s @f
  {-# INLINE gpkEncode #-}
  gpkEncode (M1 x) = gpkEncode @s x
  {-# INLINE gpkDecode #-}
  gpkDecode = M1 . gpkDecode @s

---------------
-- INSTANCES --
---------------

instance S.Store a => PopKeyEncoding (StoreBlob a) where
  type Shape (StoreBlob a) = ()
  {-# INLINE shape #-}
  shape = ISingle
  {-# INLINE pkEncode #-}
  pkEncode = Single' . S.encode . unStoreBlob
  {-# INLINE pkDecode #-}
  pkDecode (Single' x) = StoreBlob do S.decodeEx x

instance PopKeyEncoding BS.ByteString where
  type Shape BS.ByteString = ()
  {-# INLINE shape #-}
  shape = ISingle
  {-# INLINE pkEncode #-}
  pkEncode = Single'
  {-# INLINE pkDecode #-}
  pkDecode (Single' x) = x

instance PopKeyEncoding LBS.ByteString where
  type Shape LBS.ByteString = ()
  {-# INLINE shape #-}
  shape = ISingle
  {-# INLINE pkEncode #-}
  pkEncode = Single' . LBS.toStrict
  {-# INLINE pkDecode #-}
  pkDecode (Single' x) = LBS.fromStrict x

instance S.Store a => PopKeyEncoding [ a ] where
  type Shape [ a ] = ()
  {-# INLINE shape #-}
  shape = ISingle
  {-# INLINE pkEncode #-}
  pkEncode = case S.size @a of
    S.ConstSize _ -> Single' . BS.concat . fmap S.encode
    _ -> Single' . S.encode
  {-# INLINE pkDecode #-}
  pkDecode = \(Single' r) -> case S.size @a of
    S.ConstSize k -> S.decodeEx <$> chunks k r
    _ -> S.decodeEx r
    where
      chunks :: Int -> BS.ByteString -> [ BS.ByteString ]
      chunks i b
        | BS.length b == 0 = []
        | otherwise = let (x , xs) = BS.splitAt i b in x : chunks i xs

-- override text store instance since it uses Haskell's bloaded UTF16 encoding, which in this
-- context would be a terrible choice.
instance PopKeyEncoding T.Text where
  type Shape T.Text = ()
  {-# INLINE shape #-}
  shape = ISingle
  {-# INLINE pkEncode #-}
  pkEncode = Single' . T.encodeUtf8
  {-# INLINE pkDecode #-}
  pkDecode (Single' x) = T.decodeUtf8 x

instance PopKeyEncoding LT.Text where
  type Shape LT.Text = ()
  {-# INLINE shape #-}
  shape = ISingle
  {-# INLINE pkEncode #-}
  pkEncode = Single' . LBS.toStrict . LT.encodeUtf8
  {-# INLINE pkDecode #-}
  pkDecode (Single' x) = LT.decodeUtf8 (LBS.fromStrict x)

instance PopKeyEncoding Char where
  type Shape Char = ()
  {-# INLINE shape #-}
  shape = ISingle
  {-# INLINE pkEncode #-}
  pkEncode = Single' . S.encode
  {-# INLINE pkDecode #-}
  pkDecode (Single' x) = S.decodeEx x

instance PopKeyEncoding Double where
  type Shape Double = ()
  {-# INLINE shape #-}
  shape = ISingle
  {-# INLINE pkEncode #-}
  pkEncode = Single' . S.encode
  {-# INLINE pkDecode #-}
  pkDecode (Single' x) = S.decodeEx x

instance PopKeyEncoding Float where
  type Shape Float = ()
  {-# INLINE shape #-}
  shape = ISingle
  {-# INLINE pkEncode #-}
  pkEncode = Single' . S.encode
  {-# INLINE pkDecode #-}
  pkDecode (Single' x) = S.decodeEx x

instance PopKeyEncoding Int8 where
  type Shape Int8 = ()
  {-# INLINE shape #-}
  shape = ISingle
  {-# INLINE pkEncode #-}
  pkEncode = Single' . S.encode
  {-# INLINE pkDecode #-}
  pkDecode (Single' x) = S.decodeEx x

instance PopKeyEncoding Int16 where
  type Shape Int16 = ()
  {-# INLINE shape #-}
  shape = ISingle
  {-# INLINE pkEncode #-}
  pkEncode = Single' . S.encode
  {-# INLINE pkDecode #-}
  pkDecode (Single' x) = S.decodeEx x

instance PopKeyEncoding Int32 where
  type Shape Int32 = ()
  {-# INLINE shape #-}
  shape = ISingle
  {-# INLINE pkEncode #-}
  pkEncode = Single' . S.encode
  {-# INLINE pkDecode #-}
  pkDecode (Single' x) = S.decodeEx x

instance PopKeyEncoding Int64 where
  type Shape Int64 = ()
  {-# INLINE shape #-}
  shape = ISingle
  {-# INLINE pkEncode #-}
  pkEncode = Single' . S.encode
  {-# INLINE pkDecode #-}
  pkDecode (Single' x) = S.decodeEx x

instance PopKeyEncoding Int where
  type Shape Int = ()
  {-# INLINE shape #-}
  shape = ISingle
  {-# INLINE pkEncode #-}
  pkEncode = Single' . S.encode
  {-# INLINE pkDecode #-}
  pkDecode (Single' x) = S.decodeEx x

instance PopKeyEncoding Word8 where
  type Shape Word8 = ()
  {-# INLINE shape #-}
  shape = ISingle
  {-# INLINE pkEncode #-}
  pkEncode = Single' . S.encode
  {-# INLINE pkDecode #-}
  pkDecode (Single' x) = S.decodeEx x

instance PopKeyEncoding Word16 where
  type Shape Word16 = ()
  {-# INLINE shape #-}
  shape = ISingle
  {-# INLINE pkEncode #-}
  pkEncode = Single' . S.encode
  {-# INLINE pkDecode #-}
  pkDecode (Single' x) = S.decodeEx x

instance PopKeyEncoding Word32 where
  type Shape Word32 = ()
  {-# INLINE shape #-}
  shape = ISingle
  {-# INLINE pkEncode #-}
  pkEncode = Single' . S.encode
  {-# INLINE pkDecode #-}
  pkDecode (Single' x) = S.decodeEx x

instance PopKeyEncoding Word64 where
  type Shape Word64 = ()
  {-# INLINE shape #-}
  shape = ISingle
  {-# INLINE pkEncode #-}
  pkEncode = Single' . S.encode
  {-# INLINE pkDecode #-}
  pkDecode (Single' x) = S.decodeEx x

instance PopKeyEncoding Word where
  type Shape Word = ()
  {-# INLINE shape #-}
  shape = ISingle
  {-# INLINE pkEncode #-}
  pkEncode = Single' . S.encode
  {-# INLINE pkDecode #-}
  pkDecode (Single' x) = S.decodeEx x

instance PopKeyEncoding Integer where
  type Shape Integer = ()
  {-# INLINE shape #-}
  shape = ISingle
  {-# INLINE pkEncode #-}
  pkEncode = Single' . S.encode
  {-# INLINE pkDecode #-}
  pkDecode (Single' x) = S.decodeEx x

instance PopKeyEncoding Natural where
  type Shape Natural = ()
  {-# INLINE shape #-}
  shape = ISingle
  {-# INLINE pkEncode #-}
  pkEncode = Single' . S.encode . toInteger
  {-# INLINE pkDecode #-}
  pkDecode (Single' x) = fromInteger do S.decodeEx x

instance PopKeyEncoding Rational where
  type Shape Rational = ()
  {-# INLINE shape #-}
  shape = ISingle
  {-# INLINE pkEncode #-}
  pkEncode = Single' . S.encode
  {-# INLINE pkDecode #-}
  pkDecode (Single' x) = S.decodeEx x

instance S.Store a => PopKeyEncoding (Ratio a) where
  type Shape (Ratio a) = ()
  {-# INLINE shape #-}
  shape = ISingle
  {-# INLINE pkEncode #-}
  pkEncode = Single' . S.encode
  {-# INLINE pkDecode #-}
  pkDecode (Single' x) = S.decodeEx x

instance PopKeyEncoding Graph where
  type Shape Graph = ()
  {-# INLINE shape #-}
  shape = ISingle
  {-# INLINE pkEncode #-}
  pkEncode = Single' . S.encode
  {-# INLINE pkDecode #-}
  pkDecode (Single' x) = S.decodeEx x

instance S.Store a => PopKeyEncoding (IntMap a) where
  type Shape (IntMap a) = ()
  {-# INLINE shape #-}
  shape = ISingle
  {-# INLINE pkEncode #-}
  pkEncode = Single' . S.encode
  {-# INLINE pkDecode #-}
  pkDecode (Single' x) = S.decodeEx x

instance PopKeyEncoding IntSet where
  type Shape IntSet = ()
  {-# INLINE shape #-}
  shape = ISingle
  {-# INLINE pkEncode #-}
  pkEncode = Single' . S.encode
  {-# INLINE pkDecode #-}
  pkDecode (Single' x) = S.decodeEx x

instance (Ord a , S.Store a , S.Store b) => PopKeyEncoding (Map a b) where
  type Shape (Map a b) = ()
  {-# INLINE shape #-}
  shape = ISingle
  {-# INLINE pkEncode #-}
  pkEncode = Single' . S.encode
  {-# INLINE pkDecode #-}
  pkDecode (Single' x) = S.decodeEx x

instance S.Store a => PopKeyEncoding (Seq a) where
  type Shape (Seq a) = ()
  {-# INLINE shape #-}
  shape = ISingle
  {-# INLINE pkEncode #-}
  pkEncode = Single' . S.encode
  {-# INLINE pkDecode #-}
  pkDecode (Single' x) = S.decodeEx x

instance (Ord a , S.Store a) => PopKeyEncoding (Set a) where
  type Shape (Set a) = ()
  {-# INLINE shape #-}
  shape = ISingle
  {-# INLINE pkEncode #-}
  pkEncode = Single' . S.encode
  {-# INLINE pkDecode #-}
  pkDecode (Single' x) = S.decodeEx x

instance PopKeyEncoding ()
instance PopKeyEncoding (Proxy a)
instance PopKeyEncoding Bool
instance PopKeyEncoding a => PopKeyEncoding (Maybe a)
instance PopKeyEncoding a => PopKeyEncoding (Min a)
instance PopKeyEncoding a => PopKeyEncoding (Max a)
instance PopKeyEncoding a => PopKeyEncoding (First a)
instance PopKeyEncoding a => PopKeyEncoding (Last a)
instance PopKeyEncoding a => PopKeyEncoding (Option a)
instance PopKeyEncoding a => PopKeyEncoding (Identity a)
instance PopKeyEncoding a => PopKeyEncoding (Sum a)
instance PopKeyEncoding a => PopKeyEncoding (Product a)
instance PopKeyEncoding a => PopKeyEncoding (Const a b)

instance (PopKeyEncoding a , PopKeyEncoding b) => PopKeyEncoding (Arg a b)
instance (PopKeyEncoding a , PopKeyEncoding b) => PopKeyEncoding (Either a b)

instance (PopKeyEncoding a , PopKeyEncoding b) => PopKeyEncoding (a , b)
instance (PopKeyEncoding a , PopKeyEncoding b , PopKeyEncoding c) => PopKeyEncoding (a , b , c)
instance (PopKeyEncoding a , PopKeyEncoding b , PopKeyEncoding c , PopKeyEncoding d) => PopKeyEncoding (a , b , c , d)
instance (PopKeyEncoding a , PopKeyEncoding b , PopKeyEncoding c , PopKeyEncoding d , PopKeyEncoding e) => PopKeyEncoding (a , b , c , d , e)