{-# LANGUAGE DeriveFoldable    #-}
{-# LANGUAGE DeriveFunctor     #-}
{-# LANGUAGE DeriveGeneric     #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE FlexibleContexts  #-}


module Data.Variation
  (
  -- * Variation
    Variation(..)

  -- * Lenses
    , nominal, variations
  ) where

import           Control.DeepSeq
import           Data.Functor.Apply
import           Data.Functor.Bind
import           Data.Functor.Classes
import           Data.Monoid1
import           Data.Semigroup
import           Data.Serialize       (Serialize)
import           GHC.Generics


-- | the variation type contains
--
--   [@_nominal@] : a nominal value that will always exist
--
--   [@_variations@] : alternative values which are held inside a container of
--                     type @f@
--
-- it is strict in both arguments.
--
-- the 'Applicative' instance uses the 'Unit1' instance of @f@ to define pure
--
-- > pure x = Variation x empty1
--
-- and the 'Bind' and 'Append1' instances of @f@ to define '<*>'
--
-- > Variation f fs <*> Variation x xs =
-- >   Variation
-- >     (f x)
-- >     ((fs <.> xs) `append1` (f <$> xs) `append1` (($ x) <$> fs))
--
-- the 'Monad' instance uses the 'Bind' instance of @f@ ('join') to collapse
-- collections of type @f (f a)@
--
-- > joinV :: (Bind f, Monoid1 f) => Variation f (Variation f a) -> Variation f a
-- > joinV (Variation (Variation nn nv) v) =
-- >   let vv = _variations <$> v
-- >       vn = _nominal <$> v
-- >   in Variation nn $ join vv `append1` vn `append1` nv
--
-- other useful instances:
--
-- > instance Append1 f => Semigroup (Variation f a) where
-- >   (<>) = append1
--
-- > instance (Monoid a, Monoid1 f) => Monoid (Variation f a) where
-- >   mempty = Variation mempty empty1
-- >   mappend = (<>)



data Variation f a =
  Variation
    { _nominal    :: !a
    , _variations :: !(f a)
    } deriving (Generic, Functor, Foldable, Traversable)


nominal :: Functor f => (a -> f a) -> Variation t a -> f (Variation t a)
nominal f (Variation n v) = flip Variation v <$> f n

variations :: Functor f => (t a -> f (t a)) -> Variation t a -> f (Variation t a)
variations f (Variation n v) = Variation n <$> f v


instance (NFData a, NFData (f a)) => NFData (Variation f a)

instance (Serialize a, Serialize (f a)) => Serialize (Variation f a) where


-- some thoughts:
-- the requirements of Apply f and Monoid1 f appear to be related to
-- the Align typeclass in the "these" package.
-- there's something going on there.

-- what if we want to use ZipList here? it seems there is no monad instance
-- for ZipList, which makes it difficult to use (Variation ZipList a)...

instance (Apply f, Monoid1 f) => Applicative (Variation f) where
  pure = flip Variation empty1
  Variation f fs <*> Variation x xs =
    Variation
      (f x)
      ((fs <.> xs) `append1` (f <$> xs) `append1` (($ x) <$> fs))


joinV :: (Bind f, Monoid1 f) => Variation f (Variation f a) -> Variation f a
joinV (Variation (Variation nn nv) v) =
  let vv = _variations <$> v
      vn = _nominal <$> v
  in Variation nn $ join vv `append1` vn `append1` nv


instance (Bind f, Monoid1 f) => Monad (Variation f) where
  return = pure
  p >>= f = joinV $ f <$> p


instance Append1 f => Append1 (Variation f) where
  Variation x xs `append1` Variation _ ys = Variation x (xs `append1` ys)


instance Append1 f => Semigroup (Variation f a) where
  (<>) = append1


instance (Monoid a, Monoid1 f) => Monoid (Variation f a) where
  mempty = Variation mempty empty1
  mappend = (<>)


instance Show1 f => Show1 (Variation f) where
  liftShowsPrec f g n (Variation x xs) =
    showsBinaryWith f (liftShowsPrec f g) "Variation" n x xs


instance (Show1 f, Show a) => Show (Variation f a) where
  showsPrec = showsPrec1