{-# LANGUAGE DeriveFoldable        #-}
{-# LANGUAGE DeriveFunctor         #-}
{-# LANGUAGE DeriveTraversable     #-}
{-# LANGUAGE FlexibleInstances     #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NoImplicitPrelude     #-}
{-# LANGUAGE RankNTypes            #-}
{-# LANGUAGE TypeFamilies          #-}
-- | JSON Array representation and functions.
module Waargonaut.Types.JArray
  (
    -- * Types
    JArray (..)

    -- * Parser / Builder
  , parseJArray
  , jArrayBuilder
  ) where

import           Prelude                   (Eq, Show, Int)

import           Control.Category          ((.))
import           Control.Error.Util        (note)
import           Control.Lens              (AsEmpty (..), Cons (..), Rewrapped, Ixed (..), Index, IxValue,
                                            Wrapped (..), cons, isn't, iso,
                                            nearly, over, prism, to, ( # ),
                                            (^.), (^?), _2, _Wrapped)
import           Control.Monad             (Monad)

import           Data.Bifoldable           (Bifoldable (bifoldMap))
import           Data.Bifunctor            (Bifunctor (bimap))
import           Data.Bitraversable        (Bitraversable (bitraverse))
import           Data.Foldable             (Foldable)
import           Data.Function             (($))
import           Data.Functor              (Functor, (<$>))
import           Data.Monoid               (Monoid (..), mempty)
import           Data.Semigroup            (Semigroup (..))
import           Data.Traversable          (Traversable)

import           Data.ByteString.Builder   (Builder)

import           Text.Parser.Char          (CharParsing, char)

import           Waargonaut.Types.CommaSep (CommaSeparated,
                                            commaSeparatedBuilder,
                                            parseCommaSeparated)

-- $setup
-- >>> :set -XOverloadedStrings
-- >>> import Utils
-- >>> import Waargonaut.Types.Json
-- >>> import Waargonaut.Types.Whitespace
-- >>> import Control.Monad (return)
-- >>> import Data.Either (Either (..), isLeft)
-- >>> import Waargonaut.Decode.Error (DecodeError)
----

-- | Conveniently, a JSON array is a 'CommaSeparated' list with an optional
-- trailing comma, some instances and other functions need to work differently so
-- we wrap it up in a newtype.
newtype JArray ws a =
  JArray (CommaSeparated ws a)
  deriving (Eq, Show, Functor, Foldable, Traversable)

instance JArray ws a ~ t => Rewrapped (JArray ws a) t
instance Wrapped (JArray ws a) where
  type Unwrapped (JArray ws a) = CommaSeparated ws a
  _Wrapped' = iso (\(JArray x) -> x) JArray

instance Monoid ws => Cons (JArray ws a) (JArray ws a) a a where
  _Cons = prism
    (\(a,j) -> over _Wrapped (cons a) j)
    (\j -> note j $ over _2 (_Wrapped #) <$> j ^? _Wrapped . _Cons)
  {-# INLINE _Cons #-}

instance (Semigroup ws, Monoid ws) => AsEmpty (JArray ws a) where
  _Empty = nearly (JArray mempty) (^. _Wrapped . to (isn't _Empty))
  {-# INLINE _Empty #-}

instance (Monoid ws, Semigroup ws) => Semigroup (JArray ws a) where
  (JArray a) <> (JArray b) = JArray (a <> b)

instance (Semigroup ws, Monoid ws) => Monoid (JArray ws a) where
  mempty = JArray mempty
  mappend = (<>)

type instance IxValue (JArray ws a) = a
type instance Index (JArray ws a)   = Int

instance Ixed (JArray ws a) where
  ix i f (JArray cs) = JArray <$> ix i f cs

instance Bifunctor JArray where
  bimap f g (JArray cs) = JArray (bimap f g cs)

instance Bifoldable JArray where
  bifoldMap f g (JArray cs) = bifoldMap f g cs

instance Bitraversable JArray where
  bitraverse f g (JArray cs) = JArray <$> bitraverse f g cs

-- | Parse a single JSON array
--
-- >>> testparse (parseJArray parseWhitespace parseWaargonaut) "[null ]"
-- Right (JArray (CommaSeparated (WS []) (Just (Elems {_elemsElems = [], _elemsLast = Elem {_elemVal = Json (JNull (WS [Space])), _elemTrailing = Nothing}}))))
--
-- >>> testparse (parseJArray parseWhitespace parseWaargonaut) "[null,]"
-- Right (JArray (CommaSeparated (WS []) (Just (Elems {_elemsElems = [], _elemsLast = Elem {_elemVal = Json (JNull (WS [])), _elemTrailing = Just (Comma,WS [])}}))))
--
parseJArray
  :: ( Monad f
     , CharParsing f
     )
  => f ws
  -> f a
  -> f (JArray ws a)
parseJArray ws a = JArray <$>
  parseCommaSeparated (char '[') (char ']') ws a

-- | Using the given builders, build a 'JArray'.
jArrayBuilder
  :: (ws -> Builder)
  -> ((ws -> Builder) -> a -> Builder)
  -> JArray ws a
  -> Builder
jArrayBuilder ws a (JArray cs) =
  commaSeparatedBuilder '[' ']' ws (a ws) cs