{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UnboxedTuples #-}

module Data.Map.Interval.DBTSLL
  ( Map(..) -- data constructor exposed as a hack
  , pure
  , singleton
  , lookup
  , fromList
  , unionWith
    -- * Mapping
  , map
  , mapBijection
    -- * Traversals
  , traverseBijectionP
  , traverseBijection
    -- * Folds
  , foldl'
  , foldlM'
  , foldMap
  , foldrWithKey
  , foldlWithKeyM'
  , traverse_
    -- * Properties
  , size
    -- * Conversion
  , elems
  , toList
  ) where

import Prelude hiding (lookup,map,pure,foldMap)

import Data.Semigroup (Semigroup)
import Data.Primitive.Array (Array)
import Control.Monad.Primitive (PrimMonad)
import qualified Data.Semigroup as SG
import qualified Data.Foldable as F
import qualified Data.Map.Interval.DBTS.Internal as I
import qualified GHC.Exts as E

-- | A total interval map from keys @k@ to values @v@. The key type must be discrete
--   and bounded. This map is strict in the values.
newtype Map k v = Map (I.Map Array Array k v)

instance (Eq k, Eq v) => Eq (Map k v) where
  Map x == Map y = I.equals x y

-- instance (Ord k, Ord v) => Ord (Map k v) where
--   compare (Map x) (Map y) = I.compare x y

instance (Ord k, Semigroup v, Eq v) => Semigroup (Map k v) where
  Map x <> Map y = Map (I.union x y)

-- The redundant constraint is needed for GHC < 8.4
instance (Ord k, Bounded k, Semigroup v, Monoid v, Eq v) => Monoid (Map k v) where
  mappend = (SG.<>)
  mempty = Map I.empty
  mconcat = Map . I.concat . E.coerce

instance (Bounded k, Enum k, Show k, Show v) => Show (Map k v) where
  showsPrec p (Map m) = I.showsPrec p m

instance (Bounded k, Enum k, Ord k, Eq v, Monoid v) => E.IsList (Map k v) where
  type Item (Map k v) = (k,k,v)
  fromList xs = Map (I.fromList mempty xs)
  toList (Map m) = I.toList m

instance Foldable (Map k) where
  foldr f b (Map m) = F.foldr f b (I.elems m)
  foldl' f b (Map m) = F.foldl' f b (I.elems m)
  toList (Map m) = F.toList (I.elems m)
  length (Map m) = F.length (I.elems m)

pure :: Bounded k => v -> Map k v
pure = Map . I.pure

singleton :: (Bounded k, Enum k, Ord k, Eq v)
  => v -- ^ value outside of the interval
  -> k -- ^ lower bound
  -> k -- ^ upper bound
  -> v -- ^ value inside the interval
  -> Map k v
singleton def lo hi v = Map (I.singleton def lo hi v)

lookup :: Ord k => k -> Map k v -> v
lookup k (Map m) = I.lookup k m

-- | Create an interval map from a list of range-value triples. The first
--   argument is a default value used everywhere outside of the given
--   ranges. In the case of overlapping ranges, the leftmost value is
--   used.
fromList :: (Bounded k, Ord k, Enum k, Eq v)
  => v -- ^ value outside of the ranges
  -> [(k,k,v)] -- ^ low-high inclusive ranges with their corresponding values
  -> Map k v
fromList def xs = Map (I.fromList def xs)

-- | This only provides a correct result when the effectful mapping
--   is a bijection.
traverseBijectionP :: PrimMonad m
  => (v -> m w) -> Map k v -> m (Map k w)
traverseBijectionP f (Map m) = fmap Map (I.traverseP f m)

-- | This only provides a correct result when the effectful mapping
--   is a bijection.
traverseBijection :: Applicative m
  => (v -> m w) -> Map k v -> m (Map k w)
traverseBijection f (Map m) = fmap Map (I.traverse f m)

traverse_ :: Applicative m => (v -> m w) -> Map k v -> m ()
traverse_ f (Map m) = I.traverse_ f m

mapBijection :: (v -> w) -> Map k v -> Map k w
mapBijection f (Map m) = Map (I.mapBijection f m)

map :: Eq w => (v -> w) -> Map k v -> Map k w
map f (Map m) = Map (I.map f m)

foldl' ::
     (b -> v -> b)
  -> b
  -> Map k v
  -> b
foldl' f b0 (Map m) = I.foldl' f b0 m

foldlM' :: Monad m
  => (b -> v -> m b)
  -> b
  -> Map k v
  -> m b
foldlM' f b0 (Map m) = I.foldlM' f b0 m

foldMap :: (Monoid m)
  => (v -> m)
  -> Map k v
  -> m
foldMap f (Map m) = I.foldMap f m

unionWith :: (Ord k, Eq c)
  => (a -> b -> c)
  -> Map k a
  -> Map k b
  -> Map k c
unionWith f (Map a) (Map b) = Map (I.unionWith f a b)

foldrWithKey :: (Bounded k, Enum k)
  => (k -> k -> v -> b -> b)
  -> b
  -> Map k v
  -> b
foldrWithKey f z (Map m) = I.foldrWithKey f z m

foldlWithKeyM' :: (Bounded k, Enum k, Monad m)
  => (b -> k -> k -> v -> m b)
  -> b
  -> Map k v
  -> m b
foldlWithKeyM' f z (Map m) = I.foldlWithKeyM' f z m

-- | The number of values in the interval map. Also the number of
--   contiguous key ranges in the map.
size :: Map k v -> Int
size (Map m) = I.size m

elems :: Map k v -> Array v
elems (Map m) = I.elems m

toList :: (Bounded k, Enum k) => Map k v -> [(k,k,v)]
toList (Map m) = I.toList m