{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}

{-|

Module      : Data.TypedDigits.Internal
Description : Data.TypedDigits internals
Portability : portable

This module is considered internal, and may change
without warning.
The Package Versioning Policy does not apply to it.

= Description

Digits, indexed by their base at the type level.

-}

module Data.TypedDigits.Internal (
    module Data.TypedDigits.Internal
  , KnownNat
  )
  where

import Data.Maybe
import Data.Proxy
import Data.Singletons.TypeLits (Nat, Sing(..), KnownNat )
import Data.Singletons          (singByProxy, SingI(..), fromSing )

-- | digits, indexed by their base at the type level
newtype Digit (base :: Nat) = Digit { getVal :: Int }

-- | @getBaseT p@: if @p@ is of some type @a base@,
-- then reflect the base into a value.
--
-- Useful for getting the base, just given a 'Proxy'; e.g.:
--
-- >>> :set -XDataKinds
-- >>> :set -XTypeApplications
-- >>> let p = Proxy :: Proxy 3
-- >>> getBaseT p
-- 3
getBaseT :: forall a base . (KnownNat base) => a base -> Int
getBaseT _ = fromIntegral $ fromSing s
  where
      s :: Sing base
      s = singByProxy (Proxy :: Proxy base)


-- | @digit x@: construct a digit. 0 <= @x@ < base must hold
-- true.
--
-- Nicer if TypeApplications is enabled, then you can say:
--
-- >>> :set -XDataKinds
-- >>> :set -XTypeApplications
-- >>> digit @9 8
-- Just 8 (base 9)
digit :: forall base . (KnownNat base) => Int -> Maybe (Digit base)
digit x =
    if x >= 0 && x < fromIntegral (getBaseT (Proxy :: Proxy base))
    then Just $ Digit x
    else Nothing

-- | Like 'digit', but throws an 'ErrorCall' if the value is
-- out of range.
-- 
-- sample usage:
-- 
-- >>> :set -XDataKinds
-- >>> :set -XTypeApplications
-- >>> digit' @9 7
-- 7 (base 9)
digit' :: KnownNat base => Int -> Digit base
digit' x = fromMaybe (error "bad value for base") (digit x)

-- get the Sing type fore the base, given some digit
digitSing :: KnownNat base => Digit base -> Sing base
digitSing Digit{} =
    sing

-- | @getBase d@: return the base of the digit @d@
getBase :: KnownNat base => Digit base -> Int
getBase d = fromIntegral $ fromSing (digitSing d)

deriving instance Eq (Digit base)
deriving instance Ord (Digit base)

instance (KnownNat base) => Show (Digit base) where
  show d = show (getVal d) ++ " (base " ++ show (getBase d) ++ ")"

instance KnownNat base => Enum (Digit base) where
  toEnum = digit'
  fromEnum = getVal

instance (KnownNat base) => Bounded (Digit base) where
  minBound = digit' 0

  maxBound = digit' $ n - 1
    where
      n = getBaseT (Proxy :: Proxy base)

-- | Making 'Digit' an instance of 'Num' does not seem
-- sensible, but it's nevertheless useful to be able to
-- add and subtract digits, so '<+>' and '<->' are provided
-- for this.
--
-- @a <+> b@: Add @a@ and @b@. Throws an 'ErrorCall' if the
-- result is out of range.
(<+>) :: KnownNat base => Digit base -> Digit base -> Digit base
a <+> b = digit' $ getVal a + getVal b

-- | @a <-> b@: Subtract @b@ from @a@. Throws an 'ErrorCall' if the
-- result is out of range.
(<->) :: KnownNat base => Digit base -> Digit base -> Digit base
a <-> b = digit' $ getVal a - getVal b