{-# LANGUAGE TemplateHaskell #-}
module Data.Encoding.Helper.Template
    (makeISOInstance
    ,makeJISInstance)
    where

import Data.Encoding.Base
import Data.Bits
import Data.Char
import Data.Maybe (mapMaybe)
import Data.Map as Map (Map,fromList,lookup)
import Data.Array.Unboxed
import Data.Typeable
import Data.Word (Word8)
import Language.Haskell.TH

makeISOInstance :: String -> FilePath -> Q [Dec]
makeISOInstance name file = do
  trans <- runIO (readTranslation 0 id file)
  let mp = encodingMap (validTranslations trans)
  let arr = decodingArray (fillTranslations 0 255 trans)
  return $ encodingInstance (ConT ''Word8) 'encodeWithMap 'decodeWithArray 'encodeableWithMap name mp arr

makeJISInstance :: Int -> String -> FilePath -> Q [Dec]
makeJISInstance offset name file = do
  trans <- runIO (readTranslation offset (\src -> (src `shiftR` 8,src .&. 0xFF)) file)
  let mp = encodingMap2 (validTranslations trans)
  let arr = decodingArray2 (fillTranslations (0x21,0x21) (0x7E,0x7E) trans)
  return $ encodingInstance ((TupleT 2) `AppT` (ConT ''Word8) `AppT` (ConT ''Word8)) 'encodeWithMap2 'decodeWithArray2 'encodeableWithMap name mp arr

encodingInstance :: Type -> Name -> Name -> Name -> String -> Exp -> Exp -> [Dec]
encodingInstance tp enc dec able name mp arr
    = [ DataD [] rname [] [NormalC rname []] [''Show,''Eq,''Typeable]
      , SigD rmp (((ConT ''Map.Map) `AppT` (ConT ''Char)) `AppT` tp)
      , ValD (VarP rmp) (NormalB mp) []
      , SigD rarr ((ConT ''UArray) `AppT` tp `AppT` (ConT ''Int))
      , ValD (VarP rarr) (NormalB arr) []
      , InstanceD [] (AppT (ConT ''Encoding) (ConT rname))
                      [FunD 'encodeChar
                       [Clause [WildP] (NormalB $ AppE (VarE enc) (VarE rmp))
                        []
                       ]
                      ,FunD 'decodeChar
                       [Clause [WildP] (NormalB $ AppE (VarE dec) (VarE rarr))
                        []
                       ]
                      ,FunD 'encodeable
                       [Clause [WildP] (NormalB $ AppE (VarE able) (VarE rmp))
                        []
                       ]
                      ]
      ]
    where
      rname = mkName name
      rarr = mkName ("encoding_arr_"++name)
      rmp = mkName ("decoding_map_"++name)

createCharArray :: [(Integer,Maybe Char)] -> Integer -> Integer -> Exp
createCharArray lst f t = createArray (map (\(x,y) ->
                                                (LitE $ IntegerL x,mbCharToExp y)
                                           ) lst) (LitE $ IntegerL f) (LitE $ IntegerL t)

createCharArray2 :: [((Integer,Integer),Maybe Char)] -> (Integer,Integer) -> (Integer,Integer) -> Exp
createCharArray2 lst (f1,f2) (t1,t2)
    = createArray (map (\((x1,x2),y) ->
                            (TupE [integerExp x1,integerExp x2],mbCharToExp y)
                       ) lst)
      (TupE [integerExp f1,integerExp f2])
      (TupE [integerExp t1,integerExp t2])

mbCharToExp :: Maybe Char -> Exp
mbCharToExp Nothing = LitE (IntegerL (-1))
mbCharToExp (Just c) = LitE (IntegerL $ fromIntegral $ ord c)

integerExp :: Integer -> Exp
integerExp i = LitE $ IntegerL i

createArray :: [(Exp,Exp)] -> Exp -> Exp -> Exp
createArray lst from to
    = AppE
      (AppE
       (VarE 'array)
       (TupE [from,to]))
      (ListE [TupE [x,y] | (x,y) <- lst])

decodingArray :: [(Integer,Maybe Char)] -> Exp
decodingArray trans = createCharArray trans 0 255

decodingArray2 :: [((Integer,Integer),Maybe Char)] -> Exp
decodingArray2 trans = createCharArray2 trans (0x21,0x21) (0x7E,0x7E)

encodingMap :: [(Integer,Char)] -> Exp
encodingMap trans = AppE
                    (VarE 'fromList)
		    (ListE [ TupE [LitE $ CharL to,LitE $ IntegerL from]
			     | (from,to) <- trans])

encodingMap2 :: [((Integer,Integer),Char)] -> Exp
encodingMap2 trans = AppE
                     (VarE 'fromList)
                     (ListE [ TupE [LitE $ CharL to,TupE [integerExp f1,integerExp f2]]
                              | ((f1,f2),to) <- trans])

readTranslation :: Int -> (Integer -> a) -> FilePath -> IO [(a,Maybe Char)]
readTranslation offset f file = do
  cont <- readFile file
  return $ mapMaybe (\ln -> case drop offset ln of
                             [src] -> Just (f src,Nothing)
                             [src,trg] -> Just (f src,Just $ chr $ fromIntegral trg)
                             _ ->  Nothing) (parseTranslationTable cont)

parseTranslationTable :: String -> [[Integer]]
parseTranslationTable cont = filter (not.null) (map (\ln -> map read (takeWhile ((/='#').head) (words ln))) (lines cont))

fillTranslations :: (Ix a,Show a) => a -> a -> [(a,Maybe Char)] -> [(a,Maybe Char)]
fillTranslations f t = merge (range (f,t))
    where
      merge xs [] = map (\x -> (x,Nothing)) xs
      merge [] cs  = error $ "Data.Encoding.Helper.Template.fillTranslations: Character translations out of range: " ++ show cs
      merge (x:xs) (y:ys) = if x < fst y
                            then (x,Nothing):(merge xs (y:ys))
                            else y:(merge xs ys)

validTranslations :: [(a,Maybe Char)] -> [(a,Char)]
validTranslations = mapMaybe (\(n,mc) -> case mc of
                                          Nothing -> Nothing
                                          Just c -> Just (n,c))