    ---------------------------------------------------------------------------
    -- |
    -- Module      :  Data.Numbers.CrackNum.Utils
    -- Copyright   :  (c) Levent Erkok
    -- License     :  BSD3
    -- Maintainer  :  erkokl@gmail.com
    -- Stability   :  experimental
    --
    -- Various utils and sundry
    -----------------------------------------------------------------------------

    module Data.Numbers.CrackNum.Utils where

    import Data.Char     (toLower)
    import Data.List     (genericIndex)
    import Numeric

    import Data.Numbers.CrackNum.Data (Precision(..), IPrecision(..))

    -- | Returns True if all bits are False
    all0 :: [Bool] -> Bool
    all0 = all not

    -- | Returns True if all bits are True
    all1 :: [Bool] -> Bool
    all1 = and

    -- | Returns True if any bit is True
    any1 :: [Bool] -> Bool
    any1 = (True `elem`)

    -- | Lay out a sequence of separated bools as a nicely formatted binary number
    layOut :: [[Bool]] -> String
    layOut = unwords . map b2s

    -- | Binary to String conversion
    b2s :: [Bool] -> String
    b2s bs = concat [if b then "1" else "0" | b <- bs]

    -- | Test whether a digit is binary
    isBinDigit :: Char -> Bool
    isBinDigit = (`elem` "01")

    -- | Convert from binary char digit to value
    binDigit :: Char -> Int
    binDigit '0' = 0
    binDigit '1' = 1
    binDigit c   = error $ "binDigit: recevied: " ++ show c

    -- | Read a number in base 16
    readB16 :: String -> Integer
    readB16 s = case readHex s of
                  [(v, "")] -> v
                  _         -> error $ "Invalid hex input: " ++ show s

    -- | Read a number in base 2
    readB2 :: String -> Integer
    readB2 s = case readInt 2 isBinDigit binDigit s of
                  [(v, "")] -> v
                  _         -> error $ "Invalid binary input: " ++ show s

    -- | Display a binary number in groups of 4
    binDisp :: [Bool] -> String
    binDisp = grpBy4 . b2s

    -- | Group in chunks of 44
    grpBy4 :: String -> String
    grpBy4 = grp False
      where grp _   [] = []
            grp sep xs = let (f, r) = splitAt 4 xs in (if sep then " " else "") ++ f ++ grp True r

    -- | Display a binary number in groups of 4, in hexadecimal format
    hexDisp :: [Bool] -> String
    hexDisp = grpBy4 . chunkHex
      where chunkHex [] = []
            chunkHex xs = let (f, r) = splitAt 4 xs in (letters `genericIndex` (bv f :: Int)) : chunkHex r
            letters = ['0' .. '9'] ++ ['A' .. 'F']

    -- | Cluster a list into given size chunks
    cluster :: Int -> [a] -> [[a]]
    cluster n is = go is
      where s = length is `div` n
            go [] = []
            go xs = let (f, r) = splitAt s xs in f : go r

    -- | Big-endian num converter
    bv :: Num a => [Bool] -> a
    bv = foldr (\b a -> 2 * a + b2i b) 0 . reverse
     where b2i b = if b then 1 else 0

    -- | Drop unnecessary parts from input. This enables the user to be able to give data more easily
    cleanUp :: String -> String
    cleanUp = map toLower . filter (not . ignorable)
      where ignorable = (`elem` " _-")

    ----------------------------------------------------------------------------------------------------
    -- Rulers
    ----------------------------------------------------------------------------------------------------

    -- | Half-precision ruler, line 1
    hpInds1 :: String
    -- | Half-precision ruler, line 2
    hpInds2 :: String
    -- | Half-precision ruler, line 3
    hpInds3 :: String

    hpInds1 = "1       0"
    hpInds2 = "5 43210 9876543210"
    hpInds3 = "S -E5-- ---F10----"

    -- | Single-precision ruler, line 1
    spInds1 :: String
    -- | Single-precision ruler, line 2
    spInds2 :: String
    -- | Single-precision ruler, line 3
    spInds3 :: String

    spInds1 = "3  2          1         0"
    spInds2 = "1 09876543 21098765432109876543210"
    spInds3 = "S ---E8--- ----------F23----------"

    -- | Double-precision ruler, line 1
    dpInds1 :: String
    -- | Double-precision ruler, line 2
    dpInds2 :: String
    -- | Double-precision ruler, line 3
    dpInds3 :: String

    dpInds1 = "6    5          4         3         2         1         0"
    dpInds2 = "3 21098765432 1098765432109876543210987654321098765432109876543210"
    dpInds3 = "S ----E11---- ------------------------F52-------------------------"

    -- | Byte-precision ruler, line 2 (note that no line 1 is needed!)
    bInds2 :: String
    bInds2 = "7654 3210"

    -- | Word-precision ruler, line 1
    wInds1 :: String
    -- | Word-precision ruler, line 2
    wInds2 :: String

    wInds1 = "1      0"
    wInds2 = "5432 1098 7654 3210"

    -- | Double-word-precision ruler, line 1
    dInds1 :: String
    -- | Double-word-precision ruler, line 2
    dInds2 :: String

    dInds1 = "3 2            1           0"
    dInds2 = "1098 7654 3210 9876 5432 1098 7654 3210"

    -- | Quad-word-precision ruler, line 1
    qInds1 :: String
    -- | QuadDouble-word-precision ruler, line 2
    qInds2 :: String

    qInds1 = "6    5           4            3           2            1           0"
    qInds2 = "3210 9876 5432 1098 7654 3210 9876 5432 1098 7654 3210 9876 5432 1098 7654 3210"

    -- | Convert Floating point precision to corresponding number of bits
    fpSz :: Precision -> Int
    fpSz HP = 16
    fpSz SP = 32
    fpSz DP = 64

    -- | Convert Integer precision to whether it's signed and how many bits
    sgSz :: IPrecision -> (Bool, Int)
    sgSz W8  = (False,  8)
    sgSz I8  = (True,   8)
    sgSz W16 = (False, 16)
    sgSz I16 = (True,  16)
    sgSz W32 = (False, 32)
    sgSz I32 = (True,  32)
    sgSz W64 = (False, 64)
    sgSz I64 = (True,  64)