{-
Copyright 2010-2012 Cognimeta Inc.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
compliance with the License. You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is
distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
or implied. See the License for the specific language governing permissions and limitations under the License.
-}

{-# LANGUAGE MultiParamTypeClasses, ScopedTypeVariables, TypeOperators #-}

module Cgm.Data.Super (
    Super(..),
    up,
    superSInc,
    narrowIntegral,
    module Cgm.Data.Maybe,
    module Cgm.Control.InFunctor
) where

import Data.Int
import Data.Word
import Data.Bits
import Control.Applicative
import Cgm.Data.Maybe
import Cgm.Control.InFunctor

-- Laws: properties of and relations on between values in a must still hold in b. For example 0 must map to 0, and Ord must be respected.
-- However some properites cannot hold, such as (== maxBound). So this law needs some elaboration.
class Super a b where
    super ::  InjectionM' a b

superSInc :: Super a b => a :>> b
superSInc = uncheckedStrictlyIncreasing (apply super)

up :: Super a b => a -> b
up = apply super

instance Super Word8 Word64 where super = integralInjectionMaxOnly
instance Super Word8 Word32 where super = integralInjectionMaxOnly
instance Super Word8 Word16 where super = integralInjectionMaxOnly
instance Super Word16 Word64 where super = integralInjectionMaxOnly
instance Super Word16 Word32 where super = integralInjectionMaxOnly
instance Super Word32 Word64 where super = integralInjectionMaxOnly

instance Super Word8 Word where super = integralInjectionMaxOnly
instance Super Word16 Word where super = integralInjectionMaxOnly
instance Super Word32 Word where super = integralInjectionMaxOnly
instance Super Word Word64 where super = integralInjectionMaxOnly

instance Super Word8 Integer where super = integralInjection 

integralInjectionMaxOnly :: forall a b. (Integral a, Integral b, Bounded a) => InjectionM' a b
integralInjectionMaxOnly = uncheckedInjectionM fromIntegral narrowIntegralMaxOnly

integralInjection :: forall a b. (Integral a, Integral b, Bounded a) => InjectionM' a b
integralInjection = uncheckedInjectionM fromIntegral narrowIntegral

{-# INLINE narrowIntegral #-}
narrowIntegral :: forall a b. (Integral a, Integral b, Bounded b) => a -> Maybe b
narrowIntegral = (fromIntegral <$>) . predJust (\a -> fromIntegral (minBound :: b) <= a && a <= fromIntegral (maxBound :: b))

narrowIntegralMaxOnly :: forall a b. (Integral a, Integral b, Bounded b) => a -> Maybe b
narrowIntegralMaxOnly = (fromIntegral <$>) . predJust (<= fromIntegral (maxBound :: b))