content: {-# LANGUAGE MultiParamTypeClasses #-}
  {-# LANGUAGE OverloadedStrings #-}
  {-# LANGUAGE ScopedTypeVariables #-}
  
  module Main where
  
  import qualified Conduit as C
  import Control.Exception
  import qualified Data.ByteString.Char8 as B
  import Data.CSV.Conduit
  import Data.CSV.Conduit.Conversion
  import Data.CSV.Conduit.Types
  import qualified Data.Map as Map
  import qualified Data.Map.Ordered as OMap
  import Data.Monoid as M
  import Data.Text (Text)
  import qualified Data.Vector as V
  import System.Directory
  import Test.Framework (Test, defaultMain, testGroup)
  import Test.Framework.Providers.HUnit
  import Test.HUnit (assertFailure, (@=?), (@?=))
  
  main :: IO ()
  main = defaultMain tests
  
  tests :: [Test]
  tests =
    [ testGroup "Basic Ops" baseTests,
      testGroup "decodeCSV" decodeCSVTests
    ]
  
  baseTests :: [Test]
  baseTests =
    [ testCase "mapping with id works" test_identityMap,
      testCase "simple parsing works" (test_simpleParse testFile1),
      testCase "simple parsing works for Mac-Excel" (test_simpleParse testFile3),
      testCase "simple parsing works for Windows-Excel" (test_simpleParse testFile4),
      testCase "fails parsing gracefully" test_parseFail,
      testCase "OrderedMap" test_orderedMap
    ]
  
  decodeCSVTests :: [Test]
  decodeCSVTests =
    [ testCase "parses a CSV" $ do
        let efoos = decodeCSV defCSVSettings ("Foo\nfoo" :: B.ByteString)
        case efoos :: Either SomeException (V.Vector (Named Foo)) of
          Left e -> assertFailure (show e)
          Right foos -> V.fromList [Named Foo] @=? foos,
      testCase "eats parse errors, evidently" $ do
        let efoos = decodeCSV defCSVSettings ("Foo\nbad" :: B.ByteString)
        case efoos :: Either SomeException (V.Vector (Named Foo)) of
          Left e -> assertFailure (show e)
          Right foos -> M.mempty @=? foos
    ]
  
  data Foo = Foo deriving (Show, Eq)
  
  instance FromNamedRecord Foo where
    parseNamedRecord nr = do
      s <- nr .: "Foo"
      case s of
        "foo" -> pure Foo
        _ -> fail ("Expected \"foo\" but got " <> B.unpack s)
  
  instance ToNamedRecord Foo where
    toNamedRecord Foo = namedRecord ["Foo" .= ("foo" :: B.ByteString)]
  
  test_identityMap :: IO ()
  test_identityMap = do
    _ <- runResourceT $ mapCSVFile csvSettings f testFile2 outFile
    f1 <- readFile testFile2
    f2 <- readFile outFile
    f1 @=? f2
    removeFile outFile
    where
      outFile = "test/testOut.csv"
      f :: Row Text -> [Row Text]
      f = return
  
  test_simpleParse :: FilePath -> IO ()
  test_simpleParse fp = do
    (d :: V.Vector (MapRow B.ByteString)) <- readCSVFile csvSettings fp
    V.mapM_ assertRow d
    where
      assertRow r = v3 @=? (v1 + v2)
        where
          v1 = readBS $ r Map.! "Col2"
          v2 = readBS $ r Map.! "Col3"
          v3 = readBS $ r Map.! "Sum"
  
  test_parseFail :: IO ()
  test_parseFail = do
    (d :: V.Vector (MapRow B.ByteString)) <- readCSVFile csvSettings testXLS
    errored <- catch (V.mapM_ assertRow d >> pure False) handler
    if errored then pure () else assertFailure "readCSVFile shouldn't read XLS"
    where
      handler :: ErrorCall -> IO Bool
      handler _ = pure True
      assertRow r = v3 @=? (v1 + v2)
        where
          v1 = readBS $ r Map.! "Col2"
          v2 = readBS $ r Map.! "Col3"
          v3 = readBS $ r Map.! "Sum"
  
  test_orderedMap :: IO ()
  test_orderedMap = do
    unorderedRes <-
      C.runConduit $
        C.yieldMany [unorderedRow]
          C..| writeHeaders defCSVSettings
          C..| C.foldC
    unorderedRes @?= ("\"a\",\"b\"\n\"aval\",\"bval\"\n" :: B.ByteString)
    orderedRes <-
      C.runConduit $
        C.yieldMany [orderedRow]
          C..| writeHeadersOrdered defCSVSettings
          C..| C.foldC
    orderedRes @?= ("\"b\",\"a\"\n\"bval\",\"aval\"\n" :: B.ByteString)
    where
      orderedRow :: OrderedMapRow Text
      orderedRow = OMap.fromList pairs
      unorderedRow :: MapRow Text
      unorderedRow = Map.fromList pairs
      pairs = [("b", "bval"), ("a", "aval")]
  
  csvSettings :: CSVSettings
  csvSettings = defCSVSettings {csvQuoteCharAndStyle = Just ('`', DontQuoteEmpty)}
  
  testFile1, testFile2, testFile3, testFile4 :: FilePath
  testFile1 = "test/test.csv"
  testFile2 = "test/test.csv"
  testFile3 = "test/test-mac-excel.csv"
  testFile4 = "test/test-windows-excel.csv"
  
  testXLS :: FilePath
  testXLS = "test/test.xls"
  
  readBS :: B.ByteString -> Int
  readBS = read . B.unpack
  