{-# LANGUAGE CPP #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE LambdaCase #-}
module Entwine.P (
  -- * Primitive types
  -- ** Bool
    Bool (..)
  , bool
  , (&&)
  , (||)
  , not
  , otherwise
  -- ** Char
  , Char
  -- ** Int
  , Integer
  , Int
  , Int8
  , Int16
  , Int32
  , Int64
  , div
  , toInteger
  -- ** Word
  , Word64
  -- ** Real
  , fromIntegral
  , fromRational

  , Double

  -- * Algebraic structures
  -- ** Semigroup
  , Semigroup (..)
  -- ** Monoid
  , Monoid (..)
  -- ** Functor
  , Functor (..)
  , (<$>)
  , ($>)
  , void
  , with
  -- ** Bifunctor
  , Bifunctor (..)
  -- ** Applicative
  , Applicative (..)
  , (<**>)
  , orA
  , andA
  , optional
  -- ** Alternative
  , Alternative (..)
  , asum
  -- ** Monad
  , Monad (..)
  , join
  , bind
  , when
  , unless
  , mapM_
  , forever
  , unlessM
  , whenM
  , ifM
  , guardM
  , filterM
  , (=<<)
  , liftM
  -- ** MonadPlus
  , MonadPlus (..)
  , guard
  , msum
  -- ** MonadIO
  , MonadIO (..)

  -- * Data structures
  -- ** Either
  , Either (..)
  , either
  , isRight
  -- ** Maybe
  , Maybe (..)
  , fromMaybe
  , maybe
  , isJust
  , isNothing
  , mapMaybe
  , maybeToRight
  , catMaybes
  , listToMaybe
  , rightToMaybe
  -- ** Tuple
  , fst
  , snd
  , curry
  , uncurry
  -- ** List
  , module List
  , ordNub
  , ordNubBy
  -- * Typeclasses
  -- ** Enum
  , Enum
  -- ** Num
  , Num (..)
  -- ** Eq
  , Eq (..)
  -- ** Read
  , Read (..)
  , readEither
  , readMaybe
  -- ** Show
  , Show (..)
  -- *** ShowS
  , ShowS
  , showString
  -- ** Foldable
  , Foldable (..)
  , for_
  , forM_
  , all
  , head
  , concat
  , concatMap
  -- ** Ord
  , Ord (..)
  , Ordering (..)
  , comparing
  -- ** Traversable
  , Traversable (..)
  , for
  , forM
  , traverse_

  -- * Combinators
  , id
  , (.)
  , ($)
  , ($!)
  , (&)
  , const
  , flip
  , fix
  , on
  , seq

  -- * Text functions
  , Text
  -- * Partial functions
  , undefined
  , error

  -- * Debugging facilities
  , trace
  , traceM
  , traceIO
  ) where


import           Control.Monad as Monad (
           Monad (..)
         , MonadPlus (..)
         , guard
         , join
         , msum
         , when
         , unless
         , guard
         , mapM_
         , forever
         , (=<<)
         , filterM
         , liftM
         )
import           Control.Monad.IO.Class (
           MonadIO (..)
         )
import           Control.Applicative as Applicative (
           Applicative (..)
         , (<**>)
         , Alternative (..)
         , empty
         , liftA2
         , optional
         )

import           Data.Bifunctor as Bifunctor (
           Bifunctor (..)
         )
import           Data.Bool as Bool (
           Bool (..)
         , bool
         , (&&)
         , (||)
         , not
         , otherwise
         )
import           Data.Char as Char (
           Char
         )
import           Data.Either as Either (
           Either (..)
         , either
         , isRight
         )
import           Data.Foldable as Foldable (
           Foldable (..)
         , asum
         , traverse_
         , for_
         , forM_
         , all
         , concat
         , concatMap
         )
import           Data.Function as Function (
           id
         , (.)
         , ($)
         , (&)
         , const
         , flip
         , fix
         , on
         )
import           Data.Functor as Functor (
           Functor (..)
         , (<$>)
         , ($>)
         , void
         )
import           Data.Eq as Eq (
           Eq (..)
         )
import           Data.Int as Int (
           Int
         , Int8
         , Int16
         , Int32
         , Int64
         )
import           Data.Maybe as Maybe (
           Maybe (..)
         , fromMaybe
         , maybe
         , isJust
         , isNothing
         , mapMaybe
         , catMaybes
         , listToMaybe
         )
import           Data.Semigroup as Semigroup (
           Semigroup (..)
         )
import           Data.Monoid as Monoid (
           Monoid (..)
         )
import           Data.Ord as Ord (
           Ord (..)
         , Ordering (..)
         , comparing
         )
import           Data.Traversable as Traversable (
           Traversable (..)
         , for
         , forM
         , mapM
         )
import           Data.Tuple as Tuple (
           fst
         , snd
         , curry
         , uncurry
         )
import           Data.Word as Word (
           Word64
         )
import           Data.List as List (
                     intercalate
                   , isPrefixOf
                   , drop
                   , splitAt
                   , break
                   , filter
                   , reverse
                   , any
                   , and
                   , notElem
#if (__GLASGOW_HASKELL__ < 710)
                   , length
                   , null
#endif
                   )
import qualified Debug.Trace as Trace
import           GHC.Exts as Exts (
           Double
         )
import           GHC.Real as Real (
           fromIntegral
         , fromRational
         , div
         )
#if MIN_VERSION_base(4,9,0)
import           GHC.Stack (HasCallStack)
#endif

import           Prelude as Prelude (
           Enum
         , Num (..)
         , Integer
         , toInteger
         , seq
         , ($!)
         )
import qualified Prelude as Unsafe

import           System.IO as IO (
           IO
         )
import           Data.Text as Text (Text)
import           Text.Read as Read (
           Read (..)
         , readEither
         , readMaybe
         )
import           Text.Show as Show (
           Show (..)
         , ShowS
         , showString
         )
import qualified Data.Set as Set

#if MIN_VERSION_base(4,9,0)
undefined :: HasCallStack => a
#else
undefined :: a
#endif
undefined =
  Unsafe.undefined
{-# WARNING undefined "'undefined' is unsafe" #-}

#if MIN_VERSION_base(4,9,0)
error :: HasCallStack => [Char] -> a
#else
error :: [Char] -> a
#endif
error =
  Unsafe.error
{-# WARNING error "'error' is unsafe" #-}

trace :: [Char] -> a -> a
trace =
  Trace.trace
{-# WARNING trace "'trace' should only be used while debugging" #-}

#if MIN_VERSION_base(4,9,0)
traceM :: Applicative f => [Char] -> f ()
#else
traceM :: Monad m => [Char] -> m ()
#endif
traceM =
  Trace.traceM
{-# WARNING traceM "'traceM' should only be used while debugging" #-}

traceIO :: [Char] -> IO ()
traceIO =
  Trace.traceIO
{-# WARNING traceIO "'traceIO' should only be used while debugging" #-}

with :: Functor f => f a -> (a -> b) -> f b
with =
  flip fmap
{-# INLINE with #-}

whenM :: Monad m => m Bool -> m () -> m ()
whenM p m =
  p >>= flip when m

unlessM :: Monad m => m Bool -> m () -> m ()
unlessM p m =
  p >>= flip unless m

ifM :: Monad m => m Bool -> m a -> m a -> m a
ifM p x y =
  p >>= \b -> if b then x else y

guardM :: MonadPlus m => m Bool -> m ()
guardM f = guard =<< f

-- | Identifier version of 'Control.Monad.=<<'.
bind :: Monad m => (a -> m b) -> m a -> m b
bind = (=<<)

head :: (Foldable f) => f a -> Maybe a
head = foldr (\x _ -> return x) Nothing

-- | Logical disjunction.
orA :: Applicative f => f Bool -> f Bool -> f Bool
orA =
  liftA2 (||)

-- | Logical conjunction.
andA :: Applicative f => f Bool -> f Bool -> f Bool
andA =
  liftA2 (&&)

infixl 8 `andA`, `orA`

maybeToRight :: l -> Maybe r -> Either l r
maybeToRight l = maybe (Left l) Right

rightToMaybe :: Either l r -> Maybe r
rightToMaybe = either (const Nothing) Just

-- | /O(n log n)/ Remove duplicate elements from a list.
--
--   Unlike 'Data.List.nub', this version requires 'Ord' and runs in
--   /O(n log n)/ instead of /O(n²)/. Like 'Data.List.nub' the output
--   order is identical to the input order.
--   See 'sortNub' for `nub` behaviour with sorted output.
--
--   > ordNub "foo bar baz" == "fo barz"
--   > ordNub [3,2,1,2,1] == [3,2,1]
--   > List.take 3 (ordNub [4,5,6,undefined]) == [4,5,6]
--   > ordNub xs == List.nub xs
--
ordNub :: Ord a => [a] -> [a]
ordNub =
  ordNubBy compare

-- | /O(n log n)/ Behaves exactly like 'ordNub' except it uses a user-supplied
--  comparison function.
--
--  > ordNubBy (comparing length) ["foo","bar","quux"] == ["foo","quux"]
--  > ordNubBy (comparing fst) [("foo", 10),("foo", 20),("bar", 30)] == [("foo", 10),("bar", 30)]
--
ordNubBy :: (a -> a -> Ordering) -> [a] -> [a]
ordNubBy f =
  let
    loop seen = \case
      [] ->
        []
      x : xs ->
        let
          y =
            UserOrd f x
        in
          if Set.member y seen then
            loop seen xs
          else
            x : loop (Set.insert y seen) xs
   in
     loop Set.empty

--
-- Some machinery so we can use Data.Set with a custom comparator.
--

data UserOrd a =
  UserOrd (a -> a -> Ordering) a

instance Eq (UserOrd a) where
  (==) (UserOrd f x) (UserOrd _ y) =
    f x y == EQ

instance Ord (UserOrd a) where
  compare (UserOrd f x) (UserOrd _ y) =
    f x y