packages feed

bytepatch-0.4.0: src/StreamPatch/Simple.hs

-- | TODO remove ASAP. figure out Aeson with Vinyl

{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE OverloadedStrings #-}

module StreamPatch.Simple where

import StreamPatch.Patch
import StreamPatch.HFunctorList
import StreamPatch.Patch.Binary qualified as Bin
import StreamPatch.Patch.Compare qualified as Compare
import StreamPatch.Patch.Compare ( Via(..), CompareRep )
import StreamPatch.Patch.Align qualified as Align

import Numeric.Natural
import Data.Functor.Const
import Data.Vinyl
import Data.Vinyl.TypeLevel
import Data.Aeson
import GHC.Generics

data MultiPatch s (v :: Via) a = MultiPatch
  { mpData           :: a
  , mpSeek           :: s
  , mpCompare        :: Maybe (CompareRep v a)
  , mpNullTerminates :: Maybe Natural
  , mpMaxBytes       :: Maybe Natural -- TODO confirm meaning, maybe improve name
  , mpAligned        :: Maybe Integer
  } deriving (Generic)

instance Functor (MultiPatch s ('ViaEq ec)) where
    fmap f (MultiPatch d s c n m a) = MultiPatch (f d) s (fmap f c) n m a
instance Functor (MultiPatch s ('ViaDigest h)) where
    fmap f (MultiPatch d s c n m a) = MultiPatch (f d) s c          n m a

deriving instance (Eq   a, Eq   s, Eq   (CompareRep v a)) => Eq   (MultiPatch s v a)
deriving instance (Show a, Show s, Show (CompareRep v a)) => Show (MultiPatch s v a)

data Aligned p = Aligned
  { alignedAlign   :: Integer
  , alignedPatches :: [p]
  } deriving (Eq, Show, Generic)

--------------------------------------------------------------------------------

jsonCfgCamelDrop :: Int -> Options
jsonCfgCamelDrop x = defaultOptions
  { fieldLabelModifier = camelTo2 '_' . drop x
  , rejectUnknownFields = True }

instance (ToJSON   (CompareRep v a), ToJSON   a, ToJSON   s) => ToJSON   (MultiPatch s v a) where
    toJSON     = genericToJSON     $ jsonCfgCamelDrop 2
    toEncoding = genericToEncoding $ jsonCfgCamelDrop 2
instance (FromJSON (CompareRep v a), FromJSON a, FromJSON s) => FromJSON (MultiPatch s v a) where
    parseJSON  = genericParseJSON  $ jsonCfgCamelDrop 2

instance (ToJSON   p) => ToJSON   (Aligned p) where
    toJSON     = genericToJSON     $ jsonCfgCamelDrop 7
    toEncoding = genericToEncoding $ jsonCfgCamelDrop 7
instance (FromJSON p) => FromJSON (Aligned p) where
    parseJSON  = genericParseJSON  $ jsonCfgCamelDrop 7

--------------------------------------------------------------------------------

convert :: (MultiPatch s v a -> Rec (Flap a) fs) -> MultiPatch s v a -> Patch s fs a
convert f p = Patch { patchData = mpData p
                    , patchSeek = mpSeek p
                    , patchMeta = HFunctorList $ f p }

-- TODO how to clean up (it's like liftA2 over n instead of 2. like a fold)
-- likely 'ap'!
convertBinAlign
    :: forall a v
    .  MultiPatch Integer v a
    -> Patch Integer '[Const (Align.Meta Int), Const Bin.MetaPrep, Compare.Meta v, Bin.Meta] a
convertBinAlign = convert go
  where go p = cmAlign p <+> cmBinPrep p <+> cmCompare p <+> cmBin p

convertBin
    :: forall s a v
    .  MultiPatch s v a
    -> Patch s '[Const Bin.MetaPrep, Compare.Meta v, Bin.Meta] a
convertBin p = convert go p
  where go s = cmBinPrep s <+> cmCompare s <+> cmBin s

convertAlign
    :: forall a v
    .  MultiPatch Integer v a
    -> Patch Integer '[Const (Align.Meta Int)] a
convertAlign p = convert cmAlign p

convertEmpty
    :: forall s a v
    .  MultiPatch s v a
    -> Patch s '[] a
convertEmpty p = convert (const RNil) p

--------------------------------------------------------------------------------

align
    :: forall st a ss rs is i r
    .  ( r ~ Const (Align.Meta st)
       , Num st, Eq st
       , rs ~ RDelete r ss
       , RElem r ss i
       , RSubset rs ss is )
    => Aligned (Patch Integer ss a)
    -> Either (Align.Error st) [Patch st rs a]
align (Aligned a ps) = traverse (Align.align a) ps

--------------------------------------------------------------------------------

cmBin :: MultiPatch s c a -> Rec (Flap a) '[Bin.Meta]
cmBin p = cm $ Bin.Meta { Bin.mNullTerminates = mpNullTerminates p }

cmCompare :: MultiPatch s v a -> Rec (Flap a) '[Compare.Meta v]
cmCompare p = cm $ Compare.Meta { Compare.mCompare = mpCompare p }

cmBinPrep :: MultiPatch s c a -> Rec (Flap a) '[Const Bin.MetaPrep]
cmBinPrep p = cm $ Const $ Bin.MetaPrep { Bin.mpMaxBytes = mpMaxBytes p }

cmAlign :: MultiPatch Integer c a -> Rec (Flap a) '[Const (Align.Meta Int)]
cmAlign p = cm $ Const $ Align.Meta { Align.mExpected = fromInteger <$> mpAligned p }

cm :: f a -> Rec (Flap a) '[f]
cm fa = Flap fa :& RNil

--------------------------------------------------------------------------------

-- | Convenience function to wrap a single meta into an 'HFunctorList'.
metaWrap1 :: f a -> HFunctorList '[f] a
metaWrap1 = HFunctorList . cm

-- | Convenience function for the empty 'HFunctorList'.
metaEmpty :: HFunctorList '[] a
metaEmpty = HFunctorList RNil

--------------------------------------------------------------------------------

convertBackBin :: forall v s a. Patch s '[Compare.Meta v, Bin.Meta] a -> MultiPatch s v a
convertBackBin p = MultiPatch { mpData           = patchData p
                              , mpSeek           = patchSeek p
                              , mpCompare        = Compare.mCompare @v $ hflGet $ patchMeta p
                              , mpNullTerminates = Bin.mNullTerminates $ hflGet $ patchMeta p
                              , mpMaxBytes       = Nothing
                              , mpAligned        = Nothing
                              }