{-# LANGUAGE DeriveDataTypeable   #-}
{-# LANGUAGE DeriveFoldable       #-}
{-# LANGUAGE DeriveFunctor        #-}
{-# LANGUAGE DeriveGeneric        #-}
{-# LANGUAGE DeriveTraversable    #-}
{-# LANGUAGE FlexibleContexts     #-}
{-# LANGUAGE ScopedTypeVariables  #-}
{-# LANGUAGE TypeApplications     #-}
{-# LANGUAGE UndecidableInstances #-}

-- |
-- Module      : Data.Bounded.OneLiner
-- Description : Derived methods for Semigroup.
-- Copyright   : (c) Justin Le 2018
-- License     : BSD-3
-- Maintainer  : justin@jle.im
-- Stability   : unstable
-- Portability : portable
--
-- Derived methods for 'Bounded', using "Generics.OneLiner" and
-- "GHC.Generics".
--
-- Can be used for any types (deriving 'Generic') where every field is an
-- instance of 'Bounded'.
--
-- Also includes a newtype wrapper that imbues any such data type with an
-- instant 'Bounded' instance, which can one day be used with /DerivingVia/
-- syntax to derive instances automatically.
--

module Data.Bounded.OneLiner (
  -- * Newtype wrapper
    GBounded(..)
  -- * Generics-derived methods
  , gMinBound
  , gMaxBound
  ) where

import           Data.Coerce
import           Data.Data
import           GHC.Generics
import           Generics.OneLiner

-- | If @a@ is a data type whose fields are all instances of 'Bounded',
-- then @'GBounded' a@ has a 'Bounded' instance.
--
-- Will one day be able to be used with /DerivingVia/ syntax, to derive
-- instances automatically.
--
newtype GBounded a = GBounded { getGBounded :: a }
  deriving (Eq, Ord, Show, Read, Data, Generic, Functor, Foldable, Traversable)

instance ( ADT a
         , Constraints a Bounded
         )
      => Bounded (GBounded a) where
    minBound = coerce (gMinBound @a)
    {-# INLINE minBound #-}
    maxBound = coerce (gMaxBound @a)
    {-# INLINE maxBound #-}

-- | 'minBound' implemented by using 'minBound' for all of the components
-- for the first constructor
gMinBound
    :: forall a. (ADT a, Constraints a Bounded)
    => a
gMinBound = case create @Bounded [minBound] of
              []  -> error "minBound: uninhabited"
              x:_ -> x
{-# INLINE gMinBound #-}

-- | 'maxBound' implemented by using 'maxBound' for all of the components
-- for the last constructor
gMaxBound
    :: forall a. (ADT a, Constraints a Bounded)
    => a
gMaxBound = case reverse (create @Bounded [maxBound]) of
              []  -> error "maxBound: uninhabited"
              x:_ -> x
{-# INLINE gMaxBound #-}