    {-# LANGUAGE CPP #-}
    {-# LANGUAGE OverloadedStrings #-}
    {-# LANGUAGE TemplateHaskell #-}
    module System.Taffybar.DBus.Client.Util where

    import           Control.Applicative
    import           DBus.Generation
    import qualified DBus.Internal.Types as T
    import qualified DBus.Introspection as I
    import qualified Data.Char as Char
    import           Data.Coerce
    import           Data.Maybe
    import           Language.Haskell.TH
    import           StatusNotifier.Util (getIntrospectionObjectFromFile)

    #if __GLASGOW_HASKELL__ >= 802
    deriveShowAndEQ :: [DerivClause]
    deriveShowAndEQ =
      [DerivClause Nothing [ConT ''Eq, ConT ''Show]]
    #endif

    buildDataFromNameTypePairs :: Name -> [(Name, Type)] -> Dec
    buildDataFromNameTypePairs name pairs =
      DataD [] name [] Nothing [RecC name (map mkVarBangType pairs)]
    #if __GLASGOW_HASKELL__ >= 802
            deriveShowAndEQ
    #else
            []
    #endif
      where mkVarBangType (fieldName, fieldType) =
              (fieldName, Bang NoSourceUnpackedness NoSourceStrictness, fieldType)

    standaloneDeriveEqShow :: Name -> [Dec]
    #if __GLASGOW_HASKELL__ < 802
    standaloneDeriveEqShow name =
      [ StandaloneDerivD [] (ConT ''Eq `AppT` ConT name)
      , StandaloneDerivD [] (ConT ''Show `AppT` ConT name)
      ]
    #else
    standaloneDeriveEqShow _ = []
    #endif

    type GetTypeForName = String -> T.Type -> Maybe Type

    data RecordGenerationParams = RecordGenerationParams
      { recordName :: Maybe String
      , recordPrefix :: String
      , recordTypeForName :: GetTypeForName
      }

    defaultRecordGenerationParams :: RecordGenerationParams
    defaultRecordGenerationParams = RecordGenerationParams
      { recordName = Nothing
      , recordPrefix = "_"
      , recordTypeForName = const $ const Nothing
      }

    generateGetAllRecord
      :: RecordGenerationParams
      -> GenerationParams
      -> I.Interface
      -> Q [Dec]
    generateGetAllRecord
                   RecordGenerationParams
                   { recordName = recordNameString
                   , recordPrefix = prefix
                   , recordTypeForName = getTypeForName
                   }
                   GenerationParams { getTHType = getArgType }
                   I.Interface { I.interfaceName = interfaceName
                               , I.interfaceProperties = properties
                               } = do
      let theRecordName =
            maybe (mkName $ map Char.toUpper $ filter Char.isLetter $ coerce interfaceName)
                  mkName recordNameString
      let getPairFromProperty I.Property
                                { I.propertyName = propName
                                , I.propertyType = propType
                                } =
                                ( mkName $ prefix ++ propName
                                , fromMaybe (getArgType propType) $ getTypeForName propName propType
                                )
          getAllRecord =
            buildDataFromNameTypePairs
            theRecordName $ map getPairFromProperty properties
      return $ getAllRecord:standaloneDeriveEqShow theRecordName

    generateClientFromFile :: RecordGenerationParams -> GenerationParams -> Bool -> FilePath -> Q [Dec]
    generateClientFromFile recordGenerationParams params useObjectPath filepath = do
      object <- getIntrospectionObjectFromFile filepath "/"
      let interface = head $ I.objectInterfaces object
          actualObjectPath = I.objectPath object
          realParams =
            if useObjectPath
              then params {genObjectPath = Just actualObjectPath}
              else params
          (<++>) = liftA2 (++)
      generateGetAllRecord recordGenerationParams params interface <++>
        generateClient realParams interface <++>
        generateSignalsFromInterface realParams interface