{-# LANGUAGE Rank2Types #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}

module Data.Bson.Lens
( _Float
, _String
, _Doc
, _Array
, _Bin
, _Fun
, _Uuid
, _Md5
, _UserDef
, _ObjId
, _Bool
, _UTC
, _Null
, _RegEx
, _JavaScr
, _Sym
, _Int32
, _Int64

, key
, values
, fields
, members
, nth
, labels

, AsValue(..)
, AsDocument(..)
)
where

import Data.Bson
import Control.Applicative
import Control.Lens
import Data.Text
-- import Data.Profunctor

$(makePrisms ''Value)


class AsDocument t where
  -- |
  -- A traversal for types that contain documents
  --
  -- >>> ["key" .= Bool True] ^? _Document
  -- Just [ key: True]
  --
  -- >>> Doc ["key" .= Bool True] ^? _Document
  -- Just [ key: True]
  _Document :: Traversal' t Document

instance AsDocument Value where
  _Document = _Doc

instance AsDocument Document where
  _Document = id

instance AsDocument Javascript where
  _Document = _JavascriptDoc

class AsValue t where
  -- |
  -- A traversal for types that contain 'Value's
  --
  -- >>> ("key" .= Bool True) ^? _Value
  -- Just True
  --
  -- >>> Doc ["key" .= Bool True] ^? _Value
  -- Just [ key: True]
  _Value :: Traversal' t Value

instance AsValue Value where
  _Value = id

instance AsValue Field where
  _Value = lens (\(_ := v)   -> v)
                (\(k := _) v -> k := v)

_JavascriptDoc :: Lens' Javascript Document
_JavascriptDoc = lens (\(Javascript d _) -> d)
                      (\(Javascript _ v) d -> Javascript d v)

-- |
-- Traversal into an 'Val' over a 'Value'
--
-- >>> Array [String "hey"] ^? nth 0 . _Val :: Maybe Text
-- Just "hey"
--
-- >>> "[10.5]" ^? nth 0 . _Integer
-- Just 10
--
-- >>> "42" ^? _Integer
-- Just 42
_Val :: (AsValue t, Val v) => Traversal' t v
_Val = _Value.prism' val cast'

_IndexedDocument :: Iso' Document IndexedDocument
_IndexedDocument = iso IndexedDocument runIndexedDocument

_Symbol :: AsValue v => Traversal' v Text
_Symbol = _Value._Sym.iso (\(Symbol t) -> t) Symbol


-- |
-- A lens for a Label in a Field 
--
-- >>> ("someKey" := Float 3.0) ^. _Label
-- "someKey"
--
-- >>> ("someKey" := Float 3.0) & _Label %~ Data.Text.toUpper
-- SOMEKEY: 3.0
_Label :: Lens' Field Label
_Label = lens (\(k := _)   -> k)
              (\(_ := v) k -> k := v)

-- |
-- Like 'ix', but for 'Document' values with Text indices
--
-- >>> ["someKey" := Float 3.0] ^? key "someKey"
-- Just 3.0
key :: AsDocument t => Label -> Traversal' t Value
key k = field k._Value


-- |
-- A traversal for a specific Document 'Field'.
-- This is similar to 'key' except it can target labels as well as values
--
-- >>> ["someKey" := Float 3.0] & field "someKey"._Label .~ "someOtherKey"
-- [ someOtherKey: 3.0 ]
field :: AsDocument t => Label -> Traversal' t Field
field k = _Document._IndexedDocument.ix k


-- | An indexed Traversal into Document fields
--
-- >>> Array [Doc ["doc1" := Float 3.0], Doc ["doc2" := Bool True]] ^.. values . fields
-- [ key: 3.0, key2: True ]
fields :: AsDocument v => IndexedTraversal' Int v Field
fields = _Document.traversed

-- | A Traversal into Document keys
--
-- >>> ["key" := Float 3.0, "key2" := Bool True] & labels %~ Data.Text.toUpper
-- ["KEY": 3.0, "KEY2": True]
labels :: AsDocument v => Traversal' v Label
labels = fields._Label

-- | A Traversal into Document values
--
-- >>> ["key" := Float 3.0, "key2" := Bool True] & members . _Float *~ 2
-- [ key: 6.0, key2: True ]
members :: AsDocument v => Traversal' v Value
members = fields._Value


-- | An indexed Traversal into Array elements
--
-- >>> Array [String "hey", Float 2.0] ^.. values
-- ["hey", 2.0]
--
-- >>> "Array [Float 2.0,Float 3.0,Float 4.0]" & values . _Float *~ 2.0
-- "[4.0,6.0,8.0]"
values :: AsValue v => IndexedTraversal' Int v Value
values = _Value._Array.traversed


-- | Like 'ix', but for Arrays with Int indexes
--
-- >>> "Array [Float 1.0, Float 2.0, Float 3.0]" ^? nth 1
-- Just 2.0
--
-- >>> "Array [Float 1.0, Float 2.0, Float 3.0]" & nth 1 .~ Float 20.0
-- "[1.0,20.0,3.0]"
nth :: AsValue v => Int -> Traversal' v Value
nth i = _Value._Array.ix i


newtype IndexedDocument = IndexedDocument { runIndexedDocument :: Document }
type instance IxValue IndexedDocument = Field
type instance Index IndexedDocument = Text
instance Ixed IndexedDocument where
  ix i f xs0 = IndexedDocument <$> go (runIndexedDocument xs0) i
    where
      go [] _     = pure []
      go (kv@(k' := v):as) k
        | i == k'   = f (k := v) <&> (:as)
        | otherwise = (kv:) <$> go as i