-- | Generation of UModule-level AST fragments for refactorings.
-- The bindings defined here create a the annotated version of the AST constructor with the same name.
-- For example, @mkModule@ creates the annotated version of the @UModule@ AST constructor.
{-# LANGUAGE MonoLocalBinds #-}
{-# LANGUAGE OverloadedStrings #-}

module Language.Haskell.Tools.Rewrite.Create.Modules where

import Data.String (IsString(..), String)
import Language.Haskell.Tools.AST
import Language.Haskell.Tools.PrettyPrint.Prepare
import Language.Haskell.Tools.Rewrite.Create.Names (mkStringNode)
import Language.Haskell.Tools.Rewrite.Create.Utils
import Language.Haskell.Tools.Rewrite.ElementTypes

-- | The representation of a haskell module, that is a separate compilation unit.
-- It may or may not have a header.
mkModule :: [FilePragma] -> Maybe ModuleHead -> [ImportDecl] -> [Decl] -> Module
mkModule filePrags head imps decls
  = mkAnn (child <> child <> child <> child)
      $ UModule (mkAnnList (followedBy "\n" $ separatedBy "\n" list) filePrags) (mkAnnMaybe opt head)
                (mkAnnList (after "\n" $ indented list) imps) (mkAnnList (after "\n" $ indented list) decls)

-- | Module declaration with name and (optional) exports
mkModuleHead :: ModuleName -> Maybe ModulePragma -> Maybe ExportSpecs -> ModuleHead
mkModuleHead n pr es = mkAnn ("module " <> child <> child <> child <> " where")
                         $ UModuleHead n (mkAnnMaybe (after "\n" opt) pr) (mkAnnMaybe opt es)

-- | A list of export specifications surrounded by parentheses
mkExportSpecs :: [ExportSpec] -> ExportSpecs
mkExportSpecs = mkAnn ("(" <> child <> ")") . UExportSpecs . mkAnnList (separatedBy ", " list)

-- | Export a name and related names
mkExportSpec :: IESpec -> ExportSpec
mkExportSpec = mkAnn child . UDeclExport

-- | The export of an imported module (@ module A @)
mkModuleExport :: ModuleName -> ExportSpec
mkModuleExport = mkAnn ("module " <> child) . UModuleExport

-- | Marks a name to be imported or exported with related names (subspecifier)
mkIESpec :: Name -> Maybe SubSpec -> IESpec
mkIESpec name ss = mkAnn (child <> child <> child) (UIESpec noth name (mkAnnMaybe (after "(" $ followedBy ")" opt) ss))

-- | Marks a pattern synonym to be imported or exported
mkPatternIESpec :: Name -> IESpec
mkPatternIESpec name = mkAnn (child <> child) (UIESpec (justVal $ mkAnn child UImportPattern) name noth)

-- | @(a,b,c)@: a class exported with some of its methods, or a datatype exported with some of its constructors.
mkSubList :: [Name] -> SubSpec
mkSubList = mkAnn child . USubSpecList . mkAnnList (separatedBy ", " list)

-- | @(..)@: a class exported with all of its methods, or a datatype exported with all of its constructors.
mkSubAll :: SubSpec
mkSubAll = mkAnn ".." USubSpecAll

-- | An import declaration: @import Module.Name@
mkImportDecl :: Bool -> Bool -> Bool -> Maybe String -> ModuleName -> Maybe ModuleName -> Maybe ImportSpec
                  -> ImportDecl
mkImportDecl source qualified safe pkg name rename spec
  = mkAnn ("import " <> child <> child <> child <> child <> child <> child <> child) $
      UImportDecl (if source then justVal (mkAnn "{-# SOURCE #-} " UImportSource) else noth)
                  (if qualified then justVal (mkAnn "qualified " UImportQualified) else noth)
                  (if safe then justVal (mkAnn "safe " UImportSafe) else noth)
                  (case pkg of Just str -> justVal (mkStringNode str); _ -> noth)
                  name (mkAnnMaybe opt (fmap (mkAnn (" as " <> child) . UImportRenaming) rename)) (mkAnnMaybe opt spec)

-- | Restrict the import definition to ONLY import the listed names
mkImportSpecList :: [IESpec] -> ImportSpec
mkImportSpecList = mkAnn ("(" <> child <> ")") . UImportSpecList . mkAnnList (separatedBy ", " list)

-- | Restrict the import definition to DONT import the listed names
mkImportHidingList :: [IESpec] -> ImportSpec
mkImportHidingList = mkAnn (" hiding (" <> child <> ")") . UImportSpecHiding . mkAnnList (separatedBy ", " list)

-- | The name of a module
mkModuleName :: String -> ModuleName
mkModuleName s = mkAnn (fromString s) (UModuleName s)

-- * Pragmas

mkFilePragmas :: [FilePragma] -> FilePragmaList
mkFilePragmas = mkAnnList (separatedBy "\n" list)

-- | @LANGUAGE@ pragma, listing the enabled language extensions in that file
mkLanguagePragma :: [String] -> FilePragma
mkLanguagePragma extensions
  = mkAnn ("{-# LANGUAGE " <> child <> " #-}") $ ULanguagePragma
      $ mkAnnList (separatedBy ", " list) (map (\ext -> mkAnn (fromString ext) (ULanguageExtension ext)) extensions)

-- | @OPTIONS@ pragma, possibly qualified with a tool, e.g. OPTIONS_GHC
mkOptionsGHC :: String -> FilePragma
mkOptionsGHC opts
  = mkAnn ("{-# OPTIONS_GHC " <> child <> " #-}") $ UOptionsPragma
      $ mkStringNode opts

-- | A warning pragma attached to the module
mkModuleWarningPragma :: [String] -> ModulePragma
mkModuleWarningPragma msg
  = mkAnn ("{-# WARNING " <> child <> " #-}") $ UModuleWarningPragma
      $ mkAnnList (separatedBy " " list) $ map mkStringNode msg

-- | A deprecated pragma attached to the module
mkModuleDeprecatedPragma :: [String] -> ModulePragma
mkModuleDeprecatedPragma msg
  = mkAnn ("{-# DEPRECATED " <> child <> " #-}") $ UModuleDeprecatedPragma
      $ mkAnnList (separatedBy " " list) $ map mkStringNode msg
