{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TupleSections #-}
module Data.Interval where

import           Control.DeepSeq (NFData(..))
import           Data.Bool
import           Data.Data (Data(..))
import           Data.Eq (Eq(..))
import           Data.Foldable (concat)
import           Data.Function (($), flip)
import           Data.Functor (Functor(..))
import qualified Data.Functor as Functor
import           Data.Maybe (Maybe(..))
import           Data.Ord (Ord(..), Ordering(..))
import           Data.Tuple
import           Data.Typeable (Typeable)
import           Prelude (Bounded(..), seq)
import           Text.Show (Show(..))

-- * Type 'Limit'

data Limit x
 =   Limit
 { adherence :: Adherence
 , limit     :: x }
 deriving (Eq, Data, Show, Typeable)

instance Functor Limit where
	fmap f (Limit a x) = Limit a (f x)
instance NFData x => NFData (Limit x) where
	rnf (Limit _a l) = rnf l

data Adherence = Out | In
 deriving (Eq, Data, Show, Typeable)

-- | Return given 'Limit' with its 'adherence' set to the opposite one.
flip_limit :: Limit x -> Limit x
flip_limit (Limit a x) = Limit (case a of { In -> Out; Out -> In }) x

-- ** Comparing 'Limit's

-- | Compare two 'low' 'Limit's.
newtype LL x = LL { unLL :: x }
 deriving (Eq)
instance Ord x => Ord (LL (Limit x)) where
	compare (LL x) (LL y) =
		case compare (limit x) (limit y) of
		 EQ ->
			case (adherence x, adherence y) of
			 (Out, In ) -> GT
			 (In , Out) -> LT
			 _          -> EQ
		 o -> o

-- | Compare two 'high' 'Limit's.
newtype HH x = HH { unHH :: x }
 deriving (Eq)
instance Ord x => Ord (HH (Limit x)) where
	compare (HH x) (HH y) =
		case compare (limit x) (limit y) of
		 EQ ->
			case (adherence x, adherence y) of
			 (Out, In ) -> LT
			 (In , Out) -> GT
			 _          -> EQ
		 o -> o

-- * Type 'Interval'

newtype  Ord x
 => Interval x
 =  Interval (Limit x, Limit x)
 deriving (Eq, Show, Data, Typeable)
instance (NFData x, Ord x) => NFData (Interval x) where
	rnf (Interval (x, y)) = rnf x `seq` rnf y

low :: Ord x => Interval x -> Limit x
low (Interval t)  = fst t

high :: Ord x => Interval x -> Limit x
high (Interval t) = snd t

-- | Return 'Interval' with given 'low' then 'high' 'Limit's,
--   if they form a valid 'Interval'.
interval :: Ord x => Limit x -> Limit x -> Maybe (Interval x)
interval x y =
	case compare_without_adherence x y of
	 LT -> Just $ Interval (x, y)
	 EQ ->
		case (adherence x, adherence y) of
		 (In, In) -> Just $ Interval (x, y)
		 _        -> Nothing
	 GT -> Nothing

-- | Like 'Functor.fmap', but may return 'Nothing', if mapped 'Interval' is not valid.
fmap :: (Ord x, Ord y) => (x -> y) -> Interval x -> Maybe (Interval y)
fmap f (Interval (il, ih)) = interval (Functor.fmap f il) (Functor.fmap f ih)

-- | Like 'Functor.fmap', but only safe if given map preserves 'Ordering'.
fmap_unsafe :: (Ord x, Ord y) => (x -> y) -> Interval x -> Interval y
fmap_unsafe f (Interval (il, ih)) = Interval (Functor.fmap f il, Functor.fmap f ih)

{-
-- | Like 'Functor.fmap', but on 'Limit's,
--   and may return 'Nothing', if mapped 'Interval' is not valid.
fmap_limits :: (Ord x, Ord y) => (Limit x -> Limit y) -> Interval x -> Maybe (Interval y)
fmap_limits f (Interval (il, ih)) = interval (f il) (f ih)

-- | Like 'Functor.fmap', but on 'Limit's
--   and only safe if given map preserves 'Ordering'.
fmap_limits_unsafe :: (Ord x, Ord y) => (Limit x -> Limit y) -> Interval x -> Interval y
fmap_limits_unsafe f (Interval (il, ih)) = Interval (f il, f ih)
-}

-- | Lexicographical order, handling 'Adherence' correctly.
instance Ord x => Ord (Interval x) where
	compare (Interval (il, ih)) (Interval (jl, jh)) =
		case   compare (LL il) (LL jl) of
		 EQ -> compare (HH ih) (HH jh)
		 o  -> o

-- | Return 'limit's of given 'Interval' as a tuple.
limits :: Ord x => Interval x -> (Limit x, Limit x)
limits (Interval t) = t

-- | Return an 'Interval' spanning over a single 'limit'.
point :: Ord x => x -> Interval x
point x = Interval (Limit In x, Limit In x)

-- | Return given 'Interval' with 'flip_limit' applied to its 'limit's.
flip_limits :: Ord x => Interval x -> Interval x
flip_limits (Interval (l, h)) = Interval (flip_limit l, flip_limit h)

-- | Return 'Ordering' comparing given 'Interval's according to their 'limit's.
compare_without_adherence :: Ord x => Limit x -> Limit x -> Ordering
compare_without_adherence (Limit _ x) (Limit _ y) = compare x y

-- | Return:
--
-- * 'LT': if given value is lower  than all values in given 'Interval'.
-- * 'EQ': if given value is into the given 'Interval'.
-- * 'GT': if given value is higher than all values in given 'Interval'.
locate :: Ord x => x -> Interval x -> Ordering
locate x (Interval (l, h)) =
	case compare x (limit l) of
	 LT -> LT
	 EQ | adherence l == In -> EQ
	 EQ -> LT
	 GT ->
		case compare x (limit h) of
		 LT -> EQ
		 EQ | adherence h == In -> EQ
		 EQ -> GT
		 GT -> GT

-- | Return 'True' iif. given value is into the given 'Interval'.
within :: Ord x => x -> Interval x -> Bool
within x i = locate x i == EQ

-- | Return 'True' iif. every value of the first 'Interval' is into the second 'Interval'.
into :: Ord x => Interval x -> Interval x -> Bool
into i j =
	case position i j of
	 (Prefix  , LT) -> True
	 (Suffixed, GT) -> True
	 (Include , GT) -> True
	 (Equal   , _)  -> True
	 _              -> False

-- | Return 'True' iif. every value of the second 'Interval' is into the first 'Interval'.
onto :: Ord x => Interval x -> Interval x -> Bool
onto = flip into

infix 5 <=..<=
(<=..<=) :: Ord x => x -> x -> Maybe (Interval x)
(<=..<=) x y =
	case compare x y of
	 LT -> Just $ Interval (Limit In x, Limit In y)
	 EQ -> Just $ Interval (Limit In x, Limit In y)
	 GT -> Nothing

infix 5 <..<=
(<..<=) :: Ord x => x -> x -> Maybe (Interval x)
(<..<=) x y =
	case compare x y of
	 LT -> Just $ Interval (Limit Out x, Limit In y)
	 EQ -> Nothing
	 GT -> Nothing

infix 5 <=..<
(<=..<) :: Ord x => x -> x -> Maybe (Interval x)
(<=..<) x y =
	case compare x y of
	 LT -> Just $ Interval (Limit In x, Limit Out y)
	 EQ -> Nothing
	 GT -> Nothing

infix 5 <..<
(<..<) :: Ord x => x -> x -> Maybe (Interval x)
(<..<) x y =
	case compare x y of
	 LT -> Just $ Interval (Limit Out x, Limit Out y)
	 EQ -> Nothing
	 GT -> Nothing

-- * Type 'Position'

data Position
 = Away     -- ^ @-_|@ ('LT') or @|_-@ ('GT')
 | Adjacent -- ^ @-|@  ('LT') or @|-@  ('GT')
 | Overlap  -- ^ @-+|@ ('LT') or @|+-@ ('GT')
 | Prefix   -- ^ @+|@  ('LT') or @+-@  ('GT')
 | Suffixed -- ^ @-+@  ('LT') or @|+@  ('GT')
 | Include  -- ^ @-+-@ ('LT') or @|+|@ ('GT')
 | Equal    -- ^ @+@ ('EQ')
 deriving (Eq, Show)

position :: Ord x => Interval x -> Interval x -> (Position, Ordering)
position (Interval (il, ih)) (Interval (jl, jh)) =
	case compare (LL il) (LL jl) of
	 LT -> (, LT) $
		case compare_without_adherence ih jl of
		 LT -> Away -- PATTERN: -_|
		 EQ ->
			case (adherence ih, adherence jl) of
			 (In , In)  -> Overlap  -- PATTERN: -+|
			 (Out, Out) -> Away     -- PATTERN: -_|
			 _          -> Adjacent -- PATTERN: -|
		 GT ->
			case compare (HH ih) (HH jh) of
			 LT -> Overlap  -- PATTERN: -+|
			 EQ -> Suffixed -- PATTERN: -+
			 GT -> Include  -- PATTERN: -+-
	 EQ ->
		case compare (HH ih) (HH jh) of
		 LT -> (Prefix, LT) -- PATTERN: +|
		 EQ -> (Equal , EQ) -- PATTERN: +
		 GT -> (Prefix, GT) -- PATTERN: +-
	 GT -> (, GT) $
		case compare_without_adherence il jh of
		 LT ->
			case compare (HH ih) (HH jh) of
			 LT -> Include  -- PATTERN: |+|
			 EQ -> Suffixed -- PATTERN: |+
			 GT -> Overlap  -- PATTERN: |+-
		 EQ ->
			case (adherence il, adherence jh) of
			 (In , In)  -> Overlap  -- PATTERN: |+-
			 (Out, Out) -> Away     -- PATTERN: |_-
			 _          -> Adjacent -- PATTERN: |-
		 GT -> Away -- PATTERN: |_-

infix 4 ..<<..
-- | Return 'True' iif. 'Position' of given 'Interval's is ('Away', 'LT').
(..<<..) :: Ord x => Interval x -> Interval x -> Bool
(..<<..) i j = case position i j of
	 (Away, LT) -> True
	 _          -> False

infix 4 ..>>..
-- | Return 'True' iif. 'Position' of given 'Interval's is ('Away', 'GT').
(..>>..) :: Ord x => Interval x -> Interval x -> Bool
(..>>..) i j = case position i j of
	 (Away, GT) -> True
	 _          -> False

infix 4 ..<..
-- | Return 'True' iif. 'Position' of given 'Interval's is ('Away', 'LT') or ('Adjacent', 'LT').
(..<..) :: Ord x => Interval x -> Interval x -> Bool
(..<..) i j = case position i j of
	 (Away    , LT) -> True
	 (Adjacent, LT) -> True
	 _              -> False
infix 4 ..>..
-- | Return 'True' iif. 'Position' of given 'Interval's is ('Away', 'GT') or ('Adjacent', 'GT').
(..>..) :: Ord x => Interval x -> Interval x -> Bool
(..>..) i j = case position i j of
	 (Away    , GT) -> True
	 (Adjacent, GT) -> True
	 _              -> False

infix 4 ..<=..
-- | Return 'True' iif. 'Position' of given 'Interval's is ('Away', 'LT'), ('Adjacent', 'LT'), ('Overlap', 'LT'), ('Prefix', 'LT'), ('Suffixed', 'LT'), ('Include', 'GT'), or ('Equal', _).
(..<=..) :: Ord x => Interval x -> Interval x -> Bool
(..<=..) i j = case position i j of
	 (Away    , LT) -> True
	 (Adjacent, LT) -> True
	 (Overlap , LT) -> True
	 (Prefix  , LT) -> True
	 (Suffixed, LT) -> True
	 (Include , GT) -> True
	 (Equal   , _ ) -> True
	 _              -> False

infix 4 ..>=..
-- | Return 'True' iif. 'Position' of given 'Interval's is ('Away', 'GT'), ('Adjacent', 'GT'), ('Overlap', 'GT'), ('Prefix', 'GT'), ('Suffixed', 'GT'), ('Include', 'LT'), or ('Equal', _).
(..>=..) :: Ord x => Interval x -> Interval x -> Bool
(..>=..) i j = case position i j of
	 (Away    , GT) -> True
	 (Adjacent, GT) -> True
	 (Overlap , GT) -> True
	 (Prefix  , GT) -> True
	 (Suffixed, GT) -> True
	 (Include , LT) -> True
	 (Equal   , _ ) -> True
	 _              -> False

-- * Merge

union :: Ord x => Interval x -> Interval x -> Maybe (Interval x)
union i j =
	case position i j of
	 (Away, _) -> -- PATTERN: -_| or |_-
		Nothing
	 (Adjacent, o) ->
		case o of
		 LT -> Just $ Interval (low i, high j) -- PATTERN: -|
		 EQ -> Nothing
		 GT -> Just $ Interval (low j, high i) -- PATTERN: |-
	 (Overlap, o) ->
		case o of
		 LT -> Just $ Interval (low i, high j) -- PATTERN: -+|
		 EQ -> Nothing
		 GT -> Just $ Interval (low j, high i) -- PATTERN: |+-
	 (Prefix, o) ->
		case o of
		 LT -> Just j -- PATTERN: +|
		 EQ -> Nothing
		 GT -> Just i -- PATTERN: +-
	 (Suffixed, o) ->
		case o of
		 LT -> Just i -- PATTERN: -+
		 EQ -> Nothing
		 GT -> Just j -- PATTERN: |+
	 (Include, o) ->
		case o of
		 LT -> Just i -- PATTERN: -+-
		 EQ -> Nothing
		 GT -> Just j -- PATTERN: |+|
	 (Equal, _) ->  -- PATTERN: +
		Just i

intersection :: Ord x => Interval x -> Interval x -> Maybe (Interval x)
intersection i j =
	case position i j of
	 (Away, _) -> -- PATTERN: -_| or |_-
		Nothing
	 (Adjacent, _) -> -- PATTERN: -| or |-
		Nothing
	 (Overlap, o) ->
		case o of
		 LT -> Just $ Interval (low j, high i) -- PATTERN: -+|
		 EQ -> Nothing
		 GT -> Just $ Interval (low i, high j) -- PATTERN: |+-
	 (Prefix, o) ->
		case o of
		 LT -> Just i -- PATTERN: +|
		 EQ -> Nothing
		 GT -> Just j -- PATTERN: +-
	 (Suffixed, o) ->
		case o of
		 LT -> Just j -- PATTERN: -+
		 EQ -> Nothing
		 GT -> Just i -- PATTERN: |+
	 (Include, o) ->
		case o of
		 LT -> Just j -- PATTERN: -+-
		 EQ -> Nothing
		 GT -> Just i -- PATTERN: |+|
	 (Equal, _) ->  -- PATTERN: +
		Just i

span :: Ord x => Interval x -> Interval x -> Interval x
span i j =
	Interval
	 ( unLL (min (LL $ low  i) (LL $ low  j))
	 , unHH (max (HH $ high i) (HH $ high j))
	 )

-- * Type 'Unlimitable'

data Unlimitable x
 = Unlimited_low
 | Limited { limited :: x }
 | Unlimited_high
 deriving (Eq, Ord, Show)
instance Functor Unlimitable where
	fmap _f Unlimited_low  = Unlimited_low
	fmap _f Unlimited_high = Unlimited_high
	fmap f (Limited x)    = Limited (f x)
instance Bounded (Unlimitable x) where
	minBound = Unlimited_low
	maxBound = Unlimited_high
instance Bounded (Limit (Unlimitable x)) where
	minBound = Limit In Unlimited_low
	maxBound = Limit In Unlimited_high

unlimited :: Ord x => Interval (Unlimitable x)
unlimited = Interval ( Limit In Unlimited_low
                     , Limit In Unlimited_high )

unlimit :: Ord x => Interval x -> Interval (Unlimitable x)
unlimit = fmap_unsafe Limited

(<..) :: Ord x => x -> Interval (Unlimitable x)
(<..) x = Interval (Limit Out (Limited x), Limit In Unlimited_high)

(<=..) :: Ord x => x -> Interval (Unlimitable x)
(<=..) x = Interval (Limit In (Limited x), Limit In Unlimited_high)

(..<) :: Ord x => x -> Interval (Unlimitable x)
(..<) x = Interval (Limit In Unlimited_low, Limit Out (Limited x))

(..<=) :: Ord x => x -> Interval (Unlimitable x)
(..<=) x = Interval (Limit In Unlimited_low, Limit In (Limited x))

-- * Type 'Pretty'

newtype Pretty x = Pretty x
 deriving (Eq, Ord)
instance (Ord x, Show x) => Show (Pretty (Interval x)) where
	show (Pretty i) =
		concat
		 [ case adherence (low i) of
			 In  -> "["
			 Out -> "]"
		 , show (limit $ low i)
		 , ".."
		 , show (limit $ high i)
		 , case adherence (high i) of
			 In  -> "]"
			 Out -> "["
		 ]
instance Show x => Show (Pretty (Unlimitable x)) where
	show (Pretty x) =
		case x of
		 Unlimited_low  -> "-oo"
		 Limited l      -> show l
		 Unlimited_high -> "+oo"