{-# LANGUAGE CPP #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
{-
 This example simplifies a reified graph so only nodes
 referenced from multiple places are assigned labels,
 and unshared terms are folded into the parent by
 changing the type of the graph to use the free
 monad (Free e) over the original functor e.
 -}
module Main (main) where

-- to define simplification
import qualified Data.Map.Strict as Map
import           Data.Map.Strict (Map)
import           Data.Reify (Graph(Graph), Unique)
import qualified Data.Set as Set

-- for the example
import           Data.Reify (MuRef(mapDeRef), DeRef, reifyGraph)

#if !(MIN_VERSION_base(4,11,0))
import           Data.Semigroup (Semigroup(..))
#endif

#if !(MIN_VERSION_base(4,18,0))
import           Control.Applicative (liftA2)
#endif

-- Self-contained Free monad
data Free f a = Pure a | Free (f (Free f a))
deriving instance (Show a, Show (f (Free f a))) => Show (Free f a)

instance Functor f => Functor (Free f) where
  fmap f = go where
    go (Pure a)  = Pure (f a)
    go (Free fa) = Free (go <$> fa)

instance Functor f => Applicative (Free f) where
  pure = Pure
  Pure a <*> Pure b = Pure $ a b
  Pure a <*> Free mb = Free $ fmap a <$> mb
  Free ma <*> b = Free $ (<*> b) <$> ma

instance Functor f => Monad (Free f) where
#if !(MIN_VERSION_base(4,11,0))
  return = Pure
#endif
  Pure a >>= f = f a
  Free m >>= f = Free (fmap (>>= f) m)

newtype Hist a = Hist (Map a Int)
  deriving Show
count :: a -> Hist a
count x = Hist (Map.singleton x 1)

instance (Ord a) => Semigroup (Hist a) where
  (<>) (Hist m1) (Hist m2) = Hist (Map.unionWith (+) m1 m2)

instance (Ord a) => Monoid (Hist a) where
  mempty = Hist Map.empty
#if !(MIN_VERSION_base(4,11,0))
  mappend (Hist m1) (Hist m2) = Hist (Map.unionWith (+) m1 m2)
#endif
  mconcat hists = Hist (Map.unionsWith (+) [m | Hist m <- hists])

-- Count the number of times each Unique is referenced
-- in the graph.
occs :: (Foldable e) => Graph e -> Hist Unique
occs (Graph binds root) = count root `mappend` foldMap (foldMap count . snd) binds

-- nest unshared nodes into parents.
simpl :: (Functor e, Foldable e) => Graph e -> Graph (Free e)
simpl g@(Graph binds root) =
  let Hist counts = occs g
      repeated = Map.keysSet (Map.filter (>1) counts)
      grow ix
        | Set.member ix repeated = Pure ix
        | otherwise =
            case lookup ix binds of
              Just pat -> Free (fmap grow pat)
              Nothing -> error "this shouldn't happen"
  in Graph [(k, Free (fmap grow v))
           | (k,v) <- binds, Set.member k repeated]
     root

-- A data type for the example.
data Tree a =
    Leaf a
  | Fork (Tree a) (Tree a)
  deriving (Show)
data TreeF a t =
    LeafF a
  | ForkF t t
  deriving (Show, Functor, Foldable)
instance MuRef (Tree a) where
  type DeRef (Tree a) = TreeF a
  mapDeRef _     (Leaf v) = pure $ LeafF v
  mapDeRef child (Fork l r) = liftA2 ForkF (child l) (child r)

-- An example graph.
loop1, loop2 :: Tree Int

-- loop1 is referenced twice so it must have an explicit
-- label in the simplified graph whether or not it's the root.
loop1 = Fork (Fork (Leaf 1) loop1) loop2

-- loop2 is only reference once in the graph, so it will
-- have a label in the simplified graph only if it is the root.
loop2 = Fork loop1 (Leaf 2)

main :: IO ()
main = do
  putStrLn "Simplifed graph for loop1, should have one label"
  print . simpl =<< reifyGraph loop1
  putStrLn "Simplifed graph for loop2, should have two labels"
  print . simpl =<< reifyGraph loop2
