    {-# LANGUAGE CPP                         #-}
    {-# LANGUAGE DeriveGeneric               #-}
    {-# LANGUAGE FlexibleContexts            #-}
    {-# LANGUAGE GeneralizedNewtypeDeriving  #-}
    {-# LANGUAGE TypeOperators               #-}

    -- | Compatibility layer for "Data.Semigroup"
    module Distribution.Compat.Semigroup
        ( Semigroup((<>))
        , Mon.Monoid(..)
        , All(..)
        , Any(..)

        , Last'(..)

        , gmappend
        , gmempty
        ) where

    import Distribution.Compat.Binary (Binary)

    import Control.Applicative as App
    import GHC.Generics
    #if __GLASGOW_HASKELL__ >= 711
    -- Data.Semigroup is available since GHC 8.0/base-4.9
    import Data.Semigroup
    import qualified Data.Monoid as Mon
    #else
    -- provide internal simplified non-exposed class for older GHCs
    import Data.Monoid as Mon (Monoid(..), All(..), Any(..), Dual(..))
    -- containers
    import Data.Set (Set)
    import Data.IntSet (IntSet)
    import Data.Map (Map)
    import Data.IntMap (IntMap)

    class Semigroup a where
        (<>) :: a -> a -> a

    -- several primitive instances
    instance Semigroup () where
        _ <> _ = ()

    instance Semigroup [a] where
        (<>) = (++)

    instance Semigroup a => Semigroup (Dual a) where
        Dual a <> Dual b = Dual (b <> a)

    instance Semigroup a => Semigroup (Maybe a) where
        Nothing <> b       = b
        a       <> Nothing = a
        Just a  <> Just b  = Just (a <> b)

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

    instance Semigroup Ordering where
        LT <> _ = LT
        EQ <> y = y
        GT <> _ = GT

    instance Semigroup b => Semigroup (a -> b) where
        f <> g = \a -> f a <> g a

    instance Semigroup All where
        All a <> All b = All (a && b)

    instance Semigroup Any where
        Any a <> Any b = Any (a || b)

    instance (Semigroup a, Semigroup b) => Semigroup (a, b) where
        (a,b) <> (a',b') = (a<>a',b<>b')

    instance (Semigroup a, Semigroup b, Semigroup c)
             => Semigroup (a, b, c) where
        (a,b,c) <> (a',b',c') = (a<>a',b<>b',c<>c')

    instance (Semigroup a, Semigroup b, Semigroup c, Semigroup d)
             => Semigroup (a, b, c, d) where
        (a,b,c,d) <> (a',b',c',d') = (a<>a',b<>b',c<>c',d<>d')

    instance (Semigroup a, Semigroup b, Semigroup c, Semigroup d, Semigroup e)
             => Semigroup (a, b, c, d, e) where
        (a,b,c,d,e) <> (a',b',c',d',e') = (a<>a',b<>b',c<>c',d<>d',e<>e')

    -- containers instances
    instance Semigroup IntSet where
      (<>) = mappend

    instance Ord a => Semigroup (Set a) where
      (<>) = mappend

    instance Semigroup (IntMap v) where
      (<>) = mappend

    instance Ord k => Semigroup (Map k v) where
      (<>) = mappend
    #endif

    -- | Cabal's own 'Data.Monoid.Last' copy to avoid requiring an orphan
    -- 'Binary' instance.
    --
    -- Once the oldest `binary` version we support provides a 'Binary'
    -- instance for 'Data.Monoid.Last' we can remove this one here.
    --
    -- NB: 'Data.Semigroup.Last' is defined differently and not a 'Monoid'
    newtype Last' a = Last' { getLast' :: Maybe a }
                    deriving (Eq, Ord, Read, Show, Binary,
                              Functor, App.Applicative, Generic)

    instance Semigroup (Last' a) where
        x <> Last' Nothing = x
        _ <> x             = x

    instance Monoid (Last' a) where
        mempty = Last' Nothing
        mappend = (<>)

    -------------------------------------------------------------------------------
    -------------------------------------------------------------------------------
    -- Stolen from Edward Kmett's BSD3-licensed `semigroups` package

    -- | Generically generate a 'Semigroup' ('<>') operation for any type
    -- implementing 'Generic'. This operation will append two values
    -- by point-wise appending their component fields. It is only defined
    -- for product types.
    --
    -- @
    -- 'gmappend' a ('gmappend' b c) = 'gmappend' ('gmappend' a b) c
    -- @
    gmappend :: (Generic a, GSemigroup (Rep a)) => a -> a -> a
    gmappend x y = to (gmappend' (from x) (from y))

    class GSemigroup f where
        gmappend' :: f p -> f p -> f p

    instance Semigroup a => GSemigroup (K1 i a) where
        gmappend' (K1 x) (K1 y) = K1 (x <> y)

    instance GSemigroup f => GSemigroup (M1 i c f) where
        gmappend' (M1 x) (M1 y) = M1 (gmappend' x y)

    instance (GSemigroup f, GSemigroup g) => GSemigroup (f :*: g) where
        gmappend' (x1 :*: x2) (y1 :*: y2) = gmappend' x1 y1 :*: gmappend' x2 y2

    -- | Generically generate a 'Monoid' 'mempty' for any product-like type
    -- implementing 'Generic'.
    --
    -- It is only defined for product types.
    --
    -- @
    -- 'gmappend' 'gmempty' a = a = 'gmappend' a 'gmempty'
    -- @

    gmempty :: (Generic a, GMonoid (Rep a)) => a
    gmempty = to gmempty'

    class GSemigroup f => GMonoid f where
        gmempty' :: f p

    instance (Semigroup a, Monoid a) => GMonoid (K1 i a) where
        gmempty' = K1 mempty

    instance GMonoid f => GMonoid (M1 i c f) where
        gmempty' = M1 gmempty'

    instance (GMonoid f, GMonoid g) => GMonoid (f :*: g) where
        gmempty' = gmempty' :*: gmempty'