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

{- |

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 where

import Type.Digits (Digit, 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 :: k) :: Digit

-- | 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_data :: Name -> Q [Dec]
serializeType_data = serializeType_ ConT

serializeType_pro :: Name -> Q [Dec]
serializeType_pro = serializeType_ PromotedT

serializeType_ inj n@(Name (occString -> occ)
                     (NameG _ (pkgString -> pkg) (modString -> mod))) =
  let uid = (occ, mod, pkg)
      base128 = toDigits (exactly 3 . take 3 . flexible) (H.asWord64 $ H.hash uid) .
                encode $ uid
  in return [TySynInstD ''Serialize [inj 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_data :: Name -> Q [Dec]
serializeTypeAsHash_data = serializeTypeAsHash_ ConT

serializeTypeAsHash_pro :: Name -> Q [Dec]
serializeTypeAsHash_pro = serializeTypeAsHash_ PromotedT

serializeTypeAsHash_ inj n@(Name (occString -> occ)
                (NameG _ (pkgString -> pkg) (modString -> mod))) =
  let uid = (occ, mod, pkg)
      base128 = toDigits_ flexible . H.asWord64 . H.hash $ uid
  in return [TySynInstD ''Serialize [inj n] base128]
serializeTypeAsHash_ _ n = fail $ "serializeTypeAsHash expects a global name: " ++ show n