-- | This module implements Douglas Crockford's base32 encoding scheme to
--   store integers as text. See <http://www.crockford.com/wrmg/base32.html>
--   for more details on how this scheme works.
module Codec.Crockford (encode, decode, prop_crockfordRoundTrip) where

import Data.Char
import Data.Digits (digits, unDigits)
import Data.Maybe
import Test.QuickCheck

-- | Decodes a Crockford-encoded String into an integer, if possible. Returns
--   Nothing if the string is not a valid Crockford-encoded value.
decode :: Integral i => String -> Maybe i
decode :: forall i. Integral i => String -> Maybe i
decode String
s = (Char -> Maybe i) -> String -> Maybe [i]
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
forall (m :: * -> *) a b. Monad m => (a -> m b) -> [a] -> m [b]
mapM Char -> Maybe i
forall i. Integral i => Char -> Maybe i
decodeChar String
s Maybe [i] -> ([i] -> Maybe i) -> Maybe i
forall a b. Maybe a -> (a -> Maybe b) -> Maybe b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= i -> Maybe i
forall a. a -> Maybe a
forall (m :: * -> *) a. Monad m => a -> m a
return (i -> Maybe i) -> ([i] -> i) -> [i] -> Maybe i
forall b c a. (b -> c) -> (a -> b) -> a -> c
. i -> [i] -> i
forall n. Integral n => n -> [n] -> n
unDigits i
32

-- | Encodes an integer into a String, using Douglas Crockford's base32
--   encoding.
encode :: Integral i => i -> String
encode :: forall i. Integral i => i -> String
encode = Maybe String -> String
forall a. HasCallStack => Maybe a -> a
fromJust (Maybe String -> String) -> (i -> Maybe String) -> i -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (i -> Maybe Char) -> [i] -> Maybe String
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
forall (m :: * -> *) a b. Monad m => (a -> m b) -> [a] -> m [b]
mapM i -> Maybe Char
forall i. Integral i => i -> Maybe Char
encodeChar ([i] -> Maybe String) -> (i -> [i]) -> i -> Maybe String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. i -> i -> [i]
forall n. Integral n => n -> n -> [n]
digits i
32

decodeChar :: Integral i => Char -> Maybe i
decodeChar :: forall i. Integral i => Char -> Maybe i
decodeChar Char
c = case Char -> Char
toUpper Char
c of
    Char
'0' -> i -> Maybe i
forall a. a -> Maybe a
Just i
0
    Char
'O' -> i -> Maybe i
forall a. a -> Maybe a
Just i
0
    Char
'1' -> i -> Maybe i
forall a. a -> Maybe a
Just i
1
    Char
'I' -> i -> Maybe i
forall a. a -> Maybe a
Just i
1
    Char
'L' -> i -> Maybe i
forall a. a -> Maybe a
Just i
1
    Char
'2' -> i -> Maybe i
forall a. a -> Maybe a
Just i
2
    Char
'3' -> i -> Maybe i
forall a. a -> Maybe a
Just i
3
    Char
'4' -> i -> Maybe i
forall a. a -> Maybe a
Just i
4
    Char
'5' -> i -> Maybe i
forall a. a -> Maybe a
Just i
5
    Char
'6' -> i -> Maybe i
forall a. a -> Maybe a
Just i
6
    Char
'7' -> i -> Maybe i
forall a. a -> Maybe a
Just i
7
    Char
'8' -> i -> Maybe i
forall a. a -> Maybe a
Just i
8
    Char
'9' -> i -> Maybe i
forall a. a -> Maybe a
Just i
9
    Char
'A' -> i -> Maybe i
forall a. a -> Maybe a
Just i
10
    Char
'B' -> i -> Maybe i
forall a. a -> Maybe a
Just i
11
    Char
'C' -> i -> Maybe i
forall a. a -> Maybe a
Just i
12
    Char
'D' -> i -> Maybe i
forall a. a -> Maybe a
Just i
13
    Char
'E' -> i -> Maybe i
forall a. a -> Maybe a
Just i
14
    Char
'F' -> i -> Maybe i
forall a. a -> Maybe a
Just i
15
    Char
'G' -> i -> Maybe i
forall a. a -> Maybe a
Just i
16
    Char
'H' -> i -> Maybe i
forall a. a -> Maybe a
Just i
17
    Char
'J' -> i -> Maybe i
forall a. a -> Maybe a
Just i
18
    Char
'K' -> i -> Maybe i
forall a. a -> Maybe a
Just i
19
    Char
'M' -> i -> Maybe i
forall a. a -> Maybe a
Just i
20
    Char
'N' -> i -> Maybe i
forall a. a -> Maybe a
Just i
21
    Char
'P' -> i -> Maybe i
forall a. a -> Maybe a
Just i
22
    Char
'Q' -> i -> Maybe i
forall a. a -> Maybe a
Just i
23
    Char
'R' -> i -> Maybe i
forall a. a -> Maybe a
Just i
24
    Char
'S' -> i -> Maybe i
forall a. a -> Maybe a
Just i
25
    Char
'T' -> i -> Maybe i
forall a. a -> Maybe a
Just i
26
    Char
'V' -> i -> Maybe i
forall a. a -> Maybe a
Just i
27
    Char
'W' -> i -> Maybe i
forall a. a -> Maybe a
Just i
28
    Char
'X' -> i -> Maybe i
forall a. a -> Maybe a
Just i
29
    Char
'Y' -> i -> Maybe i
forall a. a -> Maybe a
Just i
30
    Char
'Z' -> i -> Maybe i
forall a. a -> Maybe a
Just i
31
    Char
_ -> Maybe i
forall a. Maybe a
Nothing

encodeChar :: Integral i => i -> Maybe Char
encodeChar :: forall i. Integral i => i -> Maybe Char
encodeChar i
i = case i
i of
    i
0  -> Char -> Maybe Char
forall a. a -> Maybe a
Just Char
'0'
    i
1  -> Char -> Maybe Char
forall a. a -> Maybe a
Just Char
'1'
    i
2  -> Char -> Maybe Char
forall a. a -> Maybe a
Just Char
'2'
    i
3  -> Char -> Maybe Char
forall a. a -> Maybe a
Just Char
'3'
    i
4  -> Char -> Maybe Char
forall a. a -> Maybe a
Just Char
'4'
    i
5  -> Char -> Maybe Char
forall a. a -> Maybe a
Just Char
'5'
    i
6  -> Char -> Maybe Char
forall a. a -> Maybe a
Just  Char
'6'
    i
7  -> Char -> Maybe Char
forall a. a -> Maybe a
Just Char
'7'
    i
8  -> Char -> Maybe Char
forall a. a -> Maybe a
Just Char
'8'
    i
9  -> Char -> Maybe Char
forall a. a -> Maybe a
Just Char
'9'
    i
10 -> Char -> Maybe Char
forall a. a -> Maybe a
Just Char
'A'
    i
11 -> Char -> Maybe Char
forall a. a -> Maybe a
Just Char
'B'
    i
12 -> Char -> Maybe Char
forall a. a -> Maybe a
Just Char
'C'
    i
13 -> Char -> Maybe Char
forall a. a -> Maybe a
Just Char
'D'
    i
14 -> Char -> Maybe Char
forall a. a -> Maybe a
Just Char
'E'
    i
15 -> Char -> Maybe Char
forall a. a -> Maybe a
Just Char
'F'
    i
16 -> Char -> Maybe Char
forall a. a -> Maybe a
Just Char
'G'
    i
17 -> Char -> Maybe Char
forall a. a -> Maybe a
Just Char
'H'
    i
18 -> Char -> Maybe Char
forall a. a -> Maybe a
Just Char
'J'
    i
19 -> Char -> Maybe Char
forall a. a -> Maybe a
Just Char
'K'
    i
20 -> Char -> Maybe Char
forall a. a -> Maybe a
Just Char
'M'
    i
21 -> Char -> Maybe Char
forall a. a -> Maybe a
Just Char
'N'
    i
22 -> Char -> Maybe Char
forall a. a -> Maybe a
Just Char
'P'
    i
23 -> Char -> Maybe Char
forall a. a -> Maybe a
Just Char
'Q'
    i
24 -> Char -> Maybe Char
forall a. a -> Maybe a
Just Char
'R'
    i
25 -> Char -> Maybe Char
forall a. a -> Maybe a
Just Char
'S'
    i
26 -> Char -> Maybe Char
forall a. a -> Maybe a
Just Char
'T'
    i
27 -> Char -> Maybe Char
forall a. a -> Maybe a
Just Char
'V'
    i
28 -> Char -> Maybe Char
forall a. a -> Maybe a
Just Char
'W'
    i
29 -> Char -> Maybe Char
forall a. a -> Maybe a
Just Char
'X'
    i
30 -> Char -> Maybe Char
forall a. a -> Maybe a
Just Char
'Y'
    i
31 -> Char -> Maybe Char
forall a. a -> Maybe a
Just Char
'Z'
    i
_ -> Maybe Char
forall a. Maybe a
Nothing

-- | A QuickCheck property that asserts a positive integer encoded and then
--   decoded using this module remains the same number.
prop_crockfordRoundTrip
    :: Integer -- ^ The integer to test.
    -> Property
prop_crockfordRoundTrip :: Integer -> Property
prop_crockfordRoundTrip Integer
i = Integer
i Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
> Integer
0 Bool -> Bool -> Property
forall prop. Testable prop => Bool -> prop -> Property
==> Integer -> Maybe Integer
forall a. a -> Maybe a
Just Integer
i Maybe Integer -> Maybe Integer -> Bool
forall a. Eq a => a -> a -> Bool
== (String -> Maybe Integer
forall i. Integral i => String -> Maybe i
decode (String -> Maybe Integer)
-> (Integer -> String) -> Integer -> Maybe Integer
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Integer -> String
forall i. Integral i => i -> String
encode) Integer
i