{-# LANGUAGE DeriveAnyClass      #-}
{-# LANGUAGE DeriveFunctor       #-}
{-# LANGUAGE DeriveGeneric       #-}
{-# LANGUAGE FlexibleContexts    #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections       #-}
module ZM.BLOB (
    BLOB(..),
    blob,
    unblob,
    TypedBLOB(..),
    typedBLOB,
    typedBLOB_,
    untypedBLOB,
    TypedValue(..),
    typedValue,
    untypedValue,
    typeErr,
    ) where

import           Control.DeepSeq
import           Data.Bifunctor
import qualified Data.ByteString         as B
import           Data.ByteString.Convert
import           Data.Flat
import           Data.Model
import           ZM.Abs
import           ZM.Model        ()
import qualified ZM.Type.BLOB    as Z
import           ZM.Types
import           ZM.Util

-- |A BLOB is binary value encoded according to a specified encoding (e.g. UTF8)
data BLOB encoding = BLOB {encoding::encoding,content::B.ByteString}
  deriving (Eq, Ord, NFData, Generic, Flat)

instance Model encoding => Model (BLOB encoding)

instance Show encoding => Show (BLOB encoding) where show (BLOB enc bs) = unwords ["BLOB",show enc,show $ B.unpack bs]

-- |Extract the binary content of a BLOB
unblob :: BLOB t -> B.ByteString
--unblob (BLOB _ bs) = bs
unblob = content

-- |Build a BLOB from an encoding and a ByteString-like value
blob :: AsByteString a => encoding -> a -> BLOB encoding
blob enc = BLOB enc . toByteString

-- |A typed value, a Flat encoded value and its absolute type
data TypedBLOB = TypedBLOB AbsType (BLOB Z.FlatEncoding)
  deriving (Eq, Ord, Show, NFData, Generic, Flat, Model)

-- |Build a TypedBLOB out of a value
typedBLOB :: forall a . (Model a,Flat a) => a -> TypedBLOB
typedBLOB = typedBLOB_ (absType (Proxy :: Proxy a))

-- |Build a TypedBLOB out of a type and a value
typedBLOB_ :: Flat a => AbsType -> a -> TypedBLOB
typedBLOB_ t v = TypedBLOB t (blob Z.FlatEncoding . flat $ v)

-- |A typed value, a value and its absolute type
data TypedValue a = TypedValue AbsType a deriving (Eq, Ord, Show, Functor,NFData,  Generic, Flat)

-- |Build a TypedValue out of a value
typedValue :: forall a . Model a => a -> TypedValue a
typedValue = TypedValue (absType (Proxy :: Proxy a))

-- |Type-checked extraction of a value of a known type from a decoded TypedBLOB
untypedBLOB ::  forall a.  (Flat a, Model a) => Decoded TypedBLOB -> TypedDecoded a
untypedBLOB ea = case ea of
                    Left e -> Left . DecodeError $ e
                    Right (TypedBLOB typ' bs) ->
                      let typ = absType (Proxy :: Proxy a)
                      in if typ' /= typ
                         then typeErr typ typ'
                         else first DecodeError . unflat $ (unblob bs :: B.ByteString)

-- |Type-checked extraction of a value of a known type from a decoded TypedValue
untypedValue ::  Model a => Decoded (TypedValue a) -> TypedDecoded a
untypedValue ea = case ea of
                    Left e -> Left . DecodeError $ e
                    Right (TypedValue typ' a) ->
                      let typ = absType (proxyOf a)
                      in if typ' /= typ
                         then typeErr typ typ'
                         else Right a

-- |Return a WrongType error
typeErr :: AbsType -> AbsType -> TypedDecoded a
typeErr typ typ' = Left $ WrongType typ typ'