content: {-# LANGUAGE NamedFieldPuns    #-}
  {-# LANGUAGE OverloadedStrings #-}
  {-# LANGUAGE RecordWildCards   #-}
  {-# LANGUAGE UnicodeSyntax     #-}
  module Main
    ( main
    ) where
  
  import qualified Codec.Archive.Tar as Tar
  import qualified Codec.Compression.GZip as GZip
  import           Control.Applicative ( (<$>), (<*>) )
  import           Control.Exception ( Exception (..) )
  import           Control.Lens ( (^.) )
  import           Control.Monad ( mzero, void )
  import           Data.ByteString.Lazy ( toStrict )
  import           Data.Foldable ( for_ )
  import qualified Data.HashMap.Strict as HM
  import           Data.List ( isPrefixOf )
  import           Data.Maybe ( fromMaybe )
  import qualified Data.Text as T
  import           Data.Yaml
                     ( FromJSON, Value (..), (.!=), (.:), (.:?), decodeEither'
                     , parseJSON
                     )
  import           Network.Wreq ( get, responseBody )
  import           System.FilePath ( takeExtension, takeFileName )
  import           Test.Hspec
                     ( Spec, describe, expectationFailure, hspec, shouldBe, it )
  import           Text.Mustache
                     ( Template (..), compileTemplate, substituteValue, toMustache
                     )
  
  langspecs :: [String]
  langspecs =
    [ "https://codeload.github.com/andrewthad/spec/legacy.tar.gz/add_list_context_check"
    , "https://codeload.github.com/mustache/spec/tar.gz/v1.1.3"
    ]
  
  
  data LangSpecFile = LangSpecFile
    { overview :: String
    , tests    :: [LangSpecTest]
    }
  
  
  data LangSpecTest = LangSpecTest
    { name            :: String
    , specDescription :: String
    , specData        :: Value
    , template        :: T.Text
    , expected        :: T.Text
    , testPartials    :: HM.HashMap String T.Text
    }
  
  
  instance FromJSON LangSpecFile where
    parseJSON (Object o) = LangSpecFile
      <$> o .: "overview"
      <*> o .: "tests"
    parseJSON _ = mzero
  
  
  instance FromJSON LangSpecTest where
    parseJSON (Object o) = LangSpecTest
      <$> o .: "name"
      <*> o .: "desc"
      <*> o .: "data"
      <*> o .: "template"
      <*> o .: "expected"
      <*> o .:? "partials" .!= HM.empty
    parseJSON _ = mzero
  
  
  getOfficialSpecRelease :: String -> IO [(String, LangSpecFile)]
  getOfficialSpecRelease releaseURL  = do
      res <- get releaseURL
      let archive = Tar.read $ GZip.decompress (res ^. responseBody)
      pure $ Tar.foldEntries handleEntry [] (error . show) archive
    where
      handleEntry e acc =
        case content of
          Tar.NormalFile f _
            | takeExtension filename `elem` [".yml", ".yaml"]
                && not ("~" `isPrefixOf` takeFileName filename) ->
                  ( filename
                  , case decodeEither' $ toStrict f of
                      Left e -> error $
                           "Error parsing spec file "
                        ++ filename
                        ++ ": "
                        ++ displayException e
                      Right spec -> spec
                  ) : acc
          _ -> acc
        where
          filename = Tar.entryPath e
          content = Tar.entryContent e
  
  
  testOfficialLangSpec :: [(String, LangSpecFile)] -> Spec
  testOfficialLangSpec testfiles =
    for_ testfiles $ \(filename, LangSpecFile { tests }) ->
      describe ("File: " ++ takeFileName filename) $
        for_ tests $ \(LangSpecTest { .. }) ->
          it ("Name: " ++ name ++ "  Description: " ++ specDescription) $
            let
              compiled = do
                partials' <- HM.traverseWithKey compileTemplate testPartials
                template' <- compileTemplate name template
                pure $ template' { partials = partials' }
            in
              case compiled of
                Left m -> expectationFailure $ show m
                Right tmp ->
                  substituteValue tmp (toMustache specData) `shouldBe` expected
  
  
  main :: IO ()
  main =
    void $ do
      specs <- mapM getOfficialSpecRelease langspecs
      hspec $ mapM_ testOfficialLangSpec specs
  