{-# LANGUAGE TemplateHaskell, TypeFamilies, ViewPatterns #-}

{- |

Module      :  Type.Serialize.Base
Copyright   :  (c) The University of Kansas 2011
License     :  BSD3

Maintainer  :  nicolas.frisby@gmail.com
Stability   :  experimental
Portability :  see LANGUAGE pragmas (... GHC)

Type-level serialization (i.e. type -> @type-digit@ type-level numeral).

-}
module Type.Serialize.Base
  (Serialize, encode, serializeType, serializeTypeAsHash, serializeTypeAsHash_) where

import Type.Spine.TH (tyConSignature)
import Type.Spine.Stage0 (kTypeG)
import Type.Digits (toDigits, toDigits_, fixed, flexible, exactly)

import Language.Haskell.TH
import Language.Haskell.TH.Syntax

import qualified Data.Hash as H

import qualified Data.Serialize as V
import qualified Data.ByteString as BS




-- | @Serialize@ maps a type to its unique type-level serialization.
type family Serialize a

-- | Encode uses the @cereal@ package serializer to encode the value and then
-- uses @type-digits@ to reflect it as a type-level numeral.
encode :: V.Serialize a => a -> Type
encode = toDigits_ (BS.foldl' ((. fixed) . (++)) []) . V.encode

-- | Generates the @Serialize@ instance corresponding to the serialization of
-- the type constructor's globally unique name (i.e. TH's @NameG@).
serializeType :: Name -> Q [Dec]
serializeType n@(Name (occString -> occ)
                (NameG _ (pkgString -> pkg) (modString -> mod))) = do
  (ks, k) <- tyConSignature n

  let qk = kTypeG $ foldr ArrowK k ks
      uid = (occ, mod, pkg)
      base128 = return .
                toDigits (exactly 3 . take 3 . flexible) (H.asWord64 $ H.hash uid) .
                encode $ uid
  (:[]) `fmap` tySynInstD ''Serialize [qk `appT` conT n] base128
serializeType n = fail $ "serializeType expects a global name: " ++ show n

-- | @serializeType@ can result in very large types, so we prefer the
-- @data-hash@ hash of the @NameG@'s serialization.
serializeTypeAsHash :: Name -> Q [Dec]
serializeTypeAsHash n = do
  (ks, k) <- tyConSignature n
  serializeTypeAsHash_ n ks k

-- | @serializeType@ can result in very large types, so we prefer the
-- @data-hash@ hash of the @NameG@'s serialization.
serializeTypeAsHash_ :: Name -> [Kind] -> Kind -> Q [Dec]
serializeTypeAsHash_ n@(Name (occString -> occ)
                (NameG _ (pkgString -> pkg) (modString -> mod))) ks k = do
  let qk = kTypeG $ foldr ArrowK k ks
      uid = (occ, mod, pkg)
      base128 = return . toDigits_ flexible . H.asWord64 . H.hash $ uid
  (:[]) `fmap` tySynInstD ''Serialize [qk `appT` conT n] base128
serializeTypeAsHash_ n _ _ = fail $ "serializeTypeAsHash expects a global name: " ++ show n

tvb_kind (PlainTV _) = StarK
tvb_kind (KindedTV _ k) = k