{-# LANGUAGE DeriveDataTypeable,RankNTypes #-}
-- | This module add unknown field support to the library.  There are no user API things here,
-- except for advanced spelunking into the data structures which can and have changed with no
-- notice.  Importer beware.
module Text.ProtocolBuffers.Unknown
  ( UnknownField(..),UnknownMessage(..),UnknownFieldValue(..)
  , wireSizeUnknownField,wirePutUnknownField,catch'Unknown
  ) where

import qualified Data.ByteString.Lazy as L
import qualified Data.Foldable as F
import Data.Generics
import Data.Monoid(mempty,mappend)
import Data.Sequence((|>))
import Data.Typeable()
import Control.Monad.Error.Class(catchError)

import Text.ProtocolBuffers.Basic
import Text.ProtocolBuffers.WireMessage

-- err :: String -> b
-- err msg = error $ "Text.ProtocolBuffers.Unknown error\n"++msg

-- | Messages that can store unknown fields implement this interface.
-- UnknownField is a supposedly opaque type.
class UnknownMessage msg where
  getUnknownField :: msg -> UnknownField
  putUnknownField :: UnknownField -> msg -> msg

-- | This is a suposedly opaque type
newtype UnknownField = UnknownField (Seq UnknownFieldValue)
  deriving (Eq,Ord,Show,Read,Data,Typeable)

data UnknownFieldValue = UFV {-# UNPACK #-} !WireTag !ByteString
  deriving (Eq,Ord,Show,Read,Data,Typeable)

instance Mergeable UnknownField where
--  mergeEmpty = UnknownField mempty
  mergeAppend (UnknownField m1) (UnknownField m2) = UnknownField (mappend m1 m2)

instance Default UnknownField where
  defaultValue = UnknownField mempty

-- | This is used by the generated code
wireSizeUnknownField :: UnknownField -> WireSize
wireSizeUnknownField (UnknownField m) = F.foldl' aSize 0 m  where
  aSize old (UFV tag bs) = old + size'Varint (getWireTag tag) + L.length bs

-- | This is used by the generated code
wirePutUnknownField :: UnknownField -> Put
wirePutUnknownField (UnknownField m) = F.mapM_ aPut m where
  aPut (UFV tag bs) = putVarUInt (getWireTag tag) >> putLazyByteString bs

{-# INLINE catch'Unknown #-}
-- | This is used by the generated code
catch'Unknown :: (Typeable a, UnknownMessage a) => (WireTag -> a -> Get a) -> (WireTag -> a -> Get a)
catch'Unknown update'Self = \wire'Tag old'Self -> catchError (update'Self wire'Tag old'Self) (\_ -> loadUnknown wire'Tag old'Self) 
  where loadUnknown :: (Typeable a, UnknownMessage a) => WireTag -> a -> Get a
        loadUnknown tag msg = do
          let (fieldId,wireType) = splitWireTag tag
              (UnknownField uf) = getUnknownField msg
          bs <- wireGetFromWire fieldId wireType
          let v' = seq bs $ UFV tag bs
              uf' = seq v' $ uf |> v'
          seq uf' $ return $ putUnknownField (UnknownField uf') msg