{-# LANGUAGE ConstraintKinds, DefaultSignatures, GADTs #-}


-- | A set of typeclasses 'Falsifier', 'Andlike', 'Orlike', and 'Xorlike',
-- that define operations dealing with boolean-representable structures such
-- as 'Maybe' which has true-like 'Just' and false-like 'Nothing', or '[]' by
-- true-like non-empty list and false-like empty list.
module Combinator.Booly
    ( Andlike(..)
    , Orlike(..)
    , Xorlike(..)
    , Falsifier(..)
    , (>&>)
    , (>|>)
    , andLast
    , andHead
    , andMappend
    , andMconcat
    , isFalse
    , isTrue
    , boolF
    , voidF
    , whenF
    , unlessF
    )
    where


import Control.Applicative (Alternative(..))

-- FIXME both strict and lazy structures when necessary
import qualified Data.Attoparsec.Internal.Types as Atto
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as BL
import qualified Data.Map as Map
import Data.Semigroup (Semigroup(..), Option(..))
import qualified Data.Text as T
import qualified Data.Vector as Vec


infixr 7 >&>
infixl 7 <&<
infixr 5 >|>
infixl 5 <|<
infixl 6 <^>


-- | Boolean-like logic operation '>&>' that acts like AND for any
-- boolean-representable datatypes, e.g. '[]' or 'Maybe'.
--
-- __Associativity__
--
-- prop> (a >&> b) >&> c == a >&> (b >&> c)
--
-- __Absorbing element / truth table__
--
-- prop> false >&> false == false
-- prop> false >&> b == false
-- prop> a >&> false == false
-- prop> a >&> b == b
--
class Andlike a where
    -- | Andlike operator, returns the rightmost argument on success, i.e.
    --   if no 'false' are present.
    (<&<) :: a -> a -> a

    -- | '<*' often shares behaviour with '<&<'.
    default (<&<) :: (Applicative f, f b ~ a) => a -> a -> a
    (<&<) = (<*)

-- | Boolean-like logic operation '<|<' that acts like OR for any
-- boolean-representable datatypes, e.g. '[]' or 'Maybe'. It is basically
-- 'Control.Applicative.(<|>)' with a list instance that doesn't append.
--
-- __Associativity__
--
-- prop> (a <|< b) <|< c == a <|< (b <|< c)
--
-- __Absorbing element / truth table__
--
-- prop> false <|< false == false
-- prop> false <|< b == b
-- prop> a <|< false == a
-- prop> a <|< b == a
--
class Orlike a where
    -- | Orlike operator, returns the leftmost true-like argument,
    -- otherwise the rightmost true-like argument, or finally 'false'.
    (<|<) :: a -> a -> a

    -- | All '<|>' instances except list-likes should share behaviour.
    default (<|<) :: (Alternative f, f b ~ a) => a -> a -> a
    (<|<) = (<|>)

-- | Boolean-like logic operation '<^>' that acts like XOR for any
-- boolean-representable datatypes, e.g. '[]' or 'Maybe'.
--
-- __Absorbing element / truth table__
--
-- prop> false <^> false == false
-- prop> false <^> b == b
-- prop> a <^> false == a
-- prop> a <^> b == false
--
class Xorlike a where
    -- | Xorlike operator, returns whichever argument is true-like as both
    -- cannot simultaneously be true-like values, or 'false'.
    (<^>) :: a -> a -> a

class Falsifier a where
    false :: a

    default false :: Monoid a => a
    false = mempty

-- {{{ Instances

instance Andlike () where
    _ <&< _ = ()

instance Orlike () where
    _ <|< _ = ()

instance Xorlike () where
    _ <^> _ = ()

instance Falsifier ()


instance Andlike Bool where
    (<&<) = (&&)

instance Orlike Bool where
    (<|<) = (||)

instance Xorlike Bool where
    ba <^> bb = (ba || bb) && (not ba || not bb)

instance Falsifier Bool where
    false = False


instance Andlike (Maybe a) where
    Nothing <&< _ = Nothing
    _ <&< Nothing = Nothing
    a <&< _ = a

instance Orlike (Maybe a) where
    (Just a) <|< _ = Just a
    _ <|< (Just a) = Just a
    _ <|< _ = Nothing

instance Xorlike (Maybe a) where
    (Just a) <^> Nothing = Just a
    Nothing <^> (Just a) = Just a
    _ <^> _ = Nothing

instance Falsifier (Maybe a) where
    false = Nothing


instance Andlike (Option a) where
    (Option Nothing) <&< _ = Option Nothing
    _ <&< (Option Nothing) = Option Nothing
    a <&< _ = a

instance Orlike (Option a) where
    (Option (Just a)) <|< _ = Option (Just a)
    _ <|< (Option (Just a)) = Option (Just a)
    _ <|< _ = Option Nothing

instance Xorlike (Option a) where
    (Option (Just a)) <^> (Option Nothing) = Option (Just a)
    (Option Nothing) <^> (Option (Just a)) = Option (Just a)
    _ <^> _ = Option Nothing

instance Falsifier (Option a) where
    false = Option Nothing


instance Andlike (Either a b) where
    (Left a) <&< _ = Left a
    _ <&< (Left b) = Left b
    a <&< _ = a

instance Orlike (Either a b) where
    (Right a) <|< _ = Right a
    _ <|< (Right b) = Right b
    (Left a) <|< _ = Left a


instance Andlike ([] a) where
    [] <&< _ = []
    _ <&< [] = []
    a <&< _ = a

instance Orlike ([] a) where
    xs@(_:_) <|< _ = xs
    _ <|< xs@(_:_) = xs
    _ <|< _ = []

instance Xorlike ([] a) where
    xs@(_:_) <^> [] = xs
    [] <^> xs@(_:_) = xs
    _ <^> _ = []

instance Falsifier ([] a)


instance Andlike T.Text where
    ta <&< tb
        | T.null ta || T.null tb = T.empty
        | otherwise              = ta

instance Orlike T.Text where
    ta <|< tb
        | not (T.null ta) = ta
        | not (T.null tb) = tb
        | otherwise = T.empty

instance Xorlike T.Text where
    ta <^> tb
        | not (T.null ta) && T.null tb = ta
        | T.null ta && not (T.null tb) = tb
        | otherwise = T.empty

instance Falsifier T.Text


instance Andlike BS.ByteString where
    ba <&< bb
        | BS.null ba || BS.null bb = BS.empty
        | otherwise                = ba
instance Andlike BL.ByteString where
    ba <&< bb
        | BL.null ba || BL.null bb = BL.empty
        | otherwise                = ba

instance Orlike BS.ByteString where
    ta <|< tb
        | not (BS.null ta) = ta
        | not (BS.null tb) = tb
        | otherwise = BS.empty
instance Orlike BL.ByteString where
    ta <|< tb
        | not (BL.null ta) = ta
        | not (BL.null tb) = tb
        | otherwise = BL.empty

instance Xorlike BS.ByteString where
    ta <^> tb
        | not (BS.null ta) && BS.null tb = ta
        | BS.null ta && not (BS.null tb) = tb
        | otherwise = BS.empty
instance Xorlike BL.ByteString where
    ta <^> tb
        | not (BL.null ta) && BL.null tb = ta
        | BL.null ta && not (BL.null tb) = tb
        | otherwise = BL.empty

instance Falsifier BS.ByteString
instance Falsifier BL.ByteString


instance Ord k => Andlike (Map.Map k v) where
    ma <&< mb
        | Map.null ma || Map.null mb = Map.empty
        | otherwise = ma

instance Ord k => Orlike (Map.Map k v) where
    ma <|< mb
        | not (Map.null ma) = ma
        | not (Map.null mb) = mb
        | otherwise       = Map.empty

instance Ord k => Xorlike (Map.Map k v) where
    ma <^> mb
        | not (Map.null ma) && Map.null mb = ma
        | Map.null ma && not (Map.null mb) = mb
        | otherwise = Map.empty

instance Ord k => Falsifier (Map.Map k v) where
    false = Map.empty


instance Andlike (Vec.Vector a)

instance Orlike (Vec.Vector a)

instance Xorlike (Vec.Vector a) where
    va <^> vb
        | not (Vec.null va) && Vec.null vb = va
        | Vec.null va && not (Vec.null vb) = vb
        | otherwise = Vec.empty

instance Falsifier (Vec.Vector a)


instance Andlike (Atto.Parser i a)

instance Orlike (Atto.Parser i a)

-- TODO
--instance Xorlike (Atto.Parser i a) where

instance Falsifier (Atto.Parser i a)


instance (Andlike a, Andlike b) => Andlike (a, b) where
    (a1, b1) <&< (a2, b2) = (a1 <&< a2, b1 <&< b2)

instance (Orlike a, Orlike b) => Orlike (a, b) where
    (a1, b1) <|< (a2, b2) = (a1 <|< a2, b1 <|< b2)

instance (Andlike a, Andlike b, Andlike c) => Andlike (a, b, c) where
    (a1, b1, c1) <&< (a2, b2, c2) = (a1 <&< a2, b1 <&< b2, c1 <&< c2)

instance (Orlike a, Orlike b, Orlike c) => Orlike (a, b, c) where
    (a1, b1, c1) <|< (a2, b2, c2) = (a1 <|< a2, b1 <|< b2, c1 <|< c2)

instance (Andlike a, Andlike b, Andlike c, Andlike d) => Andlike (a, b, c, d) where
    (a1, b1, c1, d1) <&< (a2, b2, c2, d2) = (a1 <&< a2, b1 <&< b2, c1 <&< c2, d1 <&< d2)

instance (Orlike a, Orlike b, Orlike c, Orlike d) => Orlike (a, b, c, d) where
    (a1, b1, c1, d1) <|< (a2, b2, c2, d2) = (a1 <|< a2, b1 <|< b2, c1 <|< c2, d1 <|< d2)


-- }}}


-- | Flipped version of '<&<'. Returns the leftmost argument on both
--   success or failure.
(>&>) :: Andlike a => a -> a -> a
(>&>) = flip (<&<)

-- | Flipped version of '<|<'. Returns the leftmost argument on both
--   success or failure.
(>|>) :: Orlike a => a -> a -> a
(>|>) = flip (<|<)

-- | Returns the first element on success of all values.
andHead :: (Andlike a, Falsifier a, Foldable t) => t a -> a
andHead as
    | null as   = false
    | otherwise = foldr1 (<&<) as

-- | Returns the last element on success of all values.
andLast :: (Andlike a, Falsifier a, Foldable t) => t a -> a
andLast as
    | null as   = false
    | otherwise = foldr1 (>&>) as

-- | Monadic append with the annihilating operator guarding each argument.
--   Returns the mappended result on success.
andMappend :: (Andlike a, Monoid a) => a -> a -> a
andMappend a b = (a <&< b) `mappend` (a >&> b)

-- | Monadic concatenation with the annihilating operator guarding each argument.
andMconcat :: (Andlike a, Falsifier a, Monoid a, Foldable t) => t a -> a
andMconcat as
    | null as   = false
    | otherwise = foldr1 andMappend as

isFalse :: (Eq a, Falsifier a) => a -> Bool
isFalse = (false ==)

isTrue :: (Eq a, Falsifier a) => a -> Bool
isTrue = not . isFalse

-- | Similar to 'Data.Bool.bool'.
boolF :: (Eq b, Falsifier b) => a -> a -> b -> a
boolF a b f = if isTrue f then a else b

-- | Discard the argument and return 'false'.
voidF :: Falsifier a => a -> a
voidF = const false

-- | Similar to `when` but takes a boolean-like and returns `false`
--   instead of `()`.
whenF :: (Eq a, Eq b, Falsifier a, Falsifier b) => a -> b -> b
whenF fa fb = if isTrue fa then fb else false

-- | Similar to `unless` but takes a boolean-like and returns `false`
--   instead of `()`.
unlessF :: (Eq a, Eq b, Falsifier a, Falsifier b) => a -> b -> b
unlessF fa fb = if isFalse fa then fb else false