module Composite.TH where

import Composite.Record (Record, rlens)
import Control.Lens (_1, _head, each, over, toListOf)
import Data.Char (toLower)
import Data.Monoid ((<>))
import Data.Proxy (Proxy(Proxy))
import Data.Vinyl.Lens (RElem)
import Data.Vinyl.TypeLevel (RIndex)
import Language.Haskell.TH (Q, Body(NormalB), Dec(SigD, ValD), Exp(VarE), Name, Pat(VarP), Type(AppT, ConT), TyVarBndr, mkName, nameBase)
import Language.Haskell.TH.Lens (_TySynD)

-- |Make 'Proxy' definitions for each of the @type@ synonyms in the given block of declarations. The proxies have the same names as the synonyms but with
-- the first letter lowercased.
--
-- For example:
--
-- @
--   withProxies [d|
--     type FFoo = "foo" :-> Int
--     |]
-- @
--
-- Is equivalent to:
--
-- @
--   type FFoo = "foo" :-> Int
--   fFoo :: Proxy FFoo
--   fFoo = Proxy
-- @
--
-- __Note:__ the trailing @|]@ of the quasi quote bracket has to be indented or a parse error will occur.
withProxies :: Q [Dec] -> Q [Dec]
withProxies qDecs = do
  decs <- qDecs
  proxyDecs <- traverse proxyDecForName (toListOf (each . _TySynD . _1) decs)
  pure $ decs <> concat proxyDecs
  where
    proxyDecForName tySynName = do
      let tySynType = pure $ ConT tySynName
          proxyName = mkName . over _head toLower . nameBase $ tySynName
      proxyType <- [t|Proxy $tySynType|]
      proxyVal <- [|Proxy|]
      pure
        [ SigD proxyName proxyType
        , ValD (VarP proxyName) (NormalB proxyVal) []
        ]

-- |Make 'rlens' and 'Proxy' definitions for each of the @type@ synonyms in the given block of declarations. The lenses have the same names as the synonyms
-- but with the first letter lowercased. The proxies have that name but with _ suffix.
--
-- For example:
--
-- @
--   withLensesAndProxies [d|
--     type FFoo = "foo" :-> Int
--     |]
-- @
--
-- Is equivalent to:
--
-- @
--   type FFoo = "foo" :-> Int
--   fFoo :: RElem FFoo rs (RIndex FFoo rs) => Lens' (Record rs) Int
--   fFoo = rlens fFoo_
--   fFoo_ :: Proxy FFoo
--   fFoo_ = Proxy
-- @
--
-- __Note:__ the trailing @|]@ of the quasi quote bracket has to be indented or a parse error will occur.
withLensesAndProxies :: Q [Dec] -> Q [Dec]
withLensesAndProxies qDecs = do
  decs <- qDecs
  proxyDecs <- traverse proxyDecForName $ toListOf (each . _TySynD . _1) decs
  lensDecs <- traverse lensDecForName $ toListOf (each . _TySynD) decs
  pure $ decs <> concat proxyDecs <> concat lensDecs
  where
    proxyNameForTypeName = mkName . (++ "_") . over _head toLower . nameBase

    proxyDecForName :: Name -> Q [Dec]
    proxyDecForName tySynName = do
      let tySynType = pure $ ConT tySynName
          proxyName = proxyNameForTypeName tySynName
      proxyType <- [t|Proxy $tySynType|]
      proxyVal <- [|Proxy|]
      pure
        [ SigD proxyName proxyType
        , ValD (VarP proxyName) (NormalB proxyVal) []
        ]

    lensDecForName :: (Name, [TyVarBndr], Type) -> Q [Dec]
    lensDecForName (tySynName, _, AppT (AppT (ConT (nameBase -> ":->")) _) valTy) = do -- FIXME stop doing name hacks
      let tySynType = pure $ ConT tySynName
          proxyName = proxyNameForTypeName tySynName
          proxyVal  = VarE proxyName
          lensName  = mkName . over _head toLower . nameBase $ tySynName
      lensType <- [t|forall f rs. (Functor f, RElem $tySynType rs (RIndex $tySynType rs)) => ($(pure valTy) -> f $(pure valTy)) -> Record rs -> f (Record rs)|]
      rlensVal <- [|rlens $(pure proxyVal)|]
      pure
        [ SigD lensName lensType
        , ValD (VarP lensName) (NormalB rlensVal) [] ]
    lensDecForName (tySynName, _, _) =
      fail $ "Can only make lenses and proxies for type synonyms like type FField = \"field\" :-> Type, but " <> nameBase tySynName <> " has some other form of type"