import Data.Array.Base(unsafeAt)
import Data.Bits(shiftL,shiftR,(.&.))
import Foreign.Ptr (plusPtr)
import Foreign.Storable (peek, poke)
import Foreign.ForeignPtr (withForeignPtr)
import System.IO.Unsafe (unsafePerformIO)

-- |Perform base64 encoding of data from standard input
main :: IO()
main = BL.getContents>>=BL.putStr.BL.fromChunks.map encode64.reChunk.BL.toChunks

-- |Base64 index table 
tab64 :: UArray Word32 Word8
tab64 = array (0,63) $ zip [0..] $ map (BI.c2w) $ 
        ['A'..'Z']++['a'..'z']++['0'..'9']++['+','/']

-- |Encodes 3 octets into 4 sextets
enc64 :: (Word8,Word8,Word8)->(Word8,Word8,Word8,Word8)
enc64 (b1,b2,b3) = (t 3,t 2,t 1,t 0)
    where t x   = tab64 `unsafeAt` (n `shiftR` (x*6) .&. 63)
          f b n = fromIntegral b `shiftL` n
          n     = f b1 16 + f b2 8 + f b3 0

-- |Transforms list of ByteStrings to a new list of ByteStrings with 
-- lengths guaranteed to be multiples of 3 (excepting the last one)
-- Assumes that all input ByteStrings (excepting the last one) have 
-- at least a length of 3.
reChunk :: [BS.ByteString] -> [BS.ByteString]
reChunk (y:[]) = [y]
reChunk (y:z:zs) = let c = BS.length y `mod` 3 
                    in BS.append y (BS.take 3 z):(reChunk $ (BS.drop 3 z):zs)