-- Test over all files in examples/ directory
module Main(main) where

import Control.Monad(forM, forM_, unless, join)
import Data.Char(toUpper)
import Data.Functor ((<$>))
import Data.List(isPrefixOf, isSuffixOf, isInfixOf)
import Data.Maybe(isJust)
import System.Directory(doesDirectoryExist, getDirectoryContents, createDirectoryIfMissing)
import System.FilePath((</>), (<.>), takeBaseName, replaceFileName)
import System.Exit(ExitCode(..))
import System.Environment as Env
import System.Process             (rawSystem)
import Data.Aeson.AutoType.CodeGen(runModule, Lang(Haskell))
import Data.Aeson ( Result,  Object, FromJSON, Value(Null,Number), (.:?) )
import Data.Aeson.Types ( Parser, parse )
import Data.Text ( Text, pack )
import Data.HashMap.Lazy ( singleton, empty )


-- |  <http://book.realworldhaskell.org/read/io-case-study-a-library-for-searching-the-filesystem.html>
getRecursiveContents :: FilePath -> IO [FilePath]
getRecursiveContents topdir = do
  ex<-doesDirectoryExist topdir
  if ex
        then do
          names <- getDirectoryContents topdir
          let properNames = filter (not . isPrefixOf ".") names
          paths <- forM properNames $ \name -> do
            let path = topdir </> name
            isDirectory <- doesDirectoryExist path
            if isDirectory
              then getRecursiveContents path
              else return [path]
          return (concat paths)
        else return []

capitalize :: String -> String
capitalize [] = []
capitalize (s:ss) = toUpper s:ss

main :: IO ()
main  = do
  putStrLn "****************************************"
  verifyAesonOperators
  filenames <-  filter (isSuffixOf ".json")
            <$> getRecursiveContents "examples"
  createDirectoryIfMissing True "output"
  forM_ filenames $ \filename -> do
    let outputFilename = ("output" </> capitalize (takeBaseName filename <.> "hs"))
    genResult <- runAutotype filename ["--outputFilename", outputFilename]
    unless (genResult == ExitSuccess) $
      fail (unwords ["test case", show filename, "failed with", show genResult])
    parserResult <- runModule Haskell outputFilename [filename]
    --            ^ runModule HaskellStrict -- for compiling with -Wall -Werror
    unless (parserResult == ExitSuccess) $
      fail (unwords ["generated parser", show outputFilename, "failed with", show parserResult])

runAutotype ::    String
            ->   [String]
            -> IO ExitCode
runAutotype source arguments = do
    stackEnvUpDir  <- doesDirectoryExist "../.stack-work"
    stackEnvCurDir <- doesDirectoryExist ".stack-work"
    cabalEnv       <- doesDirectoryExist "dist/build/autogen"
    maybeStack     <- Env.lookupEnv "STACK_EXEC"
    maybeSandbox   <- Env.lookupEnv "CABAL_SANDBOX_PACKAGE_PATH"
    maybePkgPath   <- Env.lookupEnv "GHC_PACKAGE_PATH"
    let isStack = maybe False ("stack" `isInfixOf`) maybePkgPath
               || isJust maybeStack
               || stackEnvUpDir
               || stackEnvCurDir
        isCabal = maybe False ("cabal" `isInfixOf`) maybePkgPath
               || cabalEnv
               || isJust maybeSandbox
        (exec, args) | Just stackExec <- maybeStack = (stackExec, ["run", source, "--"])
                     | isStack                      = ("stack",   ["run", source, "--"])
                     | isCabal                      = ("cabal",   ["run", source, "--"])
                     | otherwise                    = error "This test must be run either in either Stack or Cabal environment."
    putStrLn $ concat ["Running json-autotype with executable ", show exec, " and arguments ", show args]
    rawSystem exec $ args ++ arguments

verifyAesonOperators :: IO ()
verifyAesonOperators = do
  parseTest (singleton (pack "foo") (Number 1))
  parseTest (singleton (pack "foo")  Null     )
  parseTest (singleton (pack "bar")  Null     )
  parseTest empty

(.:??) :: FromJSON a => Object -> Text -> Parser (Maybe a)
o .:?? val = fmap join (o .:? val)

parseTest :: Object -> IO ()
parseTest o = unless (r1 == r2) (fail (show r1 ++ " /= " ++ show r2))
  where r1, r2 :: Result (Maybe Int)
        r1 = parse (.:? (pack "foo")) o
        r2 = parse (.:?? (pack "foo")) o
