content: module Main (main) where
  
  import qualified Weeder.Main
  import qualified Weeder.Run
  import qualified Weeder
  import qualified TOML
  import qualified UnitTests.Weeder.ConfigSpec
  
  import Data.Maybe
  import Algebra.Graph.Export.Dot
  import GHC.Types.Name.Occurrence (occNameString)
  import System.Directory
  import System.FilePath
  import System.IO (stderr, hPrint)
  import Test.Tasty (TestTree, defaultMain, testGroup)
  import Control.Exception ( throwIO, IOException, handle )
  import Data.List (find, sortOn)
  import qualified Data.ByteString.Lazy as LBS
  import Data.Text (pack)
  import Data.Text.Encoding (encodeUtf8)
  import Test.Tasty.Golden
  
  main :: IO ()
  main = do
    testOutputFiles <- fmap sortTests discoverIntegrationTests
    let hieDirectories = map (dropExtension . snd) testOutputFiles
    defaultMain $ 
      testGroup "Weeder"
        [ testGroup "Weeder.Run" $
            [ testGroup "runWeeder" $
                zipWith (uncurry integrationTest) 
                  testOutputFiles 
                  hieDirectories
            ]
        , UnitTests.Weeder.ConfigSpec.tests 
        ]
    where
      -- Sort the output files such that the failing ones go last
      sortTests = sortOn (isJust . fst)
  
  -- | Run weeder on @hieDirectory@, comparing the output to @stdoutFile@.
  --
  -- The directory containing @hieDirectory@ must also have a @.toml@ file
  -- with the same name as @hieDirectory@.
  --
  -- If @failingFile@ is @Just@, it is used as the expected output instead of
  -- @stdoutFile@, and a different failure message is printed if the output
  -- matches @stdoutFile@.
  integrationTest :: Maybe FilePath -> FilePath -> FilePath -> TestTree
  integrationTest failingFile stdoutFile hieDirectory = do
    goldenVsString (integrationTestText ++ hieDirectory) (fromMaybe stdoutFile failingFile) $ 
      integrationTestOutput hieDirectory
    where
      integrationTestText = case failingFile of
        Nothing -> "produces the expected output for "
        Just _ -> "produces the expected (wrong) output for "
  
  -- | Returns detected .failing and .stdout files in ./test/Spec
  discoverIntegrationTests :: IO [(Maybe FilePath, FilePath)]
  discoverIntegrationTests = do
    contents <- listDirectory testPath
    let stdoutFiles = map (testPath </>) $
          filter (".stdout" `isExtensionOf`) contents
    pure . map (\s -> (findFailing s contents, s)) $ stdoutFiles
      where
        findFailing s = fmap (testPath </>) . find (takeBaseName s <.> ".failing" ==)
        testPath = "./test/Spec"
  
  -- | Run weeder on the given directory for .hie files, returning stdout
  -- Also creates a dotfile containing the dependency graph as seen by Weeder
  integrationTestOutput :: FilePath -> IO LBS.ByteString
  integrationTestOutput hieDirectory = do
    hieFiles <- Weeder.Main.getHieFiles ".hie" [hieDirectory] False
    weederConfig <- TOML.decodeFile configExpr >>= either throwIO pure
    let (weeds, analysis) = Weeder.Run.runWeeder weederConfig hieFiles
        graph = Weeder.dependencyGraph analysis
        graph' = export (defaultStyle (occNameString . Weeder.declOccName)) graph
    handle (\e -> hPrint stderr (e :: IOException)) $
      writeFile (hieDirectory <.> ".dot") graph'
    -- Normalize weedPackage. We get different values here based on our version of cabal-install/ghc.
    let weeds' = map (\weed -> weed {Weeder.Run.weedPackage = "main"}) weeds
    pure (LBS.fromStrict $ encodeUtf8 $ pack $ unlines $ map Weeder.Run.formatWeed weeds')
    where
      configExpr = hieDirectory <.> ".toml"
  