{-# OPTIONS_GHC -fno-warn-orphans #-}

module Handler.Utils.Csv
  ( typeCsv, extensionCsv
  , decodeCsv
  , encodeCsv
  , respondCsv, respondCsvDB
  , fileSourceCsv
  , CsvParseError(..)
  , ToNamedRecord(..), FromNamedRecord(..)
  , DefaultOrdered(..)
  , ToField(..), FromField(..)
  , CsvRendered(..)
  , toCsvRendered
  ) where

import Import hiding (Header)

import Data.Csv
import Data.Csv.Conduit

import qualified Data.Conduit.List as C
import qualified Data.Conduit.Combinators as C (sourceLazy)

import qualified Data.Map as Map
import qualified Data.Vector as Vector
import qualified Data.HashMap.Strict as HashMap


deriving instance Typeable CsvParseError
instance Exception CsvParseError


typeCsv :: ContentType
typeCsv = "text/csv"

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


decodeCsv :: (MonadThrow m, FromNamedRecord csv) => Conduit ByteString m csv
decodeCsv = transPipe throwExceptT $ fromNamedCsv defaultDecodeOptions

encodeCsv :: ( ToNamedRecord csv
             , DefaultOrdered csv
             , Monad m
             )
          => Conduit csv m ByteString
-- ^ Encode a stream of records
--
-- Currently not streaming
encodeCsv = fmap encodeDefaultOrderedByName (C.foldMap pure) >>= C.sourceLazy

  
respondCsv :: ( ToNamedRecord csv
              , DefaultOrdered csv
              )
           => Source (HandlerT site IO) csv
           -> HandlerT site IO TypedContent
respondCsv src = respondSource typeCsv $ src .| encodeCsv .| awaitForever sendChunk

respondCsvDB :: ( ToNamedRecord csv
                , DefaultOrdered csv
                , YesodPersistRunner site
                )
             => Source (YesodDB site) csv
             -> HandlerT site IO TypedContent
respondCsvDB src = respondSourceDB typeCsv $ src .| encodeCsv .| awaitForever sendChunk

fileSourceCsv :: ( FromNamedRecord csv
                 , MonadResource m
                 )
              => FileInfo
              -> Source m csv
fileSourceCsv = (.| decodeCsv) . fileSource


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

instance ToWidget UniWorX CsvRendered where
  toWidget CsvRendered{..} = liftWidgetT $(widgetFile "widgets/csvRendered")
    where
      csvData = [ [ decodeUtf8 <$> HashMap.lookup columnKey row
                  | columnKey <- Vector.toList csvRenderedHeader
                  ]
                | row <- csvRenderedData
                ]

      headers = decodeUtf8 <$> Vector.toList csvRenderedHeader

toCsvRendered :: forall mono.
                 ( ToNamedRecord (Element mono)
                 , DefaultOrdered (Element mono)
                 , MonoFoldable mono
                 )
              => mono -> CsvRendered
toCsvRendered (otoList -> csvs) = CsvRendered{..}
  where
    csvRenderedHeader = headerOrder (error "not forced" :: Element mono)
    csvRenderedData   = map toNamedRecord csvs
