{-# LANGUAGE CPP                  #-}
{-# LANGUAGE DataKinds            #-}
{-# LANGUAGE DeriveDataTypeable   #-}
{-# LANGUAGE DeriveGeneric        #-}
{-# LANGUAGE FlexibleContexts     #-}
{-# LANGUAGE PolyKinds            #-}
{-# LANGUAGE UndecidableInstances #-}
-----------------------------------------------------------------------------
-- |
-- Module      :  Numeric.ProductOrd
-- Copyright   :  (c) Artem Chirkin
-- License     :  BSD3
--
--
-- Compare product types -- partial order.
--
-----------------------------------------------------------------------------
module Numeric.ProductOrd (ProductOrder (..), PartialOrdering (..), fromOrdering) where


import Data.Data
import Data.Kind      (Type)
import Data.Monoid    as Mon (Monoid (..))
import Data.Semigroup as Sem (Semigroup (..), stimesIdempotentMonoid)
import GHC.Generics

import Numeric.TypedList

-- | Partial order for comparing product types --
--     [product order](https://en.wikipedia.org/wiki/Product_order).
class ProductOrder a where
    -- | Same as `compare`, but may return @Incomparable@.
    cmp :: a -> a -> PartialOrdering

-- | Similar to `Ordering`, but may be @Incomparable@.
data PartialOrdering = PLT | PEQ | PGT | Incomparable
  deriving ( Eq, Ord, Show, Read, Data, Typeable, Generic, Enum, Bounded )

-- | Extend `Ordering` with @Incomparable@ option.
fromOrdering :: Ordering -> PartialOrdering
fromOrdering LT = PLT
fromOrdering EQ = PEQ
fromOrdering GT = PGT
{-# INLINE fromOrdering #-}

instance Sem.Semigroup PartialOrdering where
    Incomparable <> _ = Incomparable
    _ <> Incomparable = Incomparable
    PLT <> PGT = Incomparable
    PGT <> PLT = Incomparable
    PLT <> _ = PLT
    PGT <> _ = PGT
    PEQ <> y = y

    stimes = stimesIdempotentMonoid

instance Mon.Monoid PartialOrdering where
    mempty = PEQ
#if !(MIN_VERSION_base(4,11,0))
    mappend = (<>)
#endif

instance All Ord (Map f xs)
      => ProductOrder (TypedList (f :: k -> Type) (xs :: [k])) where
    cmp U U                 = PEQ
    cmp (a :* as) (b :* bs) = fromOrdering (compare a b) <> cmp as bs


cmp' :: Ord a => a -> a -> PartialOrdering
cmp' a b = fromOrdering (compare a b)
{-# INLINE cmp' #-}

instance (Ord a1, Ord a2)
      => ProductOrder (a1, a2) where
    cmp (a1, a2)
        (b1, b2)
      =  cmp' a1 b1 <> cmp' a2 b2

instance (Ord a1, Ord a2, Ord a3)
      => ProductOrder (a1, a2, a3) where
    cmp (a1, a2, a3)
        (b1, b2, b3)
      =  cmp' a1 b1 <> cmp' a2 b2 <> cmp' a3 b3

instance (Ord a1, Ord a2, Ord a3, Ord a4)
      => ProductOrder (a1, a2, a3, a4) where
    cmp (a1, a2, a3, a4)
        (b1, b2, b3, b4)
      =  cmp' a1 b1 <> cmp' a2 b2 <> cmp' a3 b3
      <> cmp' a4 b4

instance (Ord a1, Ord a2, Ord a3, Ord a4, Ord a5)
      => ProductOrder (a1, a2, a3, a4, a5) where
    cmp (a1, a2, a3, a4, a5)
        (b1, b2, b3, b4, b5)
      =  cmp' a1 b1 <> cmp' a2 b2 <> cmp' a3 b3
      <> cmp' a4 b4 <> cmp' a5 b5

instance (Ord a1, Ord a2, Ord a3, Ord a4, Ord a5, Ord a6)
      => ProductOrder (a1, a2, a3, a4, a5, a6) where
    cmp (a1, a2, a3, a4, a5, a6)
        (b1, b2, b3, b4, b5, b6)
      =  cmp' a1 b1 <> cmp' a2 b2 <> cmp' a3 b3
      <> cmp' a4 b4 <> cmp' a5 b5 <> cmp' a6 b6

instance (Ord a1, Ord a2, Ord a3, Ord a4, Ord a5, Ord a6, Ord a7)
      => ProductOrder (a1, a2, a3, a4, a5, a6, a7) where
    cmp (a1, a2, a3, a4, a5, a6, a7)
        (b1, b2, b3, b4, b5, b6, b7)
      =  cmp' a1 b1 <> cmp' a2 b2 <> cmp' a3 b3
      <> cmp' a4 b4 <> cmp' a5 b5 <> cmp' a6 b6
      <> cmp' a7 b7

instance (Ord a1, Ord a2, Ord a3, Ord a4, Ord a5, Ord a6, Ord a7, Ord a8)
      => ProductOrder (a1, a2, a3, a4, a5, a6, a7, a8) where
    cmp (a1, a2, a3, a4, a5, a6, a7, a8)
        (b1, b2, b3, b4, b5, b6, b7, b8)
      =  cmp' a1 b1 <> cmp' a2 b2 <> cmp' a3 b3
      <> cmp' a4 b4 <> cmp' a5 b5 <> cmp' a6 b6
      <> cmp' a7 b7 <> cmp' a8 b8

instance (Ord a1, Ord a2, Ord a3, Ord a4, Ord a5, Ord a6, Ord a7, Ord a8, Ord a9)
      => ProductOrder (a1, a2, a3, a4, a5, a6, a7, a8, a9) where
    cmp (a1, a2, a3, a4, a5, a6, a7, a8, a9)
        (b1, b2, b3, b4, b5, b6, b7, b8, b9)
      =  cmp' a1 b1 <> cmp' a2 b2 <> cmp' a3 b3
      <> cmp' a4 b4 <> cmp' a5 b5 <> cmp' a6 b6
      <> cmp' a7 b7 <> cmp' a8 b8 <> cmp' a9 b9