{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE PatternSynonyms #-}
-- |
-- Module: Data.Text.Optics
-- Description: Optics for working with strict or lazy 'Text'.
--
-- This module provides 'Iso's for converting strict or lazy 'Text' to or from a
-- 'String' or 'Builder', and an 'IxTraversal' for traversing the individual
-- characters of a 'Text'.
--
-- The same combinators support both strict and lazy text using the 'IsText'
-- typeclass.  You can import "Data.Text.Strict.Optics" or
-- "Data.Text.Lazy.Optics" instead if you prefer monomorphic versions.
--
module Data.Text.Optics
  ( IsText(..)
  , unpacked
  , _Text
  , pattern Text
  ) where

import Data.Text as Strict
import Data.Text.Lazy as Lazy
import Data.Text.Lazy.Builder

import Optics.Core
import qualified Data.Text.Lazy.Optics as Lazy
import qualified Data.Text.Strict.Optics as Strict

-- | Traversals for strict or lazy 'Text'
class IsText t where
  -- | This isomorphism can be used to 'pack' (or 'unpack') strict or lazy
  -- 'Text'.
  --
  -- @
  -- 'pack' x ≡ x 'Optics.Operators.^.' 'packed'
  -- 'unpack' x ≡ x 'Optics.Operators.^.' 're' 'packed'
  -- 'packed' ≡ 're' 'unpacked'
  -- @
  packed :: Iso' String t

  -- | Convert between strict or lazy 'Text' and a 'Builder'.
  --
  -- @
  -- 'fromText' x ≡ x 'Optics.Operators.^.' 'builder'
  -- @
  builder :: Iso' t Builder

  -- | Traverse the individual characters in strict or lazy 'Text'.
  --
  -- @
  -- 'text' = 'unpacked' . 'traversed'
  -- @
  text :: IxTraversal' Int t Char
  text = unpacked % itraversed
  {-# INLINE text #-}

instance IsText String where
  packed  = iso id id
  text    = itraversed
  builder = Lazy.packed % builder
  {-# INLINE packed #-}
  {-# INLINE text #-}
  {-# INLINE builder #-}

-- | This isomorphism can be used to 'unpack' (or 'pack') both strict or lazy
-- 'Text'.
--
-- @
-- 'unpack' x ≡ x 'Optics.Operators.^.' 'unpacked'
-- 'pack' x ≡ x 'Optics.Operators.^.' 're' 'unpacked'
-- @
--
-- This 'Iso' is provided for notational convenience rather than out of great
-- need, since
--
-- @
-- 'unpacked' ≡ 're' 'packed'
-- @
--
unpacked :: IsText t => Iso' t String
unpacked = re packed
{-# INLINE unpacked #-}

-- | This is an alias for 'unpacked' that makes it clearer how to use it with
-- @('Optics.Operators.#')@.
--
-- @
-- '_Text' = 're' 'packed'
-- @
--
-- >>> _Text # "hello" :: Strict.Text
-- "hello"
_Text :: IsText t => Iso' t String
_Text = re packed
{-# INLINE _Text #-}

pattern Text :: IsText t => String -> t
pattern Text a <- (view _Text -> a) where
  Text a = review _Text a

instance IsText Strict.Text where
  packed  = Strict.packed
  builder = Strict.builder
  text    = Strict.text
  {-# INLINE packed #-}
  {-# INLINE builder #-}
  {-# INLINE text #-}

instance IsText Lazy.Text where
  packed  = Lazy.packed
  builder = Lazy.builder
  text    = Lazy.text
  {-# INLINE packed #-}
  {-# INLINE builder #-}
  {-# INLINE text #-}
