{-# LANGUAGE DeriveDataTypeable  #-}
{-# LANGUAGE DeriveFoldable      #-}
{-# LANGUAGE DeriveFunctor       #-}
{-# LANGUAGE DeriveGeneric       #-}
{-# LANGUAGE DeriveTraversable   #-}
{-# LANGUAGE FlexibleContexts    #-}
{-# LANGUAGE Safe                #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeOperators       #-}
----------------------------------------------------------------------------
-- |
-- Module      :  Algebra.Lattice.Ordered
-- Copyright   :  (C) 2010-2015 Maximilian Bolingbroke, 2015-2019 Oleg Grenrus
-- License     :  BSD-3-Clause (see the file LICENSE)
--
-- Maintainer  :  Oleg Grenrus <oleg.grenrus@iki.fi>
--
----------------------------------------------------------------------------
module Algebra.Lattice.Ordered (
    Ordered(..)
  ) where

import Algebra.Heyting
import Algebra.Lattice
import Algebra.PartialOrd

import Control.DeepSeq       (NFData (..))
import Control.Monad         (ap)
import Data.Data             (Data, Typeable)
import Data.Hashable         (Hashable (..))
import Data.Universe.Class   (Finite (..), Universe (..))
import Data.Universe.Helpers (Natural, Tagged, retag)
import GHC.Generics          (Generic, Generic1)

import qualified Test.QuickCheck as QC

--
-- Ordered
--

-- | A total order gives rise to a lattice. Join is
-- 'max', meet is 'min'.
newtype Ordered a = Ordered { getOrdered :: a }
  deriving ( Eq, Ord, Show, Read, Data, Typeable, Generic, Functor, Foldable, Traversable
           , Generic1
           )

instance Applicative Ordered where
  pure = return
  (<*>) = ap

instance Monad Ordered where
  return           = Ordered
  Ordered x >>= f  = f x

instance NFData a => NFData (Ordered a) where
  rnf (Ordered a) = rnf a

instance Hashable a => Hashable (Ordered a)

instance Ord a => Lattice (Ordered a) where
  Ordered x \/ Ordered y = Ordered (max x y)
  Ordered x /\ Ordered y = Ordered (min x y)

instance (Ord a, Bounded a) => BoundedJoinSemiLattice (Ordered a) where
  bottom = Ordered minBound

instance (Ord a, Bounded a) => BoundedMeetSemiLattice (Ordered a) where
  top = Ordered maxBound

-- | This is interesting logic, as it satisfies both de Morgan laws;
-- but isn't Boolean: i.e. law of exluded middle doesn't hold.
--
-- Negation "smashes" value into 'minBound' or 'maxBound'.
instance (Ord a, Bounded a) => Heyting (Ordered a) where
    x ==> y | x > y     = y
            | otherwise = top

instance Ord a => PartialOrd (Ordered a) where
    leq = (<=)
    comparable _ _ = True

instance Universe a => Universe (Ordered a) where
    universe = map Ordered universe
instance Finite a => Finite (Ordered a) where
    universeF = map Ordered universeF
    cardinality = retag (cardinality :: Tagged a Natural)

instance QC.Arbitrary a => QC.Arbitrary (Ordered a) where
    arbitrary = Ordered <$> QC.arbitrary
    shrink    = QC.shrinkMap Ordered getOrdered

instance QC.CoArbitrary a => QC.CoArbitrary (Ordered a) where
    coarbitrary = QC.coarbitrary . getOrdered

instance QC.Function a => QC.Function (Ordered a) where
    function = QC.functionMap getOrdered Ordered
