{-# LANGUAGE ExistentialQuantification #-}
module Data.Encoding.Base where

import Data.Encoding.Exception
import Data.Encoding.ByteSource
import Data.Encoding.ByteSink

import Control.Throws
import Data.Array.Unboxed as Array
import Data.Map as Map hiding ((!))
import Data.Word
import Data.Char
import Data.Typeable

{- | The base class for all encodings. At least decodeChar, encodeChar and encodeable must be implemented.
 -}
class Encoding enc where
    -- | Read a single character of a ByteSource
    decodeChar :: ByteSource m => enc -> m Char
    -- | Encode a single character and write it to a ByteSink
    encodeChar :: ByteSink m => enc -> Char -> m ()
    -- | Read characters from a ByteSource until it is empty
    decode :: ByteSource m => enc -> m String
    decode e = untilM sourceEmpty (decodeChar e)
    -- | Encode a String and write it to a ByteSink
    encode :: ByteSink m => enc -> String -> m ()
    encode e = mapM_ (encodeChar e)
    -- | Tests whether a given character is representable in the Encoding.
    --   If this yields True, encodeChar must not fail.
    --   If it yields False, encodeChar _must_ throw an exception.
    encodeable :: enc -> Char -> Bool

{- | Wraps all possible encoding types into one data type.
     Used when a function needs to return an encoding.
 -}
data DynEncoding = forall enc. (Encoding enc,Eq enc,Typeable enc,Show enc) => DynEncoding enc

instance Show DynEncoding where
    show (DynEncoding enc) = show enc

instance Encoding DynEncoding where
    decodeChar (DynEncoding e) = decodeChar e
    encodeChar (DynEncoding e) = encodeChar e
    decode (DynEncoding e) = decode e
    encode (DynEncoding e) = encode e
    encodeable (DynEncoding e) = encodeable e

instance Eq DynEncoding where
    (DynEncoding e1) == (DynEncoding e2) = case cast e2 of
                                             Nothing -> False
                                             Just e2' -> e1==e2'

untilM :: Monad m => m Bool -> m a -> m [a]
untilM check act = do
  end <- check
  if end
    then return []
    else (do
           x <- act
           xs <- untilM check act
           return (x:xs)
         )

untilM_ :: Monad m => m Bool -> m a -> m ()
untilM_ check act = untilM check act >> return ()

encodeWithMap :: ByteSink m => Map Char Word8 -> Char -> m ()
encodeWithMap mp c = case Map.lookup c mp of
                       Nothing -> throwException $ HasNoRepresentation c
                       Just v -> pushWord8 v

encodeWithMap2 :: ByteSink m => Map Char (Word8,Word8) -> Char -> m ()
encodeWithMap2 mp c = case Map.lookup c mp of
                        Nothing -> throwException $ HasNoRepresentation c
                        Just (w1,w2) -> do
                          pushWord8 w1
                          pushWord8 w2

encodeableWithMap :: Map Char a -> Char -> Bool
encodeableWithMap = flip Map.member

decodeWithArray :: ByteSource m => UArray Word8 Int -> m Char
decodeWithArray arr = do
  w <- fetchWord8
  let res = arr!w
  if res < 0
    then throwException $ IllegalCharacter w
    else return $ chr res

decodeWithArray2 :: ByteSource m => UArray (Word8,Word8) Int -> m Char
decodeWithArray2 arr = do
  w1 <- fetchWord8
  w2 <- fetchWord8
  if inRange (bounds arr) (w1,w2)
    then (do
           let res = arr!(w1,w2)
           if res < 0
             then throwException $ IllegalCharacter w1
             else return $ chr res
         )
    else throwException $ IllegalCharacter w1
