{-# LANGUAGE CPP #-}
{-# LANGUAGE LambdaCase #-}
module UnitTests.Distribution.Client.Init.Golden
( tests
) where


import Test.Tasty
import Test.Tasty.Golden
import Test.Tasty.HUnit

import qualified Data.ByteString.Lazy.Char8 as BS8
import Data.List.NonEmpty (fromList)
import Data.List.NonEmpty as NEL (NonEmpty, drop)
#if __GLASGOW_HASKELL__ < 804
import Data.Semigroup ((<>))
#endif

import Distribution.Client.Init.Types
import Distribution.Simple.PackageIndex hiding (fromList)
import Distribution.Verbosity
import Distribution.Client.Types.SourcePackageDb
import Distribution.Client.Init.Interactive.Command
import Distribution.Client.Init.Format
import Distribution.Fields.Pretty
import Distribution.Types.PackageName (PackageName)
import Distribution.Client.Init.FlagExtractors
import Distribution.Simple.Flag
import Distribution.CabalSpecVersion

import System.FilePath

import UnitTests.Distribution.Client.Init.Utils
import Distribution.Client.Init.Defaults

-- -------------------------------------------------------------------- --
-- golden test suite

-- | Golden executable tests.
--
-- We test target generation against a golden file in @tests/fixtures/init/@ for
-- executables, libraries, and test targets with the following:
--
-- * Empty flags, non-simple target gen, no special options
-- * Empty flags, simple target gen, no special options
-- * Empty flags, non-simple target gen, with generated comments (no minimal setting)
-- * Empty flags, non-simple target gen, with minimal setting (no generated comments)
-- * Empty flags, non-simple target gen, minimal and generated comments set.
--
-- Additionally, we test whole @.cabal@ file generation for every combination
-- of library, lib + tests, exe, exe + tests, exe + lib, exe + lib + tests
-- and so on against the same options.
--
tests
    :: Verbosity
    -> InitFlags
    -> InstalledPackageIndex
    -> SourcePackageDb
    -> TestTree
tests v initFlags pkgIx srcDb = testGroup "golden"
    [ goldenLibTests v pkgIx pkgDir pkgName
    , goldenExeTests v pkgIx pkgDir pkgName
    , goldenTestTests v pkgIx pkgDir pkgName
    , goldenPkgDescTests v srcDb pkgDir pkgName
    , goldenCabalTests v pkgIx srcDb
    ]
  where
    pkgDir = evalPrompt (getPackageDir initFlags)
      $ fromList ["."]
    pkgName = evalPrompt (packageNamePrompt srcDb initFlags)
      $ fromList ["test-package", "y"]

goldenPkgDescTests
    :: Verbosity
    -> SourcePackageDb
    -> FilePath
    -> PackageName
    -> TestTree
goldenPkgDescTests v srcDb pkgDir pkgName = testGroup "package description golden tests"
    [ goldenVsString "Empty flags, non-simple, no comments"
      (goldenPkgDesc "pkg.golden") $
        let opts = WriteOpts False False False v pkgDir Library pkgName defaultCabalVersion
        in runPkgDesc opts emptyFlags pkgArgs

    , goldenVsString "Empty flags, non-simple, with comments"
      (goldenPkgDesc "pkg-with-comments.golden") $
        let opts = WriteOpts False False False v pkgDir Library pkgName defaultCabalVersion
        in runPkgDesc opts emptyFlags pkgArgs

    , goldenVsString "Dummy flags, with comments"
      (goldenPkgDesc "pkg-with-flags.golden") $
        let opts = WriteOpts False False False v pkgDir Library pkgName defaultCabalVersion
        in runPkgDesc opts dummyFlags pkgArgs

    , goldenVsString "Dummy flags, old cabal version, with comments"
      (goldenPkgDesc "pkg-old-cabal-with-flags.golden") $
        let opts = WriteOpts False False False v pkgDir Library pkgName defaultCabalVersion
        in runPkgDesc opts (dummyFlags {cabalVersion = Flag CabalSpecV2_0}) pkgArgs
    ]
  where
    runPkgDesc opts flags args = do
      case _runPrompt (genPkgDescription flags srcDb) args of
        Left e -> assertFailure $ show e
        Right (pkg, _) -> mkStanza $ mkPkgDescription opts pkg

goldenExeTests
    :: Verbosity
    -> InstalledPackageIndex
    -> FilePath
    -> PackageName
    -> TestTree
goldenExeTests v pkgIx pkgDir pkgName = testGroup "exe golden tests"
    [ goldenVsString "Empty flags, not simple, no options, no comments"
      (goldenExe "exe-no-comments.golden") $
        let opts = WriteOpts False False True v pkgDir Executable pkgName defaultCabalVersion
        in runGoldenExe opts exeArgs emptyFlags

    , goldenVsString "Empty flags, not simple, with comments + no minimal"
      (goldenExe "exe-with-comments.golden") $
        let opts = WriteOpts False False False v pkgDir Executable pkgName defaultCabalVersion
        in runGoldenExe opts exeArgs emptyFlags

    , goldenVsString "Empty flags, not simple, with minimal + no comments"
      (goldenExe "exe-minimal-no-comments.golden") $
        let opts = WriteOpts False True True v pkgDir Executable pkgName defaultCabalVersion
        in runGoldenExe opts exeArgs emptyFlags

    , goldenVsString "Empty flags, not simple, with minimal + comments"
      (goldenExe "exe-simple-minimal-with-comments.golden") $
        let opts = WriteOpts False True False v pkgDir Executable pkgName defaultCabalVersion
        in runGoldenExe opts exeArgs emptyFlags

    , goldenVsString "Build tools flag, not simple, with comments + no minimal"
      (goldenExe "exe-build-tools-with-comments.golden") $
        let opts = WriteOpts False False False v pkgDir Executable pkgName defaultCabalVersion
        in runGoldenExe opts exeArgs (emptyFlags {buildTools = Flag ["happy"]})
    ]
  where
    runGoldenExe opts args flags =
      case _runPrompt (genExeTarget flags pkgIx) args of
        Right (t, _) -> mkStanza [mkExeStanza opts $ t {_exeDependencies = mangleBaseDep t _exeDependencies}]
        Left e -> assertFailure $ show e

goldenLibTests
    :: Verbosity
    -> InstalledPackageIndex
    -> FilePath
    -> PackageName
    -> TestTree
goldenLibTests v pkgIx pkgDir pkgName = testGroup "lib golden tests"
    [ goldenVsString "Empty flags, not simple, no options, no comments"
      (goldenLib "lib-no-comments.golden") $
        let opts = WriteOpts False False True v pkgDir Library pkgName defaultCabalVersion
        in runGoldenLib opts libArgs emptyFlags

    , goldenVsString "Empty flags, simple, no options, no comments"
      (goldenLib "lib-simple-no-comments.golden") $
        let opts = WriteOpts False False True v pkgDir Library pkgName defaultCabalVersion
        in runGoldenLib opts libArgs emptyFlags

    , goldenVsString "Empty flags, not simple, with comments + no minimal"
      (goldenLib "lib-with-comments.golden") $
        let opts = WriteOpts False False False v pkgDir Library pkgName defaultCabalVersion
        in runGoldenLib opts libArgs emptyFlags

    , goldenVsString "Empty flags, not simple, with minimal + no comments"
      (goldenLib "lib-minimal-no-comments.golden") $
        let opts = WriteOpts False True True v pkgDir Library pkgName defaultCabalVersion
        in runGoldenLib opts libArgs emptyFlags

    , goldenVsString "Empty flags, not simple, with minimal + comments"
      (goldenLib "lib-simple-minimal-with-comments.golden") $
        let opts = WriteOpts False True False v pkgDir Library pkgName defaultCabalVersion
        in runGoldenLib opts libArgs emptyFlags

    , goldenVsString "Build tools flag, not simple, with comments + no minimal"
      (goldenLib "lib-build-tools-with-comments.golden") $
        let opts = WriteOpts False False False v pkgDir Library pkgName defaultCabalVersion
        in runGoldenLib opts libArgs (emptyFlags {buildTools = Flag ["happy"]})
    ]
  where
    runGoldenLib opts args flags =
      case _runPrompt (genLibTarget flags pkgIx) args of
        Right (t, _) -> mkStanza [mkLibStanza opts $ t {_libDependencies = mangleBaseDep t _libDependencies}]
        Left e -> assertFailure $ show e

goldenTestTests
    :: Verbosity
    -> InstalledPackageIndex
    -> FilePath
    -> PackageName
    -> TestTree
goldenTestTests v pkgIx pkgDir pkgName = testGroup "test golden tests"
    [ goldenVsString "Empty flags, not simple, no options, no comments"
      (goldenTest "test-no-comments.golden") $
        let opts = WriteOpts False False True v pkgDir Library pkgName defaultCabalVersion
        in runGoldenTest opts testArgs emptyFlags

    , goldenVsString "Empty flags, not simple, with comments + no minimal"
      (goldenTest "test-with-comments.golden") $
        let opts = WriteOpts False False False v pkgDir Library pkgName defaultCabalVersion
        in runGoldenTest opts testArgs emptyFlags

    , goldenVsString "Empty flags, not simple, with minimal + no comments"
      (goldenTest "test-minimal-no-comments.golden") $
        let opts = WriteOpts False True True v pkgDir Library pkgName defaultCabalVersion
        in runGoldenTest opts testArgs emptyFlags

    , goldenVsString "Empty flags, not simple, with minimal + comments"
      (goldenTest "test-simple-minimal-with-comments.golden") $
        let opts = WriteOpts False True False v pkgDir Library pkgName defaultCabalVersion
        in runGoldenTest opts testArgs emptyFlags

    , goldenVsString "Build tools flag, not simple, with comments + no minimal"
      (goldenTest "test-build-tools-with-comments.golden") $
        let opts = WriteOpts False False False v pkgDir Library pkgName defaultCabalVersion
        in runGoldenTest opts testArgs (emptyFlags {buildTools = Flag ["happy"]})
    
    , goldenVsString "Standalone tests, empty flags, not simple, no options, no comments"
      (goldenTest "standalone-test-no-comments.golden") $
        let opts = WriteOpts False False True v pkgDir TestSuite pkgName defaultCabalVersion
        in runGoldenTest opts testArgs emptyFlags

    , goldenVsString "Standalone tests, empty flags, not simple, with comments + no minimal"
      (goldenTest "standalone-test-with-comments.golden") $
        let opts = WriteOpts False False False v pkgDir TestSuite pkgName defaultCabalVersion
        in runGoldenTest opts testArgs emptyFlags
    ]
  where
    runGoldenTest opts args flags =
      case _runPrompt (genTestTarget flags pkgIx) args of
        Left e -> assertFailure $ show e
        Right (Nothing, _) -> assertFailure
          "goldenTestTests: Tests not enabled."
        Right (Just t, _) -> mkStanza [mkTestStanza opts $ t {_testDependencies = mangleBaseDep t _testDependencies}]

-- | Full cabal file golden tests
goldenCabalTests
    :: Verbosity
    -> InstalledPackageIndex
    -> SourcePackageDb
    -> TestTree
goldenCabalTests v pkgIx srcDb = testGroup ".cabal file golden tests"
    [ goldenVsString "Library and executable, empty flags, not simple, with comments + no minimal"
      (goldenCabal "cabal-lib-and-exe-with-comments.golden") $
        runGoldenTest (fullProjArgs "Y") emptyFlags

    , goldenVsString "Library and executable, empty flags, not simple, no comments + no minimal"
      (goldenCabal "cabal-lib-and-exe-no-comments.golden") $
        runGoldenTest (fullProjArgs "N") emptyFlags

    , goldenVsString "Library, empty flags, not simple, with comments + no minimal"
      (goldenCabal "cabal-lib-with-comments.golden") $
        runGoldenTest (libProjArgs "Y") emptyFlags

    , goldenVsString "Library, empty flags, not simple, no comments + no minimal"
      (goldenCabal "cabal-lib-no-comments.golden") $
        runGoldenTest (libProjArgs "N") emptyFlags
    
    , goldenVsString "Test suite, empty flags, not simple, with comments + no minimal"
      (goldenCabal "cabal-test-suite-with-comments.golden") $
        runGoldenTest (testProjArgs "Y") emptyFlags

    , goldenVsString "Test suite, empty flags, not simple, no comments + no minimal"
      (goldenCabal "cabal-test-suite-no-comments.golden") $
        runGoldenTest (testProjArgs "N") emptyFlags
    ]
  where
    runGoldenTest args flags =
      case _runPrompt (createProject v pkgIx srcDb flags) args of
        Left e -> assertFailure $ show e

        (Right (ProjectSettings opts pkgDesc (Just libTarget) (Just exeTarget) (Just testTarget), _)) -> do
          let pkgFields = mkPkgDescription opts pkgDesc
              commonStanza = mkCommonStanza opts
              libStanza  = mkLibStanza  opts $ libTarget {_libDependencies  = mangleBaseDep libTarget  _libDependencies}
              exeStanza  = mkExeStanza  opts $ exeTarget {_exeDependencies  = mangleBaseDep exeTarget  _exeDependencies}
              testStanza = mkTestStanza opts $ testTarget {_testDependencies = mangleBaseDep testTarget _testDependencies}

          mkStanza $ pkgFields ++ [commonStanza, libStanza, exeStanza, testStanza]

        (Right (ProjectSettings opts pkgDesc (Just libTarget) Nothing (Just testTarget), _)) -> do
          let pkgFields = mkPkgDescription opts pkgDesc
              commonStanza = mkCommonStanza opts
              libStanza  = mkLibStanza  opts $ libTarget  {_libDependencies  = mangleBaseDep libTarget  _libDependencies}
              testStanza = mkTestStanza opts $ testTarget {_testDependencies = mangleBaseDep testTarget _testDependencies}

          mkStanza $ pkgFields ++ [commonStanza, libStanza, testStanza]
        
        (Right (ProjectSettings opts pkgDesc Nothing Nothing (Just testTarget), _)) -> do
          let pkgFields = mkPkgDescription opts pkgDesc
              commonStanza = mkCommonStanza opts
              testStanza = mkTestStanza opts $ testTarget {_testDependencies = mangleBaseDep testTarget _testDependencies}
          
          mkStanza $ pkgFields ++ [commonStanza, testStanza]

        (Right (ProjectSettings _ _ l e t, _)) -> assertFailure $
          show l ++ "\n" ++ show e ++ "\n" ++ show t


-- -------------------------------------------------------------------- --
-- utils

mkStanza :: [PrettyField FieldAnnotation] -> IO BS8.ByteString
mkStanza fields = return . BS8.pack $ showFields'
    annCommentLines postProcessFieldLines
    4 fields

golden :: FilePath
golden = "tests" </> "fixtures" </> "init" </> "golden"

goldenExe :: FilePath -> FilePath
goldenExe file = golden </> "exe" </> file

goldenTest :: FilePath -> FilePath
goldenTest file = golden </> "test" </> file

goldenLib :: FilePath -> FilePath
goldenLib file = golden </> "lib" </> file

goldenCabal :: FilePath -> FilePath
goldenCabal file = golden </> "cabal" </> file

goldenPkgDesc :: FilePath -> FilePath
goldenPkgDesc file = golden </> "pkg-desc" </> file

libArgs :: NonEmpty String
libArgs = fromList ["1", "2"]

exeArgs :: NonEmpty String
exeArgs = fromList ["1", "2", "1"]

testArgs :: NonEmpty String
testArgs = fromList ["y", "1", "test", "1"]

pkgArgs :: NonEmpty String
pkgArgs = fromList
    [ "5"
    , "foo-package"
    , "y"
    , "0.1.0.0"
    , "2"
    , "git username"
    , "foo-kmett"
    , "git email"
    , "foo-kmett@kmett.kmett"
    , "home"
    , "synopsis"
    , "4"
    ]

testProjArgs :: String -> NonEmpty String
testProjArgs comments = fromList ["4", "n", "foo-package"]
  <> pkgArgs
  <> fromList (NEL.drop 1 testArgs)
  <> fromList [comments]

libProjArgs :: String -> NonEmpty String
libProjArgs comments = fromList ["1", "n", "foo-package"]
  <> pkgArgs
  <> libArgs
  <> testArgs
  <> fromList [comments]

fullProjArgs :: String -> NonEmpty String
fullProjArgs comments = fromList ["3", "n", "foo-package"]
  <> pkgArgs
  <> libArgs
  <> exeArgs
  <> testArgs
  <> fromList [comments]
