{-# LANGUAGE
  FlexibleInstances,
  TemplateHaskell #-}

module Inspection.Boilerplate where

import Control.Applicative (liftA2)
import Language.Haskell.TH

import Generic.Data

{- Example output this generates (modulo reordering):
eqEmptyR, eqEmptyS, eqEmptyG :: Empty a -> Empty a -> Bool
eqEmptyR = \_ _ -> True
eqEmptyS = (==)
eqEmptyG = geq
-}

class AppendQ q where
  ($++) :: q -> DecsQ -> DecsQ

  infixr 2 $++

instance AppendQ (Q Dec) where
  ($++) = liftA2 (:)

instance AppendQ (Q [Dec]) where
  ($++) = liftA2 (++)

instance AppendQ q => AppendQ [q] where
  ps $++ qs = foldr ($++) qs ps

type Top = Name -> ExpQ -> DecsQ

mk_ :: String -> Maybe Name -> Name -> (TypeQ -> TypeQ) -> Top
mk_ bname fname_ gname ty_ tname ref = do
  nameR <- newName (bname ++ nameBase tname ++ "R")  -- Reference
  nameS <- newName (bname ++ nameBase tname ++ "S")  -- Stock
  nameG <- newName (bname ++ nameBase tname ++ "G")  -- Generic
  let ty = ty_ (conT tname)
      stock = case fname_ of
        Nothing -> pure []
        Just fname ->
              sigD nameS ty
          $++ funD' nameS (varE fname)
          $++ pure []
  (     sigD nameR ty
    $++ sigD nameG ty
    $++ funD' nameR ref
    $++ funD' nameG (varE gname)
    $++ stock
    $++ pure [] )

funD' :: Name -> ExpQ -> DecQ
funD' name body = funD name [clause [] (normalB body) []]

--

newVar :: String -> Q TypeQ
newVar x = varT <$> newName x

-- Eq and Ord

-- Sometimes there isn't an Eq constraint on the parameter.
mk_eq_ :: (TypeQ -> TypeQ) -> Top
mk_eq_ = mk_ "eq" (Just '(==)) 'geq

mk_eq :: Top
mk_eq = mk_eq_ ty where
  ty f = do
    a <- newVar "a"
    [t| Eq $a => $f $a -> $f $a -> Bool |]

mk_eq' :: Top
mk_eq' = mk_eq_ ty where
  ty f = do
    a <- newVar "a"
    [t| $f $a -> $f $a -> Bool |]

-- Sometimes there isn't an Ord constraint on the parameter.
mk_compare_ :: (TypeQ -> TypeQ) -> Top
mk_compare_ = mk_ "compare" (Just 'compare) 'gcompare

mk_compare :: Top
mk_compare = mk_compare_ ty where
  ty f = do
    a <- newVar "a"
    [t| Ord $a => $f $a -> $f $a -> Ordering |]

mk_compare' :: Top
mk_compare' = mk_compare_ ty where
  ty f = do
    a <- newVar "a"
    [t| $f $a -> $f $a -> Ordering |]

-- Functor, Foldable, Traversable

mk_fmap :: Top
mk_fmap = mk_ "fmap" (Just 'fmap) 'gfmap ty where
  ty f = do
    a <- newVar "a"
    b <- newVar "b"
    [t| ($a -> $b) -> $f $a -> $f $b |]

mk_foldMap :: Top
mk_foldMap = mk_ "foldMap" (Just 'foldMap) 'gfoldMap ty where
  ty f = do
    a <- newVar "a"
    m <- newVar "m"
    [t| Monoid $m => ($a -> $m) -> $f $a -> $m |]

mk_foldr :: Top
mk_foldr = mk_ "foldr" (Just 'foldr) 'gfoldr ty where
  ty f = do
    a <- newVar "a"
    b <- newVar "b"
    [t| ($a -> $b -> $b) -> $b -> $f $a -> $b |]

mk_traverse :: Top
mk_traverse = mk_ "traverse" (Just 'traverse) 'gtraverse ty where
  ty f = do
    a <- newVar "a"
    b <- newVar "b"
    g <- newVar "g"
    [t| Applicative $g => ($a -> $g $b) -> $f $a -> $g ($f $b) |]

mk_sequenceA :: Top
mk_sequenceA = mk_ "sequenceA" (Just 'sequenceA) 'gsequenceA ty where
  ty f = do
    a <- newVar "a"
    g <- newVar "g"
    [t| Applicative $g => $f ($g $a) -> $g ($f $a) |]

-- Applicative (no stock deriving)

mk_ap :: Top
mk_ap = mk_ "ap" Nothing 'gap ty where
  ty f = do
    a <- newVar "a"
    b <- newVar "b"
    [t| $f ($a -> $b) -> $f $a -> $f $b |]

mk_liftA2 :: Top
mk_liftA2 = mk_ "liftA2" Nothing 'gliftA2 ty where
  ty f = do
    a <- newVar "a"
    b <- newVar "b"
    c <- newVar "c"
    [t| ($a -> $b -> $c) -> $f $a -> $f $b -> $f $c |]

