{-# LANGUAGE QuasiQuotes  #-}
{-# LANGUAGE ViewPatterns #-}
module Summoner.Template
       ( createProjectTemplate
       ) where
import Relude
import Data.List (delete)
import NeatInterpolation (text)
import Summoner.Default (defaultGHC, endLine)
import Summoner.GhcVer (GhcVer (..), baseVer, latestLts, showGhcVer)
import Summoner.License (License (..))
import Summoner.ProjectData (CustomPrelude (..), ProjectData (..))
import Summoner.Text (intercalateMap, packageToModule)
import Summoner.Tree (TreeFs (..))
import qualified Data.Text as T
memptyIfFalse :: Monoid m => Bool -> m -> m
memptyIfFalse p val = if p then val else mempty
createProjectTemplate :: ProjectData -> TreeFs
createProjectTemplate ProjectData{..} = Dir (toString repo) $
    [ File (toString repo <> ".cabal")
           ( createCabalTop
          <> memptyIfFalse github createCabalGit
          <> memptyIfFalse isLib createCabalLib
          <> memptyIfFalse isExe
                        ( createCabalExe
                        $ memptyIfFalse isLib $ ", " <> repo )
          <> memptyIfFalse test
                        ( createCabalTest
                        $ memptyIfFalse isLib $ ", " <> repo )
          <> memptyIfFalse bench
                        ( createCabalBenchmark
                        $ memptyIfFalse isLib $ ", " <> repo )
           )
    , File "README.md" readme
    , File "CHANGELOG.md" changelog
    , File "LICENSE" $ unLicense licenseText
    ]
 ++ createCabalFiles
 ++ memptyIfFalse stack (createStackYamls testedVersions)
 ++ [File ".gitignore" gitignore | github]
 ++ [File ".travis.yml" travisYml | travis]
 ++ [File "appveyor.yml" appVeyorYml | appVey]
 ++ maybe [] (\x -> [File ".stylish-haskell.yaml" x]) stylish
 ++ maybe [] (\x -> [File "CONTRIBUTING.md" x]) contributing
  where
    license = show licenseName
    
    libModuleName :: Text
    libModuleName = packageToModule repo
    preludeMod :: Text
    preludeMod = case prelude of
        Nothing -> ""
        Just _  -> "Prelude"
    customPreludePack :: Text
    customPreludePack = case prelude of
        Nothing          -> ""
        Just Prelude{..} -> ", " <> cpPackage
    
    createCabalTop :: Text
    createCabalTop =
        [text|
        cabal-version:       1.24
        name:                $repo
        version:             0.0.0
        description:         $description
        synopsis:            $description
        $githubHomepage
        $githubBugReports
        license:             $license
        license-file:        LICENSE
        author:              $nm
        maintainer:          $email
        copyright:           $year $nm
        category:            $category
        build-type:          Simple
        extra-doc-files:     README.md
                           , CHANGELOG.md
        tested-with:         $testedGhcs
        $endLine
        |]
      where
        githubHomepage :: Text  =  memptyIfFalse github $ "homepage:            https://github.com/" <> owner <> "/" <> repo
        githubBugReports :: Text = memptyIfFalse github $ "bug-reports:         https://github.com/" <> owner <> "/" <> repo <> "/issues"
    testedGhcs :: Text
    testedGhcs = intercalateMap ", " (mappend "GHC == " . showGhcVer) testedVersions
    defaultExtensions :: Text
    defaultExtensions = case extensions of
        [] -> ""
        xs -> "default-extensions:  " <> T.intercalate "\n                     " xs
    createCabalGit :: Text
    createCabalGit =
        [text|
        source-repository head
          type:                git
          location:            https://github.com/${owner}/${repo}.git
        $endLine
        |]
    ghcOptions :: Text
    ghcOptions = case warnings of
        [] -> defaultWarnings
        xs -> T.intercalate "\n" xs
    defaultWarnings :: Text
    defaultWarnings =
        [text|
        -Wincomplete-uni-patterns
        -Wincomplete-record-updates
        -Wcompat
        -Widentities
        $versionWarnings
        |]
    versionWarnings :: Text
    versionWarnings
        =  memptyIfFalse (testedVersions `hasLeast` Ghc801)
            "-Wredundant-constraints\n"
        <> memptyIfFalse (testedVersions `hasLeast` Ghc822)
            "-fhide-source-paths\n"
        <> memptyIfFalse (testedVersions `hasLeast` Ghc843)
            [text|
            -Wmissing-export-lists
            -Wpartial-fields
            |]
      where
        hasLeast list el = all (>= el) list
    createCabalLib :: Text
    createCabalLib =
        [text|
        library
          hs-source-dirs:      src
          exposed-modules:     $libModuleName
                               $preludeMod
          ghc-options:         -Wall
                               $ghcOptions
          build-depends:       $base
                             $customPreludePack
          default-language:    Haskell2010
          $defaultExtensions
        $endLine
        |]
    createCabalExe :: Text -> Text
    createCabalExe r =
        [text|
        executable $repo
          hs-source-dirs:      app
          main-is:             Main.hs
          ghc-options:         -Wall
                               -threaded
                               -rtsopts
                               -with-rtsopts=-N
                               $ghcOptions
          build-depends:       $base
                             $r
                             $customPreludePack
          default-language:    Haskell2010
          $defaultExtensions
        $endLine
        |]
    createCabalTest :: Text -> Text
    createCabalTest r =
        [text|
        test-suite ${repo}-test
          type:                exitcode-stdio-1.0
          hs-source-dirs:      test
          main-is:             Spec.hs
          ghc-options:         -Wall
                               -threaded
                               -rtsopts
                               -with-rtsopts=-N
                               $ghcOptions
          build-depends:       $base
                             $r
                             $customPreludePack
          default-language:    Haskell2010
          $defaultExtensions
        $endLine
        |]
    createCabalBenchmark :: Text -> Text
    createCabalBenchmark r =
        [text|
        benchmark ${repo}-benchmark
          type:                exitcode-stdio-1.0
          hs-source-dirs:      benchmark
          main-is:             Main.hs
          ghc-options:         -Wall
                               -threaded
                               -rtsopts
                               -with-rtsopts=-N
                               $ghcOptions
          build-depends:       $base
                             , gauge
                             $r
                             $customPreludePack
          default-language:    Haskell2010
          $defaultExtensions
        $endLine
        |]
    createCabalFiles :: [TreeFs]
    createCabalFiles =
        [ Dir "app"       [exeFile]               | isExe ]
     ++ [ Dir "test"      [testFile]              | test  ]
     ++ [ Dir "benchmark" [benchmarkFile]         | bench ]
     ++ [ Dir "src"       $ libFile : preludeFile | isLib ]
    testFile :: TreeFs
    testFile = File "Spec.hs"
        [text|
        main :: IO ()
        main = putStrLn ("Test suite not yet implemented" :: String)
        $endLine
        |]
    libFile :: TreeFs
    libFile = File (toString libModuleName <> ".hs")
        [text|
        module $libModuleName
               ( someFunc
               ) where
        someFunc :: IO ()
        someFunc = putStrLn ("someFunc" :: String)
        $endLine
        |]
    preludeFile :: [TreeFs]
    preludeFile = case prelude of
        Nothing -> []
        Just Prelude{..} -> one $ File "Prelude.hs"
            [text|
            -- | Uses [$cpPackage](https://hackage.haskell.org/package/${cpPackage}) as default Prelude.
            module Prelude
                   ( module $cpModule
                   ) where
            import $cpModule
            $endLine
            |]
    exeFile :: TreeFs
    exeFile = File "Main.hs" $ if isLib then createExe else createOnlyExe
    createOnlyExe :: Text
    createOnlyExe =
        [text|
        main :: IO ()
        main = putStrLn ("Hello, world!" :: String)
        $endLine
        |]
    createExe :: Text
    createExe =
        [text|
        import $libModuleName (someFunc)
        main :: IO ()
        main = someFunc
        $endLine
        |]
    benchmarkFile :: TreeFs
    benchmarkFile = File "Main.hs"
      [text|
      import Gauge.Main
      main :: IO ()
      main = defaultMain [bench "const" (whnf const ())]
      $endLine
      |]
    
    readme :: Text
    readme = T.intercalate "\n" $
        [ "# " <> repo
        , ""
        , hackage
        , licenseBadge
        ]
     ++ [stackLtsBadge | stack]
     ++ [stackNightlyBadge | stack]
     ++ [travisBadge | travis]
     ++ [appVeyorBadge | appVey]
     ++ [""
        , description
        ]
      where
        hackageShield :: Text =
            "https://img.shields.io/hackage/v/" <> repo <> ".svg"
        hackageLink :: Text =
            "https://hackage.haskell.org/package/" <> repo
        hackage :: Text = makeBadge "Hackage" hackageShield hackageLink
        licenseShield :: Text =
            "https://img.shields.io/badge/license-" <> T.replace "-" "--" license <> "-blue.svg"
        licenseBadge :: Text =
            makeBadge (license <> " license") licenseShield "LICENSE"
        stackShieldLts :: Text =
            "http://stackage.org/package/" <> repo <> "/badge/lts"
        stackLinkLts :: Text =
            "http://stackage.org/lts/package/" <> repo
        stackShieldNightly :: Text =
            "http://stackage.org/package/" <> repo <> "/badge/nightly"
        stackLinkNightly :: Text =
            "http://stackage.org/nightly/package/" <> repo
        stackLtsBadge :: Text =
            makeBadge "Stackage Lts" stackShieldLts stackLinkLts
        stackNightlyBadge :: Text =
            makeBadge "Stackage Nightly" stackShieldNightly stackLinkNightly
        travisShield :: Text =
            "https://secure.travis-ci.org/" <> owner <> "/" <> repo <> ".svg"
        travisLink :: Text =
            "https://travis-ci.org/" <> owner <> "/" <> repo
        travisBadge :: Text =
            makeBadge "Build status" travisShield travisLink
        appVeyorShield :: Text =
            "https://ci.appveyor.com/api/projects/status/github/" <> owner <> "/" <> repo <> "?branch=master&svg=true"
        appVeyorLink :: Text =
            "https://ci.appveyor.com/project/" <> owner <> "/" <> repo
        appVeyorBadge :: Text =
            makeBadge "Windows build status" appVeyorShield appVeyorLink
        makeBadge :: Text -> Text -> Text -> Text
        makeBadge title shield link = "[](" <> link <> ")"
    
    gitignore :: Text
    gitignore =
        [text|
        ### Haskell
        dist
        dist-*
        cabal-dev
        *.o
        *.hi
        *.chi
        *.chs.h
        *.dyn_o
        *.dyn_hi
        *.prof
        *.aux
        *.hp
        *.eventlog
        .virtualenv
        .hsenv
        .hpc
        .cabal-sandbox/
        cabal.sandbox.config
        cabal.config
        cabal.project.local
        .ghc.environment.*
        .HTF/
        # Stack
        .stack-work/
        ### IDE/support
        # Vim
        [._]*.s[a-v][a-z]
        [._]*.sw[a-p]
        [._]s[a-v][a-z]
        [._]sw[a-p]
        *~
        tags
        # IntellijIDEA
        .idea/
        .ideaHaskellLib/
        *.iml
        # Atom
        .haskell-ghc-mod.json
        # VS
        .vscode/
        # Emacs
        *#
        .dir-locals.el
        TAGS
        # other
        .DS_Store
        $endLine
        |]
    
    changelog :: Text
    changelog =
        [text|
        # Change log
        `$repo` uses [PVP Versioning][1].
        $githubLine
        0.0.0
        =====
        * Initially created.
        [1]: https://pvp.haskell.org
        $githubFootNote
        |]
      where
        githubLine :: Text = memptyIfFalse github $ "The change log is available [on GitHub][2]."
        githubFootNote :: Text = memptyIfFalse github $
            "[2]: https://github.com/" <> owner <> "/" <> repo <> "/releases"
    
    travisYml :: Text
    travisYml =
        let travisStackMtr = memptyIfFalse stack $
                T.concat (map travisStackMatrixItem $ delete defaultGHC testedVersions)
                    <> travisStackMatrixDefaultItem
            travisCabalMtr = memptyIfFalse cabal $
                T.concat $ map travisCabalMatrixItem testedVersions
            installAndScript =
                if cabal
                then if stack
                     then installScriptBoth
                     else installScriptCabal
                else installScriptStack
            travisCabalCache = memptyIfFalse cabal "- \"$HOME/.cabal\""
            travisStackCache = memptyIfFalse stack
                [text|
                - "$$HOME/.stack"
                - "$$TRAVIS_BUILD_DIR/.stack-work"
                |]
        in
        [text|
        sudo: true
        language: haskell
        git:
          depth: 5
        cache:
          directories:
          $travisCabalCache
          $travisStackCache
        matrix:
          include:
          $travisCabalMtr
          $travisStackMtr
        $installAndScript
        notifications:
          email: false
        $endLine
        |]
    cabalTest :: Text
    cabalTest = if test then "cabal new-test" else "echo 'No tests'"
    travisCabalMatrixItem :: GhcVer -> Text
    travisCabalMatrixItem (showGhcVer -> ghcV) =
        [text|
        - ghc: ${ghcV}
          env: GHCVER='${ghcV}' CABALVER='head'
          os: linux
          addons:
            apt:
              sources:
              - hvr-ghc
              packages:
              - ghc-${ghcV}
              - cabal-install-head
        $endLine
        |]
    travisStackMatrixItem :: GhcVer -> Text
    travisStackMatrixItem (showGhcVer -> ghcV) =
        [text|
        - ghc: ${ghcV}
          env: GHCVER='${ghcV}' STACK_YAML="$$TRAVIS_BUILD_DIR/stack-$$GHCVER.yaml"
          os: linux
          addons:
            apt:
              packages:
              - libgmp-dev
          $endLine
        |]
    travisStackMatrixDefaultItem :: Text
    travisStackMatrixDefaultItem = let defGhc = showGhcVer defaultGHC in
        [text|
        - ghc: ${defGhc}
          env: GHCVER='${defGhc}' STACK_YAML="$$TRAVIS_BUILD_DIR/stack.yaml"
          os: linux
          addons:
            apt:
              packages:
              - libgmp-dev
        $endLine
        |]
    installScriptBoth :: Text
    installScriptBoth =
        [text|
        install:
          - |
            if [ -z "$$STACK_YAML" ]; then
              export PATH="/opt/ghc/$$GHCVER/bin:/opt/cabal/$$CABALVER/bin:$$PATH"
              echo $$PATH
              cabal new-update
              cabal new-build --enable-tests --enable-benchmarks
            else
              mkdir -p ~/.local/bin
              export PATH="$$HOME/.local/bin:$$PATH"
              travis_retry curl -L 'https://www.stackage.org/stack/linux-x86_64' | tar xz --wildcards --strip-components=1 -C ~/.local/bin '*/stack'
              stack --version
              stack setup --no-terminal --install-cabal 2.0.1.0
              stack ghc -- --version
              stack build --only-dependencies --no-terminal
            fi
        script:
          - |
            if [ -z "$$STACK_YAML" ]; then
               ${cabalTest}
            else
              stack build --test --bench --no-run-benchmarks --no-terminal
            fi
        $endLine
        |]
    installScriptCabal :: Text
    installScriptCabal =
        [text|
        install:
          - export PATH="/opt/ghc/$$GHCVER/bin:/opt/cabal/$$CABALVER/bin:$$PATH"
          - echo $$PATH
          - cabal new-update
          - cabal new-build --enable-tests --enable-benchmarks
        script:
          - ${cabalTest}
        $endLine
        |]
    installScriptStack :: Text
    installScriptStack =
        [text|
        install:
          - mkdir -p ~/.local/bin
          - export PATH="$$HOME/.local/bin:$$PATH"
          - travis_retry curl -L 'https://www.stackage.org/stack/linux-x86_64' | tar xz --wildcards --strip-components=1 -C ~/.local/bin '*/stack'
          - stack --version
          - stack setup --no-terminal --install-cabal 2.0.1.0
          - stack ghc -- --version
          - stack build --only-dependencies --no-terminal
        script:
          - stack build --test --bench --no-run-benchmarks --no-terminal
        $endLine
        |]
    
    createStackYamls :: [GhcVer] -> [TreeFs]
    createStackYamls = map createStackYaml
      where
        createStackYaml :: GhcVer -> TreeFs
        createStackYaml ghcV = let ver = case ghcV of
                                      Ghc843 -> ""
                                      _      -> "-" <> showGhcVer ghcV
            in stackYaml ver (latestLts ghcV) (baseVer ghcV)
          where
            stackYaml :: Text -> Text -> Text -> TreeFs
            stackYaml ghc lts baseV = File (toString $ "stack" <> ghc <> ".yaml")
                [text|
                resolver: lts-${lts}
                $extraDeps
                $ghcOpts
                $endLine
                |]
              where
                extraDeps :: Text
                extraDeps = case prelude of
                    Nothing -> ""
                    Just _  -> "extra-deps: [base-noprelude-" <> baseV <> "]"
                ghcOpts :: Text
                ghcOpts = if ghcV <= Ghc802 then
                            ""
                          else
                            [text|
                            ghc-options:
                              "$$locals": -fhide-source-paths
                            |]
    
    appVeyorYml :: Text
    appVeyorYml =
        [text|
        build: off
        before_test:
        # http://help.appveyor.com/discussions/problems/6312-curl-command-not-found
        - set PATH=C:\Program Files\Git\mingw64\bin;%PATH%
        - curl -sS -ostack.zip -L --insecure http://www.stackage.org/stack/windows-i386
        - 7z x stack.zip stack.exe
        clone_folder: "c:\\stack"
        environment:
          global:
            STACK_ROOT: "c:\\sr"
        test_script:
        - stack setup > nul
        # The ugly echo "" hack is to avoid complaints about 0 being an invalid file
        # descriptor
        - echo "" | stack --no-terminal build --bench --no-run-benchmarks --test
        |]