module Codec.Base64Spec where

import SpecHelper
import Data.Bits
import Test.Hspec.QuickCheck (prop)
import Test.QuickCheck hiding ((.&.))

newtype Base64 = Base64 String deriving (Eq, Show)

base64 :: String
base64 = ['A'..'Z']++['a'..'z']++['0'..'9']++"+/"

instance Arbitrary Base64 where
    arbitrary = do
        xs <- listOf $ elements base64
        case length xs `mod` 4 of
            0 -> return (Base64 xs)
            1 -> do
                c <- elements "AQgw"
                return (Base64 $ xs ++ c : "==")
            2 -> do
                i <- choose (0,64)
                let c = base64 !! (i .&. 0x3c)
                return (Base64 $ xs ++ c : "=")
            _ -> do
                c <- elements base64
                return (Base64 $ xs ++ [c])

spec :: Spec
spec = do
    describe "encode" $ do
        it "encodes multiples of 3" $
            encode "no-padding!!" `shouldBe` "bm8tcGFkZGluZyEh"
        it "encodes multiples of 3 + 1" $
            encode "padding  2" `shouldBe` "cGFkZGluZyAgMg=="
        it "encodes multiples of 3 + 2" $
            encode "padding1" `shouldBe` "cGFkZGluZzE="

        prop "reverses decoded string" $ \(Base64 xs) ->
            encode (decode xs) == xs

    describe "decode" $ do
        it "decodes no padding" $
            decode "bm8tcGFkZGluZyEh" `shouldBe` "no-padding!!"
        it "decodes one padding" $
            decode "cGFkZGluZzE=" `shouldBe` "padding1"
        it "decodes two paddings" $
            decode "cGFkZGluZyAgMg==" `shouldBe` "padding  2"

        prop "reverses encoded string" $ \xs ->
            decode (encode xs) == xs

main :: IO ()
main = hspec spec
