{-# LANGUAGE Rank2Types      #-}
{-# LANGUAGE TemplateHaskell #-}

module Control.Lens.Format where

import Control.Monad ((>=>))
import Control.Lens
import Data.Functor.Invariant.TH

{- | A normalizing optic, isomorphic to Prism but with different laws, specifically `getMaybe` needs not to
be injective; i.e., distinct inputs may have the same `getMaybe` result, which combined with a subsequent
`reverseGet` yields a normalized form for `a`. Composition with stronger optics (`Prism` and `Iso`) yields
another `Format`.
-}
data Format a b = Format
    { getMaybe :: a -> Maybe b
    , reverseGet :: b -> a
    }
$(deriveInvariant ''Format)

-- | `getMaybe` and `reverseGet`, yielding a normalized formatted value. Subsequent getMaybe/reverseGet cycles are idempotent.
normalize :: Format a b -> a -> Maybe a
normalize (Format f g) x = g <$> f x

-- | Compose with a Prism.
composePrism :: Format a b -> Prism' b c -> Format a c
composePrism (Format x y) p =
    Format (x >=> (^? p)) (y . review p)

-- | Compose with an Iso.
composeIso :: Format a b -> Iso' b c -> Format a c
composeIso (Format x y) i =
    Format (fmap (^. i) . x) (y . review i)

-- | A Prism is trivially a Format.
fromPrism :: Prism' a b -> Format a b
fromPrism p = Format (^? p) (review p)

-- | An Isomorphism is trivially a Format.
fromIso :: Iso' a b -> Format a b
fromIso i = Format (^? i) (review i)