{-# LANGUAGE ExistentialQuantification, OverloadedStrings #-}

module Main where

import qualified Data.ByteString as ByteString
import Data.ByteString (ByteString)
import Data.Functor.Identity (Identity)
import Data.List (isSuffixOf)
import Data.Maybe (mapMaybe)
import Data.Text (Text)
import qualified Data.Text as Text
import Data.Text.Encoding (decodeUtf8, encodeUtf8)
import Numeric (showHex)
import System.Directory (doesDirectoryExist, listDirectory)
import System.FilePath.Posix (combine)
import qualified Text.ParserCombinators.Incremental as Incremental
import Text.ParserCombinators.Incremental.LeftBiasedLocal (Parser)
import qualified Data.Attoparsec.ByteString as Atto
import Test.Tasty (TestTree, defaultMain, testGroup)
import Test.Tasty.HUnit (assertFailure, assertEqual, testCase)

import Construct
import qualified MBR
import qualified TAR
import qualified URI
import qualified WMF

data TestFormat = forall f. TestFormat (Format (Parser ByteString) Maybe ByteString (f Identity))
                | forall f. LineFormat (Format (Parser Text) Maybe Text (f Identity))
                | forall f. AttoFormat (Format Atto.Parser Maybe ByteString (f Identity))

main = exampleTree "" "test/examples" >>= defaultMain . testGroup "examples"

exampleTree :: FilePath -> FilePath -> IO [TestTree]
exampleTree ancestry path =
   do let fullPath = combine ancestry path
      isDir <- doesDirectoryExist fullPath
      if isDir
         then (:[]) . testGroup path . concat <$> (listDirectory fullPath >>= mapM (exampleTree fullPath))
         else do blob <- ByteString.readFile fullPath
                 let format
                        | ".mbr"  `isSuffixOf` path = TestFormat MBR.format
                        | ".tar"  `isSuffixOf` path = AttoFormat TAR.archive
                        | ".uris" `isSuffixOf` path = LineFormat URI.uriReference
                        | ".wmf"  `isSuffixOf` path = TestFormat WMF.fileFormat
                     textLines = Text.lines (decodeUtf8 blob)
                     roundTrip f t
                        | Text.null t = Just t
                        | [(structure, remainder)] <- Incremental.completeResults (Incremental.feedEof $
                                                                                   Incremental.feed t $ parse f),
                          Text.null remainder = serialize f structure
                        | otherwise = Nothing
                     Just blob'
                        | TestFormat f <- format,
                          [(structure, remainder)] <- Incremental.completeResults (Incremental.feedEof $
                                                                                   Incremental.feed blob $ parse f) =
                             (<> remainder) <$> serialize f structure
                        | LineFormat f <- format = Just (encodeUtf8 $ mconcat $ mapMaybe (roundTrip f) textLines)
                        | AttoFormat f <- format,
                          Atto.Done remainder structure <- Atto.parse (parse f) blob =
                             (<> remainder) <$> serialize f structure
                        | AttoFormat f <- format, Atto.Partial i <- Atto.parse (parse f) blob,
                          Atto.Done remainder structure <- i mempty =
                             (<> remainder) <$> serialize f structure
                 return . (:[]) . testCase path $ assertEqual "round-trip" (hex blob) (hex blob')

hex :: ByteString -> String
hex = ByteString.foldr (pad . flip showHex "") ""
   where pad [x] s = ['0', x] ++ s
         pad [x, y] s = [x, y] ++ s
