content: {-# LANGUAGE GeneralizedNewtypeDeriving #-}
  
  {-|
  Module      : Data.Sv.Encode.Type
  Copyright   : (C) CSIRO 2017-2019
  License     : BSD3
  Maintainer  : George Wilson <george.wilson@data61.csiro.au>
  Stability   : experimental
  Portability : non-portable
  
  The core type for encoding
  -}
  
  module Data.Sv.Encode.Type (
    Encode (Encode, getEncode)
  , NameEncode (..)
  ) where
  
  import Control.Applicative (liftA2)
  import Control.Monad.Writer (Writer)
  import Data.Bifoldable (bifoldMap)
  import Data.ByteString.Builder (Builder)
  import Data.Functor.Contravariant (Contravariant (contramap))
  import Data.Functor.Contravariant.Compose (ComposeFC (ComposeFC))
  import Data.Functor.Contravariant.Divisible (Divisible (divide, conquer), Decidable (choose, lose))
  import Data.Semigroup (Semigroup ((<>)))
  import Data.Sequence (Seq)
  import Data.Void (absurd)
  
  import Data.Sv.Encode.Options
  
  -- | An 'Encode' converts its argument into one or more textual fields, to be
  -- written out as CSV.
  --
  -- It is 'Semigroup', 'Monoid', 'Contravariant', 'Divisible', and 'Decidable',
  -- allowing for composition of these values to build bigger 'Encode's
  -- from smaller ones.
  newtype Encode a =
    Encode { getEncode :: EncodeOptions -> a -> Seq Builder }
    deriving (Semigroup, Monoid)
  
  instance Contravariant Encode where
    contramap f (Encode g) = Encode $ fmap (. f) g
  
  instance Divisible Encode where
    conquer = Encode mempty
    divide f (Encode x) (Encode y) =
      Encode $ \e a -> bifoldMap (x e) (y e) (f a)
  
  instance Decidable Encode where
    lose f = Encode (const (absurd . f))
    choose f (Encode x) (Encode y) =
      Encode $ \e a -> either (x e) (y e) (f a)
  
  -- | A 'NameEncode' is an 'Encode' with an attached column name.
  --
  -- It is 'Semigroup', 'Monoid', 'Contravariant', and 'Divisible', allowing
  -- for composition of these values to build bigger 'NameEncode's
  -- from smaller ones.
  --
  -- Notably, 'NameEncode' is not 'Decidable', since taking the sum of column
  -- names does not make sense.
  newtype NameEncode a =
    NameEncode { unNamedE :: ComposeFC (Writer (Seq Builder)) Encode a}
    deriving (Contravariant, Divisible) -- intentionally not Decidable
  
  instance Semigroup (NameEncode a) where
    NameEncode (ComposeFC a) <> NameEncode (ComposeFC b) =
      NameEncode (ComposeFC (liftA2 (<>) a b))
  
  instance Monoid (NameEncode a) where
    mappend = (<>)
    mempty = NameEncode (ComposeFC (pure mempty))
  