module Data.Either.Both where

import Data.Bifoldable (Bifoldable (..))
import Data.Bifunctor (Bifunctor (..))
import Data.Bitraversable (Bitraversable (..))
import Data.Functor.Classes

data Either' a b = JustLeft a | JustRight b | Both a b
  deriving (Read, Show)

either' :: (a -> c) -> (b -> c) -> (a -> b -> c) -> Either' a b -> c
either' f g h = \ case
    JustLeft a -> f a
    JustRight b -> g b
    Both a b -> h a b

instance Bifunctor Either' where
    bimap f g = \ case JustLeft a -> JustLeft (f a)
                       JustRight b -> JustRight (g b)
                       Both a b -> Both (f a) (g b)

instance Bifoldable Either' where
    bifoldMap f g = \ case JustLeft a -> f a
                           JustRight b -> g b
                           Both a b -> f a <> g b

instance Bitraversable Either' where
    bitraverse f g = \ case JustLeft a -> JustLeft <$> f a
                            JustRight b -> JustRight <$> g b
                            Both a b -> Both <$> f a <*> g b

instance Eq2 Either' where
    liftEq2 f g x y = case (toMaybes x, toMaybes y) of
        ((am₁, bm₁), (am₂, bm₂)) -> liftEq f am₁ am₂ && liftEq g bm₁ bm₂

instance Ord2 Either' where
    liftCompare2 f g x y = case (toMaybes x, toMaybes y) of
        ((am₁, bm₁), (am₂, bm₂)) -> liftCompare f am₁ am₂ <> liftCompare g bm₁ bm₂

instance (Eq a, Eq b) => Eq (Either' a b) where (==) = eq2
instance (Ord a, Ord b) => Ord (Either' a b) where compare = compare2

instance (Semigroup a, Semigroup b) => Semigroup (Either' a b) where
    JustLeft  a₁ <> JustLeft  a₂ = JustLeft  (a₁ <> a₂)
    JustRight b₁ <> JustRight b₂ = JustRight (b₁ <> b₂)
    JustLeft  a₁ <> JustRight b₁ = Both a₁ b₁
    JustRight b₁ <> JustLeft  a₁ = Both a₁ b₁
    JustLeft  a₁ <> Both a₂ b₂   = Both (a₁ <> a₂) b₂
    JustRight b₁ <> Both a₂ b₂   = Both a₂ (b₁ <> b₂)
    Both a₁ b₁   <> JustLeft  a₂ = Both (a₁ <> a₂) b₁
    Both a₁ b₁   <> JustRight b₂ = Both a₁ (b₁ <> b₂)
    Both a₁ b₁   <> Both a₂ b₂   = Both (a₁ <> a₂) (b₁ <> b₂)

instance (Monoid a, Monoid b) => Monoid (Either' a b) where
    mempty = Both mempty mempty

fromMaybes :: Maybe a -> Maybe b -> Maybe (Either' a b)
fromMaybes Nothing Nothing = Nothing
fromMaybes (Just a) Nothing = Just (JustLeft a)
fromMaybes Nothing (Just b) = Just (JustRight b)
fromMaybes (Just a) (Just b) = Just (Both a b)

toMaybes :: Either' a b -> (Maybe a, Maybe b)
toMaybes (JustLeft a) = (Just a, Nothing)
toMaybes (JustRight b) = (Nothing, Just b)
toMaybes (Both a b) = (Just a, Just b)