{-# OPTIONS -fno-warn-orphans #-}

module Utils.Csv
  ( typeCsv, typeCsv', extensionCsv
  , pathPieceCsv
  , (.:??)
  , CsvRendered(..)
  , toCsvRendered
  , toDefaultOrderedCsvRendered
  ) where

import ClassyPrelude hiding (lookup)
import Settings.Mime

import Data.Csv hiding (Name)
import Data.Csv.Conduit (CsvParseError)
import qualified Data.Csv.Incremental as Incremental

import Language.Haskell.TH (Name)
import Language.Haskell.TH.Lib

import Yesod.Core.Content

import qualified Data.Map as Map


deriving instance Typeable CsvParseError
instance Exception CsvParseError


typeCsv, typeCsv' :: ContentType
typeCsv = simpleContentType typeCsv'
typeCsv' = "text/csv; charset=UTF-8; header=present"

extensionCsv :: Extension
extensionCsv = fromMaybe "csv" $ listToMaybe [ ext | (ext, mime) <- Map.toList mimeMap, mime == typeCsv ]


pathPieceCsv :: Name -> DecsQ
pathPieceCsv (conT -> t) =
  [d|
    instance ToField $(t) where
      toField = toField . toPathPiece
    instance FromField $(t) where
      parseField = maybe (fail "Could not unmarshal from PathPiece") return . fromPathPiece <=< parseField
  |]


(.:??) :: FromField (Maybe a) => NamedRecord -> ByteString -> Parser (Maybe a)
m .:?? name = lookup m name <|> return Nothing


data CsvRendered = CsvRendered
  { csvRenderedHeader :: Header
  , csvRenderedData :: [NamedRecord]
  } deriving (Eq, Read, Show, Generic, Typeable)

instance ToContent CsvRendered where
  toContent CsvRendered{..} = toContent . Incremental.encodeByName csvRenderedHeader $ foldr ((<>) . Incremental.encodeNamedRecord) mempty csvRenderedData

instance ToTypedContent CsvRendered where
  toTypedContent = TypedContent
    <$> getContentType . Identity
    <*> toContent

instance HasContentType CsvRendered where
  getContentType _ = typeCsv'

toCsvRendered :: forall mono.
                 ( ToNamedRecord (Element mono)
                 , MonoFoldable mono
                 )
              => Header
              -> mono -> CsvRendered
toCsvRendered csvRenderedHeader (otoList -> csvs) = CsvRendered{..}
  where
    csvRenderedData   = map toNamedRecord csvs

toDefaultOrderedCsvRendered :: forall mono.
                               ( ToNamedRecord (Element mono)
                               , DefaultOrdered (Element mono)
                               , MonoFoldable mono
                               )
                            => mono -> CsvRendered
toDefaultOrderedCsvRendered = toCsvRendered $ headerOrder (error "headerOrder" :: Element mono)
