{-# language UndecidableInstances #-}
module Church.FromChurch where
import GHC.Generics
import Church.TF
import Prelude

data Traverse a c = Meta a c a | InL a a | InR a a | Term a

type family PathArg (t :: [Traverse * Meta]) where
  PathArg (Term a     ': '[] ) = a
  PathArg ('Meta a b p ': rest) = PathArg rest
  PathArg (InR l p    ': rest) = PathArg rest
  PathArg (InL r p    ': rest) = PathArg rest

-- | Construct a product type where all the fields are
-- _|_
class GEmpty a where empty :: a
instance GEmpty (U1 p) where empty = U1
instance GEmpty (K1 a t p) where empty = K1 (error "Error! The impossible has happened.")
instance GEmpty (f p) => GEmpty (M1 a b f p) where empty = M1 empty
instance (GEmpty (l p), GEmpty (r p)) => GEmpty ((:*:) l r p) where empty = empty :*: empty

type family MakeProdPaths v (m :: [Traverse * Meta]) (r :: [ [Traverse * Meta] ]) :: [[Traverse * Meta]] where
  MakeProdPaths (K1 a t p) s all    = Reverse (Term (K1 a t p) ': s) ': all
  MakeProdPaths (M1 a b f p) s all  = MakeProdPaths (f p) ('Meta a b p ': s) all
  MakeProdPaths ((:*:) l r p) s all =
    Append (MakeProdPaths (l p) (InL (r p) p ': s) '[])
            (Append (MakeProdPaths (r p) (InR (l p) p ': s) '[]) all)

class GUpdate (path :: [Traverse * Meta]) a where update :: a -> PathArg path -> a
instance GUpdate (Term (K1 a t p) ': '[]) (K1 a t p) where update _ a = a
instance GUpdate rest (l p) => GUpdate (InL (r p) p ': rest) ((:*:) l r p) where
  update (l :*: r) a = update @rest l a :*: r
instance GUpdate rest (r p) => GUpdate (InR (l p) p ': rest) ((:*:) l r p) where
  update (l :*: r) a = l :*: update @rest r a
instance GUpdate rest (f p) => GUpdate ('Meta a b p ': rest) (M1 a b f p) where
  update (M1 f) a = M1 (update @rest f a)

type family Fill (paths :: [[Traverse * Meta]]) r where
  Fill (x ': xs) r = StripK (PathArg x) -> Fill xs r
  Fill '[] r = r

class GFill (paths :: [[Traverse * Meta]]) a where
  fill :: (a -> r) -> a -> Fill paths r
instance GFill '[] a where
  fill f a = f a
instance (PathArg x ~ K1 m t p, StripK (PathArg x) ~ t, GUpdate x a, GFill xs a) =>
         GFill (x ': xs) a where
  fill f a = \x -> fill @xs f $ update @x a (K1 x)

type family MakePaths v (m :: [Traverse * Meta]) (r :: [ [Traverse * Meta] ]) :: [[Traverse * Meta]] where
  MakePaths ((:+:) l r p) s all =
    Append (MakePaths (l p) (InL (r p) p ': s) '[])
            (Append (MakePaths (r p) (InR (l p) p ': s) '[]) all)
  MakePaths (M1 a b f p) s all  = MakePaths (f p) ('Meta a b p ': s) all
  MakePaths (K1 a t p) s all    =  Reverse (Term (K1 a t p) ': s)    ': all
  MakePaths (U1 p) s all        =  Reverse (Term (U1 p) ': s)        ': all
  MakePaths ((:*:) l r p) s all =  Reverse (Term ((:*:) l r p) ': s) ': all

type family ReconstructPath (t :: [Traverse * Meta]) where
  ReconstructPath (InL r p  ': rest) = (WithoutParam (ReconstructPath rest) :+: WithoutParam r) p
  ReconstructPath (InR l p  ': rest) = (WithoutParam l :+: WithoutParam (ReconstructPath rest)) p
  ReconstructPath ('Meta a b p ': rest) = M1 a b (WithoutParam (ReconstructPath rest)) p
  ReconstructPath (Term a     ': '[])  = a

class GPath' (p :: [Traverse * Meta]) where path' :: PathArg p -> ReconstructPath p
instance GPath' (Term a ': '[]) where path' = id
instance ((WithoutParam (ReconstructPath rest)) p ~ ReconstructPath rest, GPath' rest)
         => GPath' (InR r p ': rest) where path' a = R1 $ path' @rest a
instance ((WithoutParam (ReconstructPath rest)) p ~ ReconstructPath rest, GPath' rest)
         => GPath' (InL l p ': rest) where path' a = L1 $ path' @rest a
instance ((WithoutParam (ReconstructPath rest)) p ~ ReconstructPath rest, GPath' rest)
         => GPath' ('Meta a b p ': rest) where path' a = M1 $ path' @rest a

class GBuild (paths :: [[Traverse * Meta]]) f r where build :: f -> r
-- | Unit case. This represents constructors with no arguments
instance (ReconstructPath x ~ r, GPath' x, GBuild xs f' r, PathArg x ~ U1 p)
         => GBuild (x ': xs) (r -> f') r where build f = build @xs $ f (path' @x U1)
instance ((f -> g) ~ Fill (MakeProdPaths (PathArg x) '[] '[]) r,
          ReconstructPath x ~ r, GEmpty (PathArg x), GBuild xs f' r,
          (GFill (MakeProdPaths (PathArg x) '[] '[]) (PathArg x)),
          GPath' x)
         => GBuild (x ': xs) ((f -> g) -> f') r where
  build f = build @xs $ f (fill @(MakeProdPaths (PathArg x) '[] '[]) (path' @x) empty)
instance GBuild '[] r r where build = id