-- |
-- Module:     Database.PostgreSQL.Store.Result
-- Copyright:  (c) Ole Krüger 2015-2016
-- License:    BSD3
-- Maintainer: Ole Krüger <ole@vprsm.de>
module Database.PostgreSQL.Store.Result (
	-- *
	ResultError (..),
	ResultProcessor,
	ColumnInfo,
	runResultProcessor,
	columnNumber,
	columnType,
	columnFormat,
	columnInfo,
	foreachRow,
	cellValue,
	unpackCellValue,

	-- * Result
	Result (..),
	RawRow (..)
) where

import           Control.Monad
import           Control.Monad.Trans.Class
import           Control.Monad.Trans.Reader
import           Control.Monad.Trans.Except

import           Data.List
import           Data.Typeable
import qualified Data.ByteString           as B

import qualified Database.PostgreSQL.LibPQ as P
import           Database.PostgreSQL.Store.Columns

-- | Error that occured during result processing
data ResultError
	= ColumnMissing B.ByteString
	| ValueError P.Row P.Column P.Oid P.Format ColumnDescription
	deriving (Show, Eq)

-- | Result processor
type ResultProcessor = ReaderT P.Result (ExceptT ResultError IO)

-- | Column information
type ColumnInfo = (P.Column, P.Oid, P.Format)

-- | Process the result.
runResultProcessor :: P.Result -> ResultProcessor a -> ExceptT ResultError IO a
runResultProcessor = flip runReaderT

-- | Get the number of columns.
numColumns :: ResultProcessor P.Column
numColumns = do
	result <- ask
	lift (lift (P.nfields result))

-- | Get the column number for a column name.
columnNumber :: B.ByteString -> ResultProcessor P.Column
columnNumber name = do
	result <- ask
	lift (ExceptT (maybe (Left (ColumnMissing name)) pure <$> P.fnumber result name))

-- | Get the type of a column.
columnType :: P.Column -> ResultProcessor P.Oid
columnType col = do
	result <- ask
	lift (lift (P.ftype result col))

-- | Get the format of a column.
columnFormat :: P.Column -> ResultProcessor P.Format
columnFormat col = do
	result <- ask
	lift (lift (P.fformat result col))

-- | Get information about a column.
columnInfo :: B.ByteString -> ResultProcessor ColumnInfo
columnInfo name = do
	col <- columnNumber name
	(,,) col <$> columnType col <*> columnFormat col

-- | Do something for each row.
foreachRow :: (P.Row -> ResultProcessor a) -> ResultProcessor [a]
foreachRow rowProcessor = do
	result <- ask
	numRows <- lift (lift (P.ntuples result))
	mapM rowProcessor [0 .. numRows - 1]

-- | Get cell value.
cellValue :: P.Row -> ColumnInfo -> ResultProcessor Value
cellValue row (col, oid, fmt) = do
	result <- ask
	value <- lift (lift (P.getvalue' result row col))

	case value of
		Just dat -> pure (Value oid dat fmt)
		Nothing  -> pure NullValue

-- | Get cell value.
cellValue' :: P.Row -> P.Column -> ResultProcessor Value
cellValue' row col = do
	result <- ask
	(oid, fmt, value) <- lift (lift ((,,) <$> P.ftype result col
	                                      <*> P.fformat result col
	                                      <*> P.getvalue' result row col))

	case value of
		Just dat -> pure (Value oid dat fmt)
		Nothing  -> pure NullValue


-- | Unpack cell value.
unpackCellValue :: (Column a) => P.Row -> ColumnInfo -> ResultProcessor a
unpackCellValue row info =
	withProxy Proxy
	where
		withProxy :: (Column a) => Proxy a -> ResultProcessor a
		withProxy proxy = do
			value <- cellValue row info
			lift (ExceptT (make (describeColumn proxy) (unpack value)))

		(col, oid, fmt) = info

		valueError desc =
			pure (Left (ValueError row col oid fmt desc))

		make desc =
			maybe (valueError desc) (pure . pure)

-- | Result row
class Result a where
	-- | Extract rows from the given result.
	resultProcessor :: ResultProcessor [a]

instance Result () where
	resultProcessor = foreachRow (const (pure ()))

instance (Result a, Result b) => Result (a, b) where
	resultProcessor =
		zip <$> resultProcessor
		    <*> resultProcessor

instance (Result a, Result b, Result c) => Result (a, b, c) where
	resultProcessor =
		zip3 <$> resultProcessor
		     <*> resultProcessor
		     <*> resultProcessor

instance (Result a, Result b, Result c, Result d) => Result (a, b, c, d) where
	resultProcessor =
		zip4 <$> resultProcessor
		     <*> resultProcessor
		     <*> resultProcessor
		     <*> resultProcessor

instance (Result a, Result b, Result c, Result d, Result e) => Result (a, b, c, d, e) where
	resultProcessor =
		zip5 <$> resultProcessor
		     <*> resultProcessor
		     <*> resultProcessor
		     <*> resultProcessor
		     <*> resultProcessor

instance (Result a, Result b, Result c, Result d, Result e, Result f) => Result (a, b, c, d, e, f) where
	resultProcessor =
		zip6 <$> resultProcessor
		     <*> resultProcessor
		     <*> resultProcessor
		     <*> resultProcessor
		     <*> resultProcessor
		     <*> resultProcessor

instance (Result a, Result b, Result c, Result d, Result e, Result f, Result g) => Result (a, b, c, d, e, f, g) where
	resultProcessor =
		zip7 <$> resultProcessor
		     <*> resultProcessor
		     <*> resultProcessor
		     <*> resultProcessor
		     <*> resultProcessor
		     <*> resultProcessor
		     <*> resultProcessor

-- | A row containing all a list of all column values.
newtype RawRow = RawRow [Value]
	deriving (Show, Eq, Ord)

instance Result RawRow where
	resultProcessor = do
		ncols <- numColumns
		foreachRow $ \ row ->
			RawRow <$> forM [0 .. ncols - 1] (cellValue' row)