-- |
-- Module      : Text.Show.Number
-- Copyright   : (c) Justus Sagemüller 2016
-- License     : GPL v3
-- 
-- Maintainer  : (@) jsagemue $ uni-koeln.de
-- Stability   : experimental
-- Portability : portable
-- 
{-# LANGUAGE FlexibleInstances        #-}
{-# LANGUAGE TemplateHaskell          #-}

module Text.Show.Number where

import Lens.Micro
import Lens.Micro.TH
import Control.Arrow

type Δ n = n

data NumShowComponents n = NumShowComponents {
      _isNegative :: Bool
    , _scaleExponent :: Int
    , _scaledIntPart :: Int
    , _significantDecimals :: [Int]
    , _remainder :: n
    } deriving (Show)
makeLenses ''NumShowComponents

errorLtdShow :: RealFloat n => Δ n -> n -> ShowS
errorLtdShow δ = preShowNum δ 10 3 >>> asm
 where asm nsc = (if nsc^.isNegative then ('-':) else id)
                   . shows (nsc^.scaledIntPart)
                   . case nsc^.significantDecimals of
                       [] -> id
                       ds  -> ('.':) . flip (foldr shows) ds
                   . case nsc^.scaleExponent of
                       0 -> id
                       e -> ('e':) . shows e


preShowNum :: RealFloat n =>
           Δ n       -- ^ Uncertainty allowance
        -> Int       -- ^ Basis
        -> Int       -- ^ Minimum magnitude of exponent to warrant scientific notation
        -> n         -- ^ Number to show
        -> NumShowComponents n
preShowNum δ b emin 𝑥
  | δ<0        = preShowNum (-δ) b emin 𝑥
  | 𝑥<0        = preShowNum δ b emin (-𝑥) & isNegative .~ True
  | 𝑥>0        = NumShowComponents False exponent intPart sigDigits (rmd * 𝑏^^exponent)
  | otherwise  = NumShowComponents False 0 0 [] 𝑥
 where exponent = closeZero emin . max uncrtExp . floor $ logBase 𝑏 𝑥
       uncrtExp = floor $ logBase 𝑏 δ
       μ = 𝑏^^exponent
       fIntPart = floor $ 𝑥/μ
       (intPart, sigDigits) = case sigDigs of
           [hd] | uncrtExp >= exponent
                               -> (fIntPart + (2*hd)`div`b, [])
           (hd:hds) | hd >= b  -> (fIntPart+1, 0:hds)
           hds                 -> (fIntPart  ,   hds)
       (sigDigs, rmd) = go (exponent - uncrtExp - 1) (𝑥/μ - fromIntegral fIntPart)
        where go n 𝑟
                | n>0
                , r' <- floor 𝑟'
                , (sd', 𝑟'') <- go (n-1) (𝑟' - fromIntegral r')
                   = case sd' of
                      (sd₀:sds) | sd₀ >= b  -> (r'+1:0:sds, 𝑟''/𝑏)
                      _                     -> (r':sd', 𝑟''/𝑏)
                | r' <- round 𝑟'
                   = ([r'], 𝑟 - fromIntegral r'/𝑏)
               where 𝑟' = 𝑟*𝑏
       𝑏 = fromIntegral b

closeZero :: (Num a, Ord a) => a -> a -> a
closeZero c x | x >= c     = x
              | x <= (-c)  = x
              | otherwise  = 0