{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeSynonymInstances #-}

{-# OPTIONS_GHC -Wno-missing-fields #-}

module Nix.TH where

import           Data.Fix
import           Data.Foldable
import           Data.Generics.Aliases
import           Data.Set (Set)
import qualified Data.Set as Set
import qualified Data.Text as Text
import           Language.Haskell.TH
import           Language.Haskell.TH.Quote
import           Nix.Atoms
import           Nix.Expr
import           Nix.Parser

quoteExprExp :: String -> ExpQ
quoteExprExp s = do
    expr <- case parseNixTextLoc (Text.pack s) of
        Failure err -> fail $ show err
        Success e   -> return e
    dataToExpQ (const Nothing `extQ` metaExp (freeVars expr)) expr

quoteExprPat :: String -> PatQ
quoteExprPat s = do
    expr <- case parseNixTextLoc (Text.pack s) of
        Failure err -> fail $ show err
        Success e   -> return e
    dataToPatQ (const Nothing `extQ` metaPat (freeVars expr)) expr

freeVars :: NExprLoc -> Set VarName
freeVars = cata $ \case
    NSym_ _ var -> Set.singleton var
    Compose (Ann _ x) -> fold x

class ToExpr a where
    toExpr :: a -> NExprLoc

instance ToExpr NExprLoc where
    toExpr = id

instance ToExpr VarName where
    toExpr = Fix . NSym_ nullSpan

instance ToExpr Int where
    toExpr = Fix . NConstant_ nullSpan . NInt . fromIntegral

instance ToExpr Integer where
    toExpr = Fix . NConstant_ nullSpan . NInt

instance ToExpr Float where
    toExpr = Fix . NConstant_ nullSpan . NFloat

metaExp :: Set VarName -> NExprLoc -> Maybe ExpQ
metaExp fvs (Fix (NSym_ _ x)) | x `Set.member` fvs =
    Just [| toExpr $(varE (mkName (Text.unpack x))) |]
metaExp _ _ = Nothing

metaPat :: Set VarName -> NExprLoc -> Maybe PatQ
metaPat fvs (Fix (NSym_ _ x)) | x `Set.member` fvs =
    Just (varP (mkName (Text.unpack x)))
metaPat _ _ = Nothing

nix :: QuasiQuoter
nix = QuasiQuoter
    { quoteExp = quoteExprExp
    , quotePat = quoteExprPat
    }