{-# LANGUAGE TypeOperators              #-}
{-# LANGUAGE FlexibleContexts           #-}

-----------------------------------------------------------------------------
-- |
-- Module      :  Generics.EMGM.Functions.Compare
-- Copyright   :  (c) 2008 Universiteit Utrecht
-- License     :  BSD3
--
-- Maintainer  :  generics@haskell.org
-- Stability   :  experimental
-- Portability :  non-portable
--
-- Summary: Generic functions for comparing two values in different ways.
--
-- The fundamental function here is 'compare', a function that returns the
-- 'Ordering' of two values (less than, equal to, or greater than). It uses the
-- same lexicographical ordering as @deriving Ord@ (e.g. left alternative of a
-- sum is less than the right alternative, the first component of a product is
-- compared first while the second is only compared if the first is equal,
-- etc.).
--
-- All of the remaining functions are simply derived (in the most obvious way)
-- from 'compare'. All of these functions are equivalent to methods in the 'Eq'
-- and 'Ord' type classes. The difference with using this approach vs. @deriving
-- (Eq, Ord)@ is that you can write ad-hoc cases for certain datatypes while
-- most of the functionality is handled generically.
-----------------------------------------------------------------------------

module Generics.EMGM.Functions.Compare (

  -- * Compare
  -- | 'compare' is equivalent to the function of the same name when @deriving
  -- Ord@. All other comparison functions in this module are derived from
  -- 'compare'.
  Compare(..),
  compare,

  -- * Equality, inequality
  -- | These functions are equivalent to @(==)@ and @(/=)@ when @deriving Eq@.
  eq,
  neq,

  -- * Less than, greater than
  -- | These functions are equivalent to @(\<)@, @(\<=)@, @(>)@, and @(>=)@ when
  -- @deriving Ord@.
  lt,
  lteq,
  gt,
  gteq,

  -- * Minimum and maximum
  -- | These functions are equivalent to functions of the same name when
  -- @deriving Ord@.
  min,
  max,
) where

import Prelude hiding (compare, min, max)
import qualified Prelude as P (compare)

import Generics.EMGM.Common

-----------------------------------------------------------------------------
-- Types
-----------------------------------------------------------------------------

-- | The type of a generic function that takes two values of the same type and
-- returns an 'Ordering'.
newtype Compare a = Compare { selCompare :: a -> a -> Ordering }

-----------------------------------------------------------------------------
-- Generic instance declaration
-----------------------------------------------------------------------------

rconstantCompare :: (Ord a) => a -> a -> Ordering
rconstantCompare = P.compare

rsumCompare :: Compare a -> Compare b -> a :+: b -> a :+: b -> Ordering
rsumCompare ra _  (L a1) (L a2) = {-EQ-} selCompare ra a1 a2
rsumCompare _  rb (R b1) (R b2) = {-EQ-} selCompare rb b1 b2
rsumCompare _  _  (L _)  (R _)  =   LT
rsumCompare _  _  (R _)  (L _)  =   GT

rprodCompare :: Compare a -> Compare b -> a :*: b -> a :*: b -> Ordering
rprodCompare ra rb (a1 :*: b1) (a2 :*: b2) =
  case selCompare ra a1 a2 of
    EQ    -> selCompare rb b1 b2
    other -> other

rconCompare :: ConDescr -> Compare a -> a -> a -> Ordering
rconCompare _ = selCompare

rtypeCompare :: EP a b -> Compare b -> a -> a -> Ordering
rtypeCompare ep rb a1 a2 = selCompare rb (from ep a1) (from ep a2)

instance Generic Compare where
  rconstant      = Compare rconstantCompare
  rsum     ra rb = Compare (rsumCompare ra rb)
  rprod    ra rb = Compare (rprodCompare ra rb)
  rcon  cd ra    = Compare (rconCompare cd ra)
  rtype ep ra    = Compare (rtypeCompare ep ra)

-----------------------------------------------------------------------------
-- Exported functions
-----------------------------------------------------------------------------

-- Set the fixity and precedence the same as the infix operators according to
-- the Haskell Report: http://www.haskell.org/onlinereport/decls.html
infix 4 `compare`, `lt`, `lteq`, `eq`, `neq`, `gt`, `gteq`, `min`, `max`

-- | Compare two values and return an 'Ordering' (i.e. @LT@, @GT@, or @EQ@).
-- This is implemented exactly as if the datatype was @deriving Ord@.
compare :: (Rep Compare a) => a -> a -> Ordering
compare = selCompare rep

-- | Less than. Returns @x < y@.
lt :: (Rep Compare a) => a -> a -> Bool
lt x y =
  case compare x y of
    LT -> True
    _  -> False

-- | Less than or equal to. Returns @x <= y@.
lteq :: (Rep Compare a) => a -> a -> Bool
lteq x y =
  case compare x y of
    GT -> False
    _  -> True

-- | Equal to. Returns @x == y@.
eq :: (Rep Compare a) => a -> a -> Bool
eq x y =
  case compare x y of
    EQ -> True
    _  -> False

-- | Not equal to. Returns @x /= y@.
neq :: (Rep Compare a) => a -> a -> Bool
neq x y =
  case compare x y of
    EQ -> False
    _  -> True

-- | Greater than. Returns @x > y@.
gt :: (Rep Compare a) => a -> a -> Bool
gt x y =
  case compare x y of
    GT -> True
    _  -> False

-- | Greater than or equal to. Returns @x >= y@.
gteq :: (Rep Compare a) => a -> a -> Bool
gteq x y =
  case compare x y of
    LT -> False
    _  -> True

-- | The minimum of two values.
min :: (Rep Compare a) => a -> a -> a
min x y = if x `lteq` y then x else y

-- | The maximum of two values.
max :: (Rep Compare a) => a -> a -> a
max x y = if x `gteq` y then x else y