-----------------------------------------------------------------------------

-- Copyright 2019, Ideas project team. This file is distributed under the

-- terms of the Apache License 2.0. For more information, see the files

-- "LICENSE.txt" and "NOTICE.txt", which are included in the distribution.

-----------------------------------------------------------------------------

-- |

-- Maintainer  :  bastiaan.heeren@ou.nl

-- Stability   :  provisional

-- Portability :  portable (depends on ghc)

--

-----------------------------------------------------------------------------


module Ideas.Common.Rewriting.AutoTerm (toTermG, fromTermG, testTermFor) where

import Control.Monad.State
import Data.Data
import Ideas.Common.Rewriting.Term
import Ideas.Utils.Prelude (headM)

toTermG :: Data a => a -> Term
toTermG a =
   case constrRep constr of
      IntConstr n   -> TNum n                  -- for Int and Integer

      FloatConstr r -> TFloat (fromRational r) -- for Double and Float

      CharConstr c  -> TVar [c]
      AlgConstr _   ->
         case cast a of
            Just s  -> TVar s -- for String

            Nothing -> makeTerm constr (gfoldl op e a)
 where
   op (M xs) x = M (xs ++ [toTermG x])
   e _    = M []
   constr = toConstr a

newtype M a = M [Term]

-- test for list constructors

makeTerm :: Constr -> M a -> Term
makeTerm c (M xs) =
   case xs of
      [y, TList ys] | isCons  -> TList (y:ys)
      []            | isNil   -> TList []
      _ -> TCon (constrSymbol c) xs
 where
   txt = showConstr c
   isNil   = txt == "[]"
   isCons  = txt == "(:)"

isTuple :: String -> Bool
isTuple ('(':xs) = rec xs
 where
   rec ")"      = True
   rec (',':ys) = rec ys
   rec _        = False
isTuple _       = False

------------------------------------------------------------------------


constrSymbol :: Constr -> Symbol
constrSymbol c
   | txt == "[]"  = nilSymbol
   | txt == "(:)" = consSymbol
   | isTuple txt  = tupleSymbol
   | otherwise    = newSymbol (dataTypeName (constrType c) `mappend` show c)
 where
   txt = showConstr c

nilSymbol, consSymbol, tupleSymbol :: Symbol
nilSymbol   = newSymbol "list.nil"
consSymbol  = newSymbol "list.cons"
tupleSymbol = newSymbol "tuple"

constructors :: Data a => Proxy a -> [Constr]
constructors = dataTypeConstrs . dataTypeOf . fromProxy
 where
   fromProxy :: Proxy a -> a
   fromProxy = error "fromProxy"

findConstr :: (Monad m, Data a) => Proxy a -> Symbol -> m Constr
findConstr p s =
   headM [ c | c <- constructors p, s == constrSymbol c ]

fromTermG :: (MonadPlus m, Data a) => Term -> m a
fromTermG term =
   case term of
      TCon s xs -> fromTermTConG Proxy s xs
      TVar [c]  -> castM c `mplus` castM [c]
      TVar s    -> castM s
      TList xs  -> fromTermG (foldr cons nil xs)
      TNum n    -> castM n `mplus` castM (fromInteger n :: Int)
      TFloat d  -> castM d `mplus` castM (doubleToFloat d)
      TMeta _   -> fail "fromTermG: found TMeta"
 where
   cons = binary consSymbol
   nil  = symbol nilSymbol

castM :: (Monad m, Typeable a, Typeable b) => a -> m b
castM = maybe (fail "fromTermG") return . cast

doubleToFloat :: Double -> Float
doubleToFloat = fromRational . toRational

fromTermTConG :: (MonadPlus m, Data a) => Proxy a -> Symbol -> [Term] -> m a
fromTermTConG p s xs = do
   c <- findConstr p s
   evalStateT (gunfold op return c) xs
 where
   op m = do
      f <- m
      t <- pop
      a <- lift (fromTermG t)
      return (f a)

pop :: Monad m => StateT [a] m a
pop = do
   ts <- get
   case ts of
      []    -> fail "pop"
      hd:tl -> put tl >> return hd

--------------


testTermFor :: (Data a, Eq a) => a -> Bool
testTermFor x = fromTermG (toTermG x) == Just x