-- Copyright (c) 2009, Diego Souza
-- All rights reserved.
-- 
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are met:
-- 
--   * Redistributions of source code must retain the above copyright notice,
--     this list of conditions and the following disclaimer.
--   * Redistributions in binary form must reproduce the above copyright notice,
--     this list of conditions and the following disclaimer in the documentation
--     and/or other materials provided with the distribution.
--   * Neither the name of the <ORGANIZATION> nor the names of its contributors
--     may be used to endorse or promote products derived from this software
--     without specific prior written permission.
-- 
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
-- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-- DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
-- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-- SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-- CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-- OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

-- | Percent encoding <http://tools.ietf.org/html/rfc3986#page-12> functions,
-- with the exception that all encoding/decoding is in UTF-8.
module Network.OAuth.Http.PercentEncoding (PercentEncoding(..)
                                          ,decodeWithDefault
                                          ) where

import Data.Monoid (mappend)
import Data.List (splitAt)
import qualified Codec.Binary.UTF8.String as U
import Data.Char (intToDigit,digitToInt,toUpper,ord)
import Data.Bits

class PercentEncoding a where
  -- | Encodes a type into its percent encoding representation.
  encode :: a -> String

  -- | Decodes a percent-encoded type to its native type.
  decode :: String -> Maybe (a,String)

-- | Encodes Char types using UTF\-8 charset.
instance PercentEncoding Char where
  encode c | c `elem` whitelist = [c]
           | otherwise          = concatMap (run.fromIntegral) (U.encode [c])
    where whitelist =    ['a'..'z'] 
                      ++ ['A'..'Z']
                      ++ ['0'..'9']
                      ++ "-._~"
          run b = '%' : map (toUpper.intToDigit) [shiftR (b .&. 0xF0) 4,b .&. 0x0F]

  decode xs = case (U.decode . tobytes $ xs)
              of []     -> Nothing
                 (y:_)  -> let sizeof = if ("%"==take 1 xs)
                                        then length (encode y)
                                        else 1
                           in Just (y,drop sizeof xs)
    where tobytes (b:bs) = case b 
                           of '%' -> let ([c0,c1],bs') = splitAt 2 bs
                                         b0            = (shiftL (digitToInt c0) 4) .&. 0xF0
                                         b1            = (digitToInt c1) .&. 0x0F
                                         byte          = fromIntegral (b0 .|. b1)
                                     in byte : tobytes bs'
                              _   -> fromIntegral (ord b) : tobytes bs
          tobytes []     = []

-- | Add support for encoding strings
instance (PercentEncoding a) => PercentEncoding [a] where
  encode (x:xs) = encode x ++ encode xs
  encode []     = []
  
  decode xs = do (c,ys) <- (decode xs)
                 cs     <- fmap (fst) (decode ys `mappend` Just ([],""))
                 return (c:cs,"")

-- | Decodes a percent encoded string. In case of failure returns a default value, instead of Nothing.
decodeWithDefault :: (PercentEncoding a) => a -> String -> a
decodeWithDefault def str = case (decode str)
                            of Just (v,"") -> v
                               _           -> def

-- vim:sts=2:sw=2:ts=2:et