{-# LANGUAGE OverloadedStrings, ScopedTypeVariables #-}

-- | Explicit methods for CouchDB documents. Documents represents in \"good
--   old\" aeson manner through 'A.ToJSON' and 'A.FromJSON'.
--
-- > {-# LANGUAGE OverloadedStrings, ScopedTypeVariables #-}
-- >
-- > import Control.Applicative ((<$>), (<*>))
-- > import Control.Monad.IO.Class (liftIO)
-- > import Data.Aeson
-- > import Database.CouchDB.Conduit
-- > import Database.CouchDB.Conduit.Explicit
-- >
-- > -- | Our doc with instances
-- > data D = D { f1 :: Int, f2 :: String } deriving (Show)
-- > 
-- > instance FromJSON D where
-- >    parseJSON (Object v) = D <$> v .: "f1" <*> v .: "f2"
-- >    parseJSON _          = mzero
-- >
-- > instance ToJSON D where
-- >    toJSON (D f1 f2) = object ["f1" .= f1, "f2" .= f2]
-- > 
-- > runCouch def $ do
-- >    -- Put new doc and update it
-- >    rev1 <- couchPut "mydb" "my-doc1" "" [] $ D 123 "str"         
-- >    rev2 <- couchPut "mydb" "my-doc1" rev1 [] $ D 1234 "another"
-- >
-- >    -- get it and print
-- >    (rev3, d1 :: D) <- couchGet "mydb" "my-doc1" [] 
-- >    liftIO $ print d1
-- >
-- >    -- update it in brute-force manner    
-- >    couchPut' "mydb" "my-doc1" [] $ D 12345 "third"    -- notice - no rev
-- >    
-- >    -- get revision and delete
-- >    rev3 <- couchRev "mydb" "my-doc1"
-- >    couchDelete "mydb" "my-doc1" rev3
-- 
--   For details of types see "Data.Aeson". To work with documents in 
--   generic manner, look at "Database.CouchDB.Conduit.Generic".

module Database.CouchDB.Conduit.Explicit (
    -- * Accessing documents
    couchGet,
    -- * Manipulating documents
    couchPut,
    couchPut_,
    couchPut',
    -- * Working with views #view#
    toType
) where

import qualified Data.Aeson as A
import Data.Conduit (ResourceT, Conduit(..), ResourceIO)

import Network.HTTP.Types (Query)

import Database.CouchDB.Conduit.Internal.Connection (MonadCouch(..), 
        Path, Revision, mkPath)
import Database.CouchDB.Conduit.Internal.Doc (couchGetWith, couchPutWith, 
        couchPutWith_, couchPutWith') 
import Database.CouchDB.Conduit.Internal.View (toTypeWith)

------------------------------------------------------------------------------
-- Document
------------------------------------------------------------------------------

-- | Load a single 'A.ToJSON' object with 'Revision' from couch DB. 
couchGet :: (MonadCouch m, A.FromJSON a) => 
       Path         -- ^ Database
    -> Path         -- ^ Document path
    -> Query     -- ^ Query
    -> ResourceT m (Revision, a)
couchGet db p = couchGetWith A.fromJSON (mkPath [db, p])

-- | Put an 'A.FromJSON' object in Couch DB with revision, returning the 
--   new 'Revision'.
couchPut :: (MonadCouch m, A.ToJSON a) => 
       Path         -- ^ Database
    -> Path         -- ^ Document path
    -> Revision    -- ^ Document revision. For new docs provide empty string.
    -> Query    -- ^ Query arguments.
    -> a           -- ^ The object to store.
    -> ResourceT m Revision      
couchPut db p = couchPutWith A.encode (mkPath [db, p])

-- | \"Don't care\" version of 'couchPut'. Creates document only in its 
--   absence.
couchPut_ :: (MonadCouch m, A.ToJSON a) => 
       Path         -- ^ Database
    -> Path         -- ^ Document path
    -> Query    -- ^ Query arguments.
    -> a           -- ^ The object to store.
    -> ResourceT m Revision      
couchPut_ db p = couchPutWith_ A.encode (mkPath [db, p])

-- | Brute force version of 'couchPut'. Creates a document regardless of 
--   presence. 
couchPut' :: (MonadCouch m, A.ToJSON a) => 
       Path         -- ^ Database
    -> Path         -- ^ Document path
    -> Query    -- ^ Query arguments.
    -> a           -- ^ The object to store.
    -> ResourceT m Revision      
couchPut' db p = couchPutWith' A.encode (mkPath [db, p])

------------------------------------------------------------------------------
-- View conduit
------------------------------------------------------------------------------

-- | Convert CouchDB view row or row value from "Database.CouchDB.Conduit.View" 
--   to concrete 'A.FromJSON' type.
--   
-- > res <- couchView "mydesign" "myview" [] $ rowValue =$= toType =$ consume
toType :: (ResourceIO m, A.FromJSON a) => Conduit A.Value m a
toType = toTypeWith A.fromJSON