{-# LANGUAGE OverloadedStrings #-}

module Wingman.Simplify
  ( simplify
  ) where

import Data.Generics (GenericT, everywhere, mkT)
import Data.List.Extra (unsnoc)
import Data.Monoid (Endo (..))
import Development.IDE.GHC.Compat
import GHC.SourceGen (var)
import GHC.SourceGen.Expr (lambda)
import Wingman.CodeGen.Utils
import Wingman.GHC (containsHsVar, fromPatCompat, pattern SingleLet)


------------------------------------------------------------------------------
-- | A pattern over the otherwise (extremely) messy AST for lambdas.
pattern Lambda :: [Pat GhcPs] -> HsExpr GhcPs -> HsExpr GhcPs
pattern Lambda pats body <-
  HsLam _
    MG {mg_alts = L _ [L _
      Match { m_pats = fmap fromPatCompat -> pats
            , m_grhss = GRHSs {grhssGRHSs = [L _ (
                 GRHS _ [] (L _ body))]}
            }]
        }
  where
    -- If there are no patterns to bind, just stick in the body
    Lambda [] body   = body
    Lambda pats body = lambda pats body



------------------------------------------------------------------------------
-- | Simlify an expression.
simplify :: LHsExpr GhcPs -> LHsExpr GhcPs
simplify
  = (!!3) -- Do three passes; this should be good enough for the limited
          -- amount of gas we give to auto
  . iterate (everywhere $ foldEndo
    [ simplifyEtaReduce
    , simplifyRemoveParens
    , simplifyCompose
    , simplifySingleLet
    ])


------------------------------------------------------------------------------
-- | Like 'foldMap' but for endomorphisms.
foldEndo :: Foldable t => t (a -> a) -> a -> a
foldEndo = appEndo . foldMap Endo


------------------------------------------------------------------------------
-- | Perform an eta reduction. For example, transforms @\x -> (f g) x@ into
-- @f g@.
simplifyEtaReduce :: GenericT
simplifyEtaReduce = mkT $ \case
  Lambda
      [VarPat _ (L _ pat)]
      (HsVar _ (L _ a)) | pat == a ->
    var "id"
  Lambda
      (unsnoc -> Just (pats, VarPat _ (L _ pat)))
      (HsApp _ (L _ f) (L _ (HsVar _ (L _ a))))
      | pat == a
        -- We can only perform this simplifiation if @pat@ is otherwise unused.
      , not (containsHsVar pat f) ->
    Lambda pats f
  x -> x

------------------------------------------------------------------------------
-- | Eliminates the unnecessary binding in @let a = b in a@
simplifySingleLet :: GenericT
simplifySingleLet = mkT $ \case
  SingleLet bind [] val (HsVar _ (L _ a)) | a == bind -> val
  x -> x


------------------------------------------------------------------------------
-- | Perform an eta-reducing function composition. For example, transforms
-- @\x -> f (g (h x))@ into @f . g . h@.
simplifyCompose :: GenericT
simplifyCompose = mkT $ \case
  Lambda
      (unsnoc -> Just (pats, VarPat _ (L _ pat)))
      (unroll -> (fs@(_:_), HsVar _ (L _ a)))
      | pat == a
        -- We can only perform this simplifiation if @pat@ is otherwise unused.
      , not (containsHsVar pat fs) ->
    Lambda pats (foldr1 (infixCall ".") fs)
  x -> x


------------------------------------------------------------------------------
-- | Removes unnecessary parentheses on any token that doesn't need them.
simplifyRemoveParens :: GenericT
simplifyRemoveParens = mkT $ \case
  HsPar _ (L _ x) | isAtomicHsExpr x -> x
  (x :: HsExpr GhcPs)                -> x


------------------------------------------------------------------------------
-- | Unrolls a right-associative function application of the form
-- @HsApp f (HsApp g (HsApp h x))@ into @([f, g, h], x)@.
unroll :: HsExpr GhcPs -> ([HsExpr GhcPs], HsExpr GhcPs)
unroll (HsPar _ (L _ x)) = unroll x
unroll (HsApp _ (L _ f) (L _ a)) =
  let (fs, r) = unroll a
   in (f : fs, r)
unroll x = ([], x)

