-- Defined in RFC 2045.
-- We assume we have US-ASCII characters.
-- We return a string with native '\n' line endings.

module Codec.MIME.String.QuotedPrintable (encode, decode) where

import Codec.MIME.String.Internal.Utils
import Data.Bits
import Data.Char
import Data.List

encode :: [String] -> String
encode = enc 0

-- The Int is the number of characters on this line so far
-- 76 is the maximum we can have no one line, and 3 is the most
-- generated for 1 input char (but we also need space for a trailing
-- '=' for a soft line break).
enc :: Int -> [String] -> String
enc _ [] = ""
enc _ [[]] = ""
enc _ ([]:ls) = '\n':enc 0 ls
enc n ls | n > 72 = '=':'\n':enc 0 ls
enc n ((c:cs):ls)
 | (33 <= o && o <= 126 && o /= 61) ||
   (not (null cs) && (o == 9 || o == 32))  = c:enc (n+1) (cs:ls)
 | otherwise                               = '=':x1:x2:enc (n+3) (cs:ls)
    where o = ord c
          x1 = toUpper $ intToDigit (o `shiftR` 4)
          x2 = toUpper $ intToDigit (o .&. 0xF)

-- decode is very forgiving, and makes some best guesses
decode :: [String] -> String
decode = dec . intercalate "\n"
       . removeSoftLinebreaks
       . map (dropFromEndWhile is_tab_space)
    where is_tab_space ' ' = True
          is_tab_space '\t' = True
          is_tab_space _ = False
          breakLast "" = ("", "")
          breakLast [x] = ("", [x])
          breakLast (x:xs) = case breakLast xs of
                                 (ys, zs) -> (x:ys, zs)
          removeSoftLinebreaks [] = []
          removeSoftLinebreaks (x:xs)
              = case breakLast x of
                    (x', "=") ->
                        case removeSoftLinebreaks xs of
                            [] -> [x']
                            (y:ys) -> (x' ++ y):ys
                    _ -> x:removeSoftLinebreaks xs

dec :: String -> String
dec ('=':c1:c2:cs)
 | isAsciiHexDigit c1 && isAsciiHexDigit c2
    = chr ((digitToInt c1 `shiftL` 4)  + digitToInt c2):dec cs
dec (c:cs) = c:dec cs
dec "" = ""