{-# LANGUAGE NoImplicitPrelude #-}
-- |
-- Module:       $HEADER$
-- Description:  Get number of digits of a number from a Int-family of numbers
--               in decimal or hexadecimal representation.
-- Copyright:    (c) 2015-2016, Peter Trško
-- License:      BSD3
--
-- Stability:    experimental
-- Portability:  NoImplicitPrelude
--
-- Get number of digits of a number from a 'Int'-family of numbers in decimal
-- or hexadecimal representation.
module Data.NumberLength.Int
    (
    -- * Decimal (base 10)
      lengthInt
    , lengthInt8
    , lengthInt16
    , lengthInt32
    , lengthInt64

    -- * Hexadecimal (base 16)
    , lengthIntHex
    , lengthInt8hex
    , lengthInt16hex
    , lengthInt32hex
    , lengthInt64hex
    )
  where

import Prelude
    ( Bounded(minBound)
    , Integral(quot)
    , Num((+), negate)
    , fromIntegral
    )

import Data.Bool ((&&), otherwise)
import Data.Eq (Eq((==)))
import Data.Int (Int, Int16, Int32, Int64, Int8)
import Data.Ord (Ord((<), (>), (>=)))

import Data.NumberLength.Internal (either32or64)


-- {{{ Decimal ----------------------------------------------------------------

-- | Number of digits in a @number :: 'Int8'@ in base 10.
lengthInt8 :: Int8 -> Int
lengthInt8 n
  | n < 0         = go (negate (fromIntegral n))
  | otherwise     = go (fromIntegral n)
  where
    go :: Int -> Int
    go m
      -- Maximum is 127 for positive and 128 for negative integer.
      | m < 10     = 1
      | m < 100    = 2
      | otherwise  = 3
{-# INLINE lengthInt8 #-}

-- | Number of digits in a @number :: 'Int16'@ in base 10.
lengthInt16 :: Int16 -> Int
lengthInt16 n
  | n < 0     = go (negate (fromIntegral n))
  | otherwise = go (fromIntegral n)
  where
    -- Maximum is 32767 for positive and 32768 for negative integer.
    go :: Int -> Int
    go m
      | m < 10     = 1
      | m < 100    = 2
      | m < 1000   = 3
      | m < 10000  = 4
      | otherwise  = 5
{-# INLINE lengthInt16 #-}

-- | Number of digits in a @number :: 'Int32'@ in base 10.
lengthInt32 :: Int32 -> Int
lengthInt32 n
  | n == minBound = 10  -- "negate minBound" is out of range of Int32.
  | n < 0         = go (negate (fromIntegral n))
  | otherwise     = go (fromIntegral n)
  where
    -- Maximum is 2147483647 for positive and 2147483648 for negative integer.
    go :: Int -> Int
    go m
      | m < 10         = 1
      | m < 100        = 2
      | m < 1000       = 3
      | m < 10000      = 4
      | m >= 100000000 = 8 + go (m `quot` 100000000)
      | otherwise      = 4 + go (m `quot` 10000)
        -- m >= 10000
{-# INLINE lengthInt32 #-}

-- | Number of digits in a @number :: 'Int64'@ in base 10.
lengthInt64 :: Int64 -> Int
lengthInt64 n
  | n == minBound = 19 -- "negate minBound" is out of range of Int64
  | n < 0         = go (negate n)
  | otherwise     = go n
  where
    -- Maximum is 9223372036854775807 for positive and 9223372036854775808
    -- for negative integer.
    go m
      | m < 10                 = 1
      | m < 100                = 2
      | m < 1000               = 3
      | m < 10000              = 4
      | m >= 10000000000000000 = 16 + go (m `quot` 10000000000000000)
      | m >= 100000000         = 8  + go (m `quot` 100000000)
      | otherwise              = 4  + go (m `quot` 10000)
        -- m >= 10000
{-# INLINE lengthInt64 #-}

-- | Number of digits in a @number :: 'Int'@ in base 10.
lengthInt :: Int -> Int
lengthInt n = l32 `either32or64` l64
  where
    -- Same code as lengthInt64:
    l64
      | n == minBound = 19 -- "negate minBound" is out of range of Int64
      | n < 0         = go (negate n)
      | otherwise     = go n
      where
        -- Maximum is 9223372036854775807 for positive and 9223372036854775808
        -- for negative integer.
        go m
          | m < 10                 = 1
          | m < 100                = 2
          | m < 1000               = 3
          | m < 10000              = 4
          | m >= 10000000000000000 = 16 + go (m `quot` 10000000000000000)
          | m >= 100000000         = 8  + go (m `quot` 100000000)
          | otherwise              = 4  + go (m `quot` 10000)
            -- m >= 10000

    -- Same code as lengthInt32:
    l32
      | n == minBound = 10  -- "negate minBound" is out of range of Int32.
      | n < 0         = go (negate n)
      | otherwise     = go n
      where
        -- Maximum is 2147483647 for positive and 2147483648 for negative integer.
        go m
          | m < 10         = 1
          | m < 100        = 2
          | m < 1000       = 3
          | m < 10000      = 4
          | m >= 100000000 = 8 + go (m `quot` 100000000)
          | otherwise      = 4 + go (m `quot` 10000)
            -- m >= 10000
{-# INLINE lengthInt #-}

-- }}} Decimal ----------------------------------------------------------------

-- {{{ Hexadecimal ------------------------------------------------------------

-- | Number of digits in a @number :: 'Int8'@ in base 16.
lengthInt8hex :: Int8 -> Int
lengthInt8hex n
  | n < 16 && n > -16 = 1
  | otherwise         = 2
  -- Maximum is 127 = 0x7f for positive and 128 = 0x80 for negative integer.
{-# INLINE lengthInt8hex #-}

-- | Number of digits in a @number :: 'Int16'@ in base 16.
lengthInt16hex :: Int16 -> Int
lengthInt16hex n
  | n < 0     = go (negate (fromIntegral n))
  | otherwise = go (fromIntegral n)
  where
    -- Maximum is 32767 = 0x7fff for positive and 32768 = 0x8000 for negative
    -- integer.
    go :: Int -> Int
    go m
      | m < 0x10   = 1
      | m < 0x100  = 2
      | m < 0x1000 = 3
      | otherwise  = 4
{-# INLINE lengthInt16hex #-}

-- | Number of digits in a @number :: 'Int32'@ in base 16.
lengthInt32hex :: Int32 -> Int
lengthInt32hex n
  | n == minBound = 8   -- "negate minBound" is out of range of Int32.
  | n < 0         = go (negate (fromIntegral n))
  | otherwise     = go (fromIntegral n)
  where
    -- Maximum is 2147483647 = 0x7fffffff for positive and
    -- 2147483648 = 0x80000000 for negative integer.
    go :: Int -> Int
    go m
      | m < 0x10    = 1
      | m < 0x100   = 2
      | m < 0x1000  = 3
      | m < 0x10000 = 4
      | otherwise   = 4 + go (m `quot` 0x10000)
        -- m >= 0x10000
{-# INLINE lengthInt32hex #-}

-- | Number of digits in a @number :: 'Int64'@ in base 16.
lengthInt64hex :: Int64 -> Int
lengthInt64hex n
  | n == minBound = 16 -- "negate minBound" is out of range of Int64
  | n < 0         = go (negate n)
  | otherwise     = go n
  where
    -- Maximum is 9223372036854775807 = 0x7fffffffffffffff for positive and
    -- 9223372036854775808 = 0x8000000000000000 for negative integer.
    go m
      | m <  0x10        = 1
      | m <  0x100       = 2
      | m <  0x1000      = 3
      | m <  0x10000     = 4
      | m >= 0x100000000 = 8  + go (m `quot` 0x100000000)
      | otherwise        = 4  + go (m `quot` 0x10000)
        -- m >= 0x10000
{-# INLINE lengthInt64hex #-}

-- | Number of digits in a @number :: 'Int'@ in base 16.
lengthIntHex :: Int -> Int
lengthIntHex n = l32hex `either32or64` l64hex
  where
    -- Same code as lengthInt64hex:
    l64hex
      | n == minBound = 16 -- "negate minBound" is out of range of Int64
      | n < 0         = go (negate n)
      | otherwise     = go n
      where
        -- Maximum is 9223372036854775807 = 0x7fffffffffffffff for positive and
        -- 9223372036854775808 = 0x8000000000000000 for negative integer.
        go m
          | m <  0x10        = 1
          | m <  0x100       = 2
          | m <  0x1000      = 3
          | m <  0x10000     = 4
          | m >= 0x100000000 = 8  + go (m `quot` 0x100000000)
          | otherwise        = 4  + go (m `quot` 0x10000)
            -- m >= 0x10000

    -- Same code as lengthInt32hex:
    l32hex
      | n == minBound = 8   -- "negate minBound" is out of range of Int32.
      | n < 0         = go (negate n)
      | otherwise     = go n
      where
        -- Maximum is 2147483647 = 0x7fffffff for positive and
        -- 2147483648 = 0x80000000 for negative integer.
        go :: Int -> Int
        go m
          | m < 0x10    = 1
          | m < 0x100   = 2
          | m < 0x1000  = 3
          | m < 0x10000 = 4
          | otherwise   = 4 + go (m `quot` 0x10000)
            -- m >= 0x10000
{-# INLINE lengthIntHex #-}

-- }}} Hexadecimal ------------------------------------------------------------