{-# LANGUAGE ExistentialQuantification, RankNTypes, Rank2Types #-}

module Chatty.Expansion where

import Data.List
import Control.Arrow
import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Trans.Class
import System.Environment hiding (getEnv)
import System.Posix.Env (getEnv, setEnv)
--import System.SetEnv

data EnvVar = NotSet | Literal String | forall a.Show a => Scalar a | Array [EnvVar]

instance Show EnvVar where
  show (Scalar s) = show s
  show (Literal s) = s
  show (Array ps) = unwords $ map show ps
  show NotSet = ""

newtype ExpanderT m a = Expander {
    runExpanderT :: [(String,EnvVar)] -> m (a,[(String,EnvVar)])
  }

instance Monad m => Monad (ExpanderT m) where
  return a = Expander $ \vs -> return (a,vs)
  (Expander e) >>= f = Expander $ \vs -> do (a,vs') <- e vs; runExpanderT (f a) vs'

instance MonadTrans ExpanderT where
  lift m = Expander $ \vs -> do a <- m; return (a,vs)

instance MonadIO m => MonadIO (ExpanderT m) where
  liftIO = lift . liftIO

instance Monad m => Functor (ExpanderT m) where
  fmap f a = Expander $ \vs -> do (a',vs') <- runExpanderT a vs; return (f a',vs')

localEnvironment :: Functor m => ExpanderT m a -> m a
localEnvironment m = fmap fst $ runExpanderT m []

forkEnvironment :: (Functor m,Monad m,MonadIO m) => ExpanderT m a -> m a
forkEnvironment m = do
  es <- liftIO getEnvironment
  fmap fst $ runExpanderT m $ fmap (second Literal) es

exportAll :: (Monad m,MonadIO m) => ExpanderT m ()
exportAll = Expander $ \vs -> do
  liftIO $ forM_ vs $ \(k,v) -> setEnv k (show v) True
  return ((),vs)

class Monad ee => ExpanderEnv ee where
  mgetv :: String -> ee EnvVar
  mputv :: String -> EnvVar -> ee ()

instance Monad m => ExpanderEnv (ExpanderT m) where
  mgetv s = Expander $ \vs -> return $
    case filter ((==s).fst) vs of
      [] -> (NotSet,vs)
      ((_,v):_) -> (v,vs)
  mputv k v = Expander $ \vs -> return ((),(k,v):filter ((/=k).fst) vs)

instance ExpanderEnv IO where
  mgetv = fmap (\v -> case v of Nothing -> NotSet; Just v' -> Literal v') . getEnv
  mputv k v = setEnv k (show v) True

class Monad e => MonadExpand e where
  expand :: String -> e String

instance MonadExpand IO where
  expand = expandVars

instance Monad m => MonadExpand (ExpanderT m) where
  expand = expandVars

expandVars :: (Monad m,Functor m,ExpanderEnv m) => String -> m String
expandVars [] = return []
expandVars ('$':ss) =
  let nm = takeWhile isAnum ss
      rm = dropWhile isAnum ss
  in do
    v <- fmap show $ mgetv nm
    r <- expandVars rm
    return (v++r)
expandVars (s:ss) = do ss' <- expandVars ss; return (s:ss')

isAnum = (`elem` (['a'..'z']++['A'..'Z']++"_"++['0'..'9']))