{-# LANGUAGE TemplateHaskell #-}
module Data.GCode.TH where

import Language.Haskell.TH

import qualified Data.Char

-- this walks constructors of a datatype
-- and creates isXYZ checks and CodeMod constructors
-- for example for constructor `Rapid` these two are generated
-- isRapid :: Code -> Bool
-- isRapid x = x `codeIsRS274` Rapid
--
-- rapid :: Code
-- rapid = codeFromName Rapid
genShortcuts :: Name -> Q [Dec]
genShortcuts names = do
  info <- reify names
  case info of
    TyConI (DataD _cxt _name _tyvarbndr _kind constructors _deriv)
      -> do
        a <- mapM genTests constructors
        b <- mapM genConstructors constructors
        return $ a ++ b
    _ -> error "Unexpected reify input for genShortcuts"

  where
    genTests (NormalC name _bangs) = do
      varName <- newName "x"
      let
        funName = mkName $ "is" ++ (nameBase name)

      return $ FunD funName
        [ Clause
           [VarP varName]
           (NormalB (InfixE (Just (VarE varName)) (VarE (mkName "codeIsRS274")) (Just (ConE name))))
           []
        ]
    genTests _ = error "Unexpteced input for genTests"

    genConstructors (NormalC name _bangs) = do
      let
        funName = mkName $ (\(x:rest) -> (Data.Char.toLower x : rest)) (nameBase name)
      return $ FunD funName
        [ Clause
          []
          (NormalB ( (VarE (mkName "codeFromName")) `AppE` (ConE name)) )
          []
        ]
    genConstructors _ = error "Unexpteced input for genConstructors"

-- this walks constructors of a datatype
-- and creates constructors to be used in writer monad
--
-- for example for constructor `Move` these two are generated
-- move' :: Control.Monad.Trans.Writer.Lazy.Writer (Endo Program) ()
-- move' = generateName Move
--
-- and a wariant accepting Code endofunctor so we can do move' and also move (xy 2 3)
-- move :: (Code -> Code) -> Control.Monad.Trans.Writer.Lazy.Writer (Endo Program) ()
-- move fn = generateNameArgs Move fn
--
-- We prefer variant with args as it seems to be more common
-- to have GCodes with arguments than just standalone ones.
genWriterEndos :: Name -> Q [Dec]
genWriterEndos names = do
  info <- reify names
  case info of
    TyConI (DataD _cxt _name _tyvarbndr _kind constructors _deriv)
      -> do
        a <- mapM genConstructors constructors
        b <- mapM genConstructorsArgs constructors
        return $ a ++ b
    _ -> error "Unexpected reify input for genWriterEndos"

  where
    genConstructors (NormalC name _bangs) = do
      let
        funName = mkName $ (\(x:rest) -> (Data.Char.toLower x : rest ++ "'")) (nameBase name)
      return $ FunD funName
        [ Clause
          []
          (NormalB ( (VarE (mkName "generateName")) `AppE` (ConE name)) )
          []
        ]
    genConstructors _ = error "Unexpteced input for genConstructors"

    genConstructorsArgs (NormalC name _bangs) = do
      endoName <- newName "x"
      let
        funName = mkName $ (\(x:rest) -> (Data.Char.toLower x : rest)) (nameBase name)
      return $ FunD funName
        [ Clause
          [VarP endoName]
          (NormalB (((VarE (mkName "generateNameArgs")) `AppE` (ConE name)) `AppE` (VarE endoName)) )
          []
        ]
    genConstructorsArgs _ = error "Unexpteced input for genConstructorArgs"