{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE Safe #-}
{-# LANGUAGE DeriveGeneric      #-}

-----------------------------------------------------------------------------
-- |
--
-- The strict variant of the standard Haskell 'L.Either' type and the
-- corresponding variants of the functions from "Data.Either".
--
-- Note that the strict 'Either' type is not an applicative functor, and
-- therefore also no monad. The reasons are the same as the ones for the
-- strict @Maybe@ type, which are explained in "Data.Maybe.Strict".
--
-----------------------------------------------------------------------------

module Data.Strict.Either (
    Either(..)
  , either
  , isLeft, isRight
  , fromLeft, fromRight
  , lefts, rights
  , partitionEithers
) where

-- import parts explicitly, helps with compatibility
import           Prelude ( Functor (..), Eq (..), Ord (..), Show (..), Read (..), Bool (..), (.), ($)
                         , error, Ordering (..), showParen, showString, lex, return, readParen)
import           Control.Applicative (pure, (<$>))
import           Data.Semigroup (Semigroup (..))
import           Data.Foldable (Foldable (..))
import           Data.Traversable (Traversable (..))

-- Lazy variants
import qualified Prelude             as L

import           Control.DeepSeq      (NFData (..), NFData1 (..), NFData2 (..))
import           Data.Bifoldable      (Bifoldable (..))
import           Data.Bifunctor       (Bifunctor (..))
import           Data.Bifunctor.Assoc (Assoc (..))
import           Data.Bifunctor.Swap  (Swap (..))
import           Data.Binary          (Binary (..))
import           Data.Bitraversable   (Bitraversable (..))
import           Data.Data            (Data (..), Typeable)
import           Data.Hashable        (Hashable(..))
import           Data.Hashable.Lifted (Hashable1 (..), Hashable2 (..))
import           GHC.Generics         (Generic, Generic1)

import Data.Functor.Classes
       (Eq1 (..), Eq2 (..), Ord1 (..), Ord2 (..), Read1 (..), Read2 (..),
       Show1 (..), Show2 (..))

-- | The strict choice type.
data Either a b = Left !a | Right !b
  deriving (Eq, Ord, Read, Show, Typeable, Data, Generic, Generic1)

toStrict :: L.Either a b -> Either a b
toStrict (L.Left x)  = Left x
toStrict (L.Right y) = Right y

toLazy :: Either a b -> L.Either a b
toLazy (Left x)  = L.Left x
toLazy (Right y) = L.Right y

-- | Case analysis: if the value is @'Left' a@, apply the first function to @a@;
-- if it is @'Right' b@, apply the second function to @b@.
either :: (a -> c) -> (b -> c) -> Either a b -> c
either f _ (Left  x) = f x
either _ g (Right y) = g y

-- | Yields 'True' iff the argument is of the form @Left _@.
--
isLeft :: Either a b -> Bool
isLeft (Left _) = True
isLeft _        = False

-- | Yields 'True' iff the argument is of the form @Right _@.
--
isRight :: Either a b -> Bool
isRight (Right _) = True
isRight _         = False

-- | Extracts the element out of a 'Left' and throws an error if the argument
-- is a 'Right'.
fromLeft :: Either a b -> a
fromLeft (Left x) = x
fromLeft _        = error "Data.Strict.Either.fromLeft: Right"

-- | Extracts the element out of a 'Right' and throws an error if the argument
-- is a 'Left'.
fromRight :: Either a b -> b
fromRight (Right x) = x
fromRight _         = error "Data.Strict.Either.fromRight: Left"

-- | Analogous to 'L.lefts' in "Data.Either".
lefts   :: [Either a b] -> [a]
lefts x = [a | Left a <- x]

-- | Analogous to 'L.rights' in "Data.Either".
rights   :: [Either a b] -> [b]
rights x = [a | Right a <- x]

-- | Analogous to 'L.partitionEithers' in "Data.Either".
partitionEithers :: [Either a b] -> ([a],[b])
partitionEithers =
    L.foldr (either left right) ([],[])
  where
    left  a ~(l, r) = (a:l, r)
    right a ~(l, r) = (l, a:r)

-- Instances
------------

instance Functor (Either a) where
  fmap _ (Left  x) = Left x
  fmap f (Right y) = Right (f y)

instance Foldable (Either e) where
  foldr _ y (Left _)  = y
  foldr f y (Right x) = f x y

  foldl _ y (Left _)  = y
  foldl f y (Right x) = f y x

instance Traversable (Either e) where
  traverse _ (Left x)  = pure (Left x)
  traverse f (Right x) = Right <$> f x

instance Semigroup (Either a b) where
  Left _ <> b = b
  a      <> _ = a

-- deepseq
instance (NFData a, NFData b) => NFData (Either a b) where
  rnf = rnf . toLazy

instance (NFData a) => NFData1 (Either a) where
  liftRnf rnfA = liftRnf rnfA . toLazy

instance NFData2 Either where
  liftRnf2 rnfA rnfB = liftRnf2 rnfA rnfB . toLazy

-- binary
instance (Binary a, Binary b) => Binary (Either a b) where
  put = put . toLazy
  get = toStrict <$> get

-- bifunctors
instance Bifunctor Either where
  bimap f _ (Left a) = Left (f a)
  bimap _ g (Right a) = Right (g a)
  first f = either (Left . f) Right
  second g = either Left (Right . g)

instance Bifoldable Either where
  bifold (Left a) = a
  bifold (Right b) = b
  bifoldMap = either
  bifoldr f _ c (Left a) = f a c
  bifoldr _ g c (Right b) = g b c
  bifoldl f _ c (Left a) = f c a
  bifoldl _ g c (Right b) = g c b

instance Bitraversable Either where
  bitraverse f _ (Left a) = fmap Left (f a)
  bitraverse _ g (Right b) = fmap Right (g b)

-- hashable
instance (Hashable a, Hashable b) => Hashable (Either a b) where
  hashWithSalt salt = hashWithSalt salt . toLazy

instance (Hashable a) => Hashable1 (Either a) where
  liftHashWithSalt hashA salt = liftHashWithSalt hashA salt . toLazy

instance Hashable2 Either where
  liftHashWithSalt2 hashA hashB salt = liftHashWithSalt2 hashA hashB salt . toLazy

-- assoc
instance Assoc Either where
    assoc (Left (Left a))  = Left a
    assoc (Left (Right b)) = Right (Left b)
    assoc (Right c)        = Right (Right c)

    unassoc (Left a)          = Left (Left a)
    unassoc (Right (Left b))  = Left (Right b)
    unassoc (Right (Right c)) = Right c

instance Swap Either where
    swap (Left x) = Right x
    swap (Right x) = Left x

-- Data.Functor.Classes
instance Eq2 Either where
  liftEq2 f _ (Left a)  (Left a')  = f a a'
  liftEq2 _ g (Right b) (Right b') = g b b'
  liftEq2 _ _ _         _          = False

instance Eq a => Eq1 (Either a) where
  liftEq = liftEq2 (==)

instance Ord2 Either where
  liftCompare2 f _ (Left a)    (Left a')     = f a a'
  liftCompare2 _ _ (Left _)    _             = LT
  liftCompare2 _ _ _           (Left _)      = GT
  liftCompare2 _ g (Right b)    (Right b')     = g b b'

instance Ord a => Ord1 (Either a) where
  liftCompare = liftCompare2 compare

instance Show a => Show1 (Either a) where
  liftShowsPrec = liftShowsPrec2 showsPrec showList

instance Show2 Either where
  liftShowsPrec2 sa _ _sb _ d (Left a) = showParen (d > 10)
    $ showString "Left "
    . sa 11 a
  liftShowsPrec2 _sa _ sb _ d (Right b) = showParen (d > 10)
    $ showString "Right "
    . sb 11 b

instance Read2 Either where
  liftReadsPrec2 ra _ rb _ d = readParen (d > 10) $ \s -> cons s
    where
      cons s0 = do
        (ident, s1) <- lex s0
        case ident of
            "Left" ->  do
                (a, s2) <- ra 11 s1
                return (Left a, s2)
            "Right" ->  do
                (b, s2) <- rb 11 s1
                return (Right b, s2)
            _ -> []

instance Read a => Read1 (Either a) where
  liftReadsPrec = liftReadsPrec2 readsPrec readList
