{-# OPTIONS_GHC -fno-warn-orphans #-}
{-# LANGUAGE MultiParamTypeClasses #-}

-- |
-- Module      : Data.Functor.ProductIsomorphic.Instances
-- Copyright   : 2017 Kei Hibino
-- License     : BSD3
--
-- Maintainer  : ex8k.hibino@gmail.com
-- Stability   : experimental
-- Portability : unknown
--
-- This module defines functor instances morphed functions
-- are restricted to products.
module Data.Functor.ProductIsomorphic.Instances (
  WrappedFunctor (..),
  WrappedAlter (..),
  ) where

import Data.Monoid (Monoid, mempty, (<>))
import Control.Applicative
  ((<$>), Applicative, pure, (<*>),
   Alternative, empty, (<|>),
   Const (..))

import Data.Functor.ProductIsomorphic.Class
  (ProductIsoFunctor(..), ProductIsoApplicative (..),
   ProductIsoAlternative (..), ProductIsoEmpty (..))


instance ProductIsoFunctor (Const a) where
  _ |$| Const a = Const a
  {-# INLINABLE (|$|) #-}

instance Monoid a => ProductIsoApplicative (Const a) where
  pureP _ = Const mempty
  {-# INLINABLE pureP #-}
  Const a |*| Const b = Const $ a <> b
  {-# INLINABLE (|*|) #-}

instance Monoid a => ProductIsoEmpty (Const a) () where
  pureE = pureP ()
  {-# INLINABLE pureE #-}
  peRight (Const a) = Const a
  {-# INLINABLE peRight #-}
  peLeft (Const a) = Const a
  {-# INLINABLE peLeft #-}


-- | Wrapped functor type to make instances of product-iso functors.
newtype WrappedFunctor f a = WrapFunctor { unwrapFunctor :: f a }

instance Functor f => ProductIsoFunctor (WrappedFunctor f) where
  f |$| fa = WrapFunctor $ f <$> unwrapFunctor fa
  {-# INLINABLE (|$|) #-}

instance Applicative f => ProductIsoApplicative (WrappedFunctor f) where
  pureP = WrapFunctor . pure
  {-# INLINABLE pureP #-}
  WrapFunctor ff |*| WrapFunctor fa = WrapFunctor $ ff <*> fa
  {-# INLINABLE (|*|) #-}

instance Alternative f => ProductIsoAlternative (WrappedFunctor f) where
  emptyP = WrapFunctor empty
  {-# INLINABLE emptyP #-}
  WrapFunctor fa1 ||| WrapFunctor fa2 = WrapFunctor $ fa1 <|> fa2
  {-# INLINABLE (|||) #-}

instance Applicative f => ProductIsoEmpty (WrappedFunctor f) () where
  pureE   = pureP ()
  {-# INLINABLE pureE #-}
  peRight = WrapFunctor . fmap fst . unwrapFunctor
  {-# INLINABLE peRight #-}
  peLeft  = WrapFunctor . fmap snd . unwrapFunctor
  {-# INLINABLE peLeft #-}


-- | Wrapped Const Alternative objects to make instances like Const functor.
newtype WrappedAlter f a b = WrapAlter { unWrapAlter :: Const (f a) b }

instance ProductIsoFunctor (WrappedAlter f a) where
  _ |$| WrapAlter (Const fa) = WrapAlter $ Const fa
  {-# INLINABLE (|$|) #-}

instance Alternative f => ProductIsoApplicative (WrappedAlter f a) where
  pureP _ = WrapAlter $ Const empty
  {-# INLINABLE pureP #-}
  WrapAlter (Const a) |*| WrapAlter (Const b) = WrapAlter $ Const $ a <|> b
  {-# INLINABLE (|*|) #-}

instance Alternative f => ProductIsoEmpty (WrappedAlter f a) () where
  pureE   = pureP ()
  {-# INLINABLE pureE #-}
  peRight = WrapAlter . fmap fst . unWrapAlter
  {-# INLINABLE peRight #-}
  peLeft  = WrapAlter . fmap snd . unWrapAlter