{-# LANGUAGE QuasiQuotes, ScopedTypeVariables, TypeOperators, GADTs,
  MultiParamTypeClasses, FlexibleContexts, TypeSynonymInstances,
  FlexibleInstances, UndecidableInstances, TypeFamilies #-}

{- |

Module      :  Data.Yoko.Reduce
Copyright   :  (c) The University of Kansas 2011
License     :  BSD3

Maintainer  :  nicolas.frisby@gmail.com
Stability   :  experimental
Portability :  see LANGUAGE pragmas (... GHC)

A @t@-algebra reduces a disbanded @t@ into a mediation of @t@.

-}
module Data.Yoko.Reduce
  (Alg, Algebra(..), AlgebraU(..),
   algebraFin, AlgebraDT(..), AlgebraUni, AlgebraDC(..)) where

import Type.Yoko

import Data.Yoko.Generic
import Data.Yoko.Reflect





-- | A @t@-algebra reduces a disbanded @t@ to the same mediation of @t@.
type Alg m t = Disbanded m t -> Med m t
newtype Algebra m t = Alg (Alg m t)
type instance Unwrap (Algebra m) t = Alg m t
instance Wrapper (Algebra m) where wrap = Alg; unwrap (Alg x) = x

data AlgebraU m t where
  AlgebraU :: (DT t, AlgebraDT m t) => AlgebraU m t
instance (DT t, AlgebraDT m t
         ) => t ::: AlgebraU m where inhabits = AlgebraU





algebraFin :: (AlgebraUni m (Inhabitants u), Finite u) =>
             AnRMN m u -> Med m (LeftmostRange (Inhabitants u))
algebraFin = algebraUni . finiteNP



-- | @algebraDT@ determines the algebra from the type and mediator.
class DT t => AlgebraDT m t where algebraDT :: Disbanded m t -> Med m t


-- | @algebraUni@ determines the \"algebra\" from the type-sum and mediator.
class AlgebraUni m dcs where
  algebraUni :: AnRMN m (Uni dcs) -> Med m (LeftmostRange dcs)

instance (Med m (LeftmostRange ts) ~ Med m (LeftmostRange us),
          AlgebraUni m ts, AlgebraUni m us) => AlgebraUni m (ts :+ us) where
  algebraUni = algebraUni `two` algebraUni
instance AlgebraDC m dc => AlgebraUni m (N dc) where
  algebraUni (NP (Uni (Here Refl)) x) = algebraDC x

-- | @algebraDC@ determines the \"alegbra\" from the constructor type and
-- mediator.
class DC dc => AlgebraDC m dc where algebraDC :: RMN m dc -> Med m (Range dc)



type OneOf ts = NP (Uni ts)

two :: (OneOf ts f -> a) -> (OneOf us f -> a) -> (OneOf (ts :+ us) f -> a)
two f g (NP (Uni tag) x) = case tag of
  OnLeft u -> f $ NP (Uni u) x
  OnRight v -> g $ NP (Uni v) x