{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE ScopedTypeVariables #-}
-- | Fresh monad and the Name type class
module Tip.Fresh where

import Tip.Utils
import Tip.Pretty
import Control.Applicative hiding (empty)
import Control.Monad.State.Strict
import Control.Arrow ((&&&))

import Data.Foldable (Foldable)

-- | The Fresh monad
newtype Fresh a = Fresh (State Int a)
  deriving (Monad, Applicative, Functor, MonadFix)

-- | Continues making unique names after the highest
--   numbered name in a foldable value.
freshPass :: (Foldable f,Name a) => (f a -> Fresh b) -> f a -> b
f `freshPass` x = f x `freshFrom` x

-- | Run fresh from starting from the greatest unique in a structure
freshFrom :: (Foldable f,Name b) => Fresh a -> f b -> a
freshFrom m x = runFreshFrom (succ (maximumOn getUnique x)) m

-- | Run fresh, starting from zero
runFresh :: Fresh a -> a
runFresh (Fresh m) = evalState m 0

-- | Run fresh from some starting value
runFreshFrom :: Int -> Fresh a -> a
runFreshFrom n (Fresh m) = evalState m (n+1)

-- | The Name type class
class (PrettyVar a, Ord a) => Name a where
  -- | Make a fresh name
  fresh   :: Fresh a

  -- | Refresh a name, which could have some resemblance to the original
  -- name
  refresh :: a -> Fresh a
  refresh _ = fresh

  -- | Make a fresh name that can incorporate the given string
  freshNamed :: String -> Fresh a
  freshNamed _ = fresh

  -- | Refresh a name with an additional hint string
  refreshNamed :: String -> a -> Fresh a
  refreshNamed s n = freshNamed (s ++ varStr n)

  -- | Gets the unique associated with a name.
  getUnique :: a -> Int

instance Name Int where
  fresh     = Fresh (state (id &&& succ))
  getUnique = id

instance Name a => Name (PPVar a) where
  fresh = fmap PPVar fresh
  refresh = fmap PPVar . refresh . unPPVar
  freshNamed = fmap PPVar . freshNamed
  refreshNamed s n = fmap PPVar (refreshNamed s (unPPVar n))