-- |
-- Module      : Math.LaTeX.Internal.OperatorGenerator
-- Copyright   : (c) Justus Sagemüller 2017
-- License     : GPL v3
-- 
-- Maintainer  : (@) jsag $ hvl.no
-- Stability   : experimental
-- Portability : portable
-- 

{-# LANGUAGE TemplateHaskell     #-}
{-# LANGUAGE PatternSynonyms     #-}
{-# LANGUAGE QuasiQuotes         #-}
{-# LANGUAGE CPP                 #-}

module Math.LaTeX.Internal.OperatorGenerator where

import CAS.Dumb.Tree
import CAS.Dumb.Symbols

import Language.Haskell.TH
import Language.Haskell.Meta.Parse
import Data.Char

import qualified Data.HashSet as HS
import qualified Data.Hashable as HS

operatorExpression :: Fixity ->  -> CAS' γ (Infix )  s⁰
                -> CAS' γ (Infix )  s⁰ -> CAS' γ (Infix )  s⁰
operatorExpression fxty iop = symbolInfix (Infix fxty iop)

chainOperatorExpression :: (Eq , HS.Hashable )
          => Fixity -> HS.HashSet  ->  -> CAS' γ (Infix )  s⁰
                -> CAS' γ (Infix )  s⁰ -> CAS' γ (Infix )  s⁰
chainOperatorExpression fxty caste iop = chainableInfix isInCaste (Infix fxty iop)
 where isInCaste (Infix _ iop') = iop' `HS.member` caste

makeOperatorCaste :: String   -- ^ What to call the collection of operators
                  -> (Name, Name)
                              -- ^ Haskell type of the final operators
                              --   and of the infix primitives therein
                  -> Fixity
                  -> Bool     -- ^ Should the ops in this caste be chainable?
                  -> [(String, ExpQ)]
                              -- ^ The operator symbols with corresponding implementation
                  -> DecsQ
makeOperatorCaste caste (typeSig, opType) fxty isChainable ops
                = fmap concat $ (:) <$> mkCollection
                                    <*> mapM mkOp ops
 where mkCollection
        | isChainable  = do
           opRenditions <- mapM snd ops
           return
            [ SigD casteName $ ConT ''HS.HashSet `AppT` ConT opType
            , ValD (VarP casteName)
                   (NormalB
                     $ VarE 'HS.fromList
                        `AppE` ListE opRenditions )
                   []
            ]
        | otherwise    = return []
       mkOp (op, implementation) = do
        impExp <- implementation
        return
         [ InfixD fxty opName
         , SigD opName $ ConT typeSig
         , ValD (VarP opName)
                (NormalB
                   $ if isChainable
                      then VarE 'chainOperatorExpression
                             `AppE` fxtyE `AppE` VarE casteName `AppE` impExp
                      else VarE 'operatorExpression `AppE` fxtyE `AppE` impExp)
                [] ]
        where opName = mkName op
              fxtyE = case parseExp $ show fxty of
                 Right fe -> fe
                 Left err -> error err
       casteName = mkName caste