{-# LANGUAGE GADTs, FlexibleContexts, Rank2Types, KindSignatures
           , MultiParamTypeClasses, FunctionalDependencies
           , FlexibleInstances, UndecidableInstances
           , TypeFamilies
           , EmptyDataDecls  -- temporary
  #-}
{-# OPTIONS_GHC -Wall #-}
----------------------------------------------------------------------
-- |
-- Module      :  Shady.Language.Glom
-- Copyright   :  (c) Conal Elliott 2009
-- License     :  AGPLv3
-- 
-- Maintainer  :  conal@conal.net
-- Stability   :  experimental
-- 
-- Typed conglomerate of values
----------------------------------------------------------------------

module Shady.Language.Glom
  ( FunctorU(..), MonadU(..)
  , Glom(..), foldG, mapAG
  -- , Glommable(..), Unglommable(..)
  ) where

import Control.Applicative (Applicative(..),liftA2)

import Text.PrettyPrint.Leijen
import Text.PrettyPrint.Leijen.PrettyPrec
import Text.PrettyPrint.Leijen.DocExpr

import Shady.Language.Type (HasType,PairF(..),UnitF(..))

infixr 7 :*

-- | Map a polymorphic function over a conglomerate (preserving
-- structure).  The required laws are the same as with 'Functor'.
class FunctorU q where
  fmapU :: (forall a.   f a ->   g a)
        -> (forall a. q f a -> q g a)

-- TODO: fill in ApplicativeU

class FunctorU m => MonadU m where
  returnU :: f a -> m f a
  extendU :: (forall a.   f a -> m g a)
          -> (forall a. m f a -> m g a)

-- TODO: does FunctorU already have a name?

-- | A typed conglomerate of values
data Glom f a where
  BaseG :: f a -> Glom f a
  UnitG :: Glom f ()
  (:*)  :: (HasType a, HasType b, Show a, Show b) =>
           Glom f a -> Glom f b -> Glom f (a,b)

instance UnitF (Glom f) where unit = UnitG
instance PairF (Glom f) where (#)  = (:*)

instance FunctorU Glom where
  fmapU h (BaseG x) = BaseG (h x)
  fmapU _ UnitG     = UnitG
  fmapU h (p :* q)  = fmapU h p :* fmapU h q

-- | Applicative/monadic map over a 'Glom'.
mapAG :: Applicative m =>
         (forall a.      f a -> m (     g a)) ->
         (forall a. Glom f a -> m (Glom g a))
mapAG h (BaseG x) = fmap BaseG (h x)
mapAG _ UnitG     = pure UnitG
mapAG h (p :* q)  = liftA2 (:*) (mapAG h p) (mapAG h q)

-- Like the tree/substitution monad
instance MonadU Glom where
  returnU             = BaseG
  extendU h (BaseG x) = h x
  extendU _ UnitG     = UnitG
  extendU h (p :* q)  = extendU h p :* extendU h q

-- | Fold over a 'Glom', given handlers for '(:*)', 'UnitG', and 'BaseG',
-- respectively.
foldG :: (c -> c -> c) -> c -> (forall b. f b -> c)
      -> Glom f a -> c
foldG k e f (a :* b)  = foldG k e f a `k` foldG k e f b
foldG _ e _ UnitG     = e
foldG _ _ f (BaseG x) = f x


-- Convert a type to an 'Expr' for unparsing
instance HasExprU f => HasExprU (Glom f) where
  exprU (BaseG x) = exprU x
  exprU UnitG     = var "()"
  exprU (t :* t') = op InfixR 1 ":*" (exprU t) (exprU t')

instance (HasExpr a, HasExprU f) => HasExpr (Glom f a) where expr = exprU

-- Idea: convert a glom into a Doc glom.

instance (HasExpr a, HasExprU f) => PrettyPrec (Glom f a) where 
  prettyPrec p = prettyPrec p . expr
instance (HasExpr a, HasExprU f) => Pretty     (Glom f a) where 
  pretty       = prettyPrec 0
instance (HasExpr a, HasExprU f) => Show       (Glom f a) where 
  show         = show . pretty


-- Examples:

-- newtype Sink a = Sink { sink :: a -> IO () }

-- type Type      = Glom VectorT
-- type Pat       = Glom V
-- type Sinks     = Glom Sink
-- type UniformsE = Glom E

{-

{--------------------------------------------------------------------
    Composing & decomposing Gloms
--------------------------------------------------------------------}

class Glommable u f a | u f -> a where
  glom :: u -> Glom f a

instance Glommable () f () where glom () = UnitG

instance (Glommable ua f a, Glommable ub f b, HasExpr a, HasExpr b) =>
         Glommable (ua,ub) f (a,b) where
  glom (ua,ub)  = glom ua :* glom ub

-- Template to specialize per f:
-- 
--   instance Glommable (f a) f a where glom = BaseG

class Unglommable u f a | a f -> u where
  unglom :: Glom f a -> u

instance Unglommable () f () where unglom _ = ()

instance (Unglommable ua f a, Unglommable ub f b) =>
         Unglommable (ua,ub) f (a,b) where
  unglom (ga :* gb)  = (unglom ga, unglom gb)
  unglom _ = error "unglom: oops"  -- :(

-- Template to specialize per (non-unit, non-pair) t:
-- 
--  instance Unglommable (f t) f t where unglom = unglomId

-- | Unglom a non-unit, non-pair
unglomId :: Glom f a -> f a
unglomId (BaseG ea)  = ea
unglomId _           = error "unglomId: not BaseG.  wtf?"

instance Unglommable (f Int  ) f Int   where unglom = unglomId
instance Unglommable (f Bool ) f Bool  where unglom = unglomId
instance Unglommable (f Float) f Float where unglom = unglomId

instance Unglommable (f (Vec1 a)) f (Vec1 a) where unglom = unglomId
instance Unglommable (f (Vec2 a)) f (Vec2 a) where unglom = unglomId
instance Unglommable (f (Vec3 a)) f (Vec3 a) where unglom = unglomId
instance Unglommable (f (Vec4 a)) f (Vec4 a) where unglom = unglomId

-}