{-# OPTIONS_GHC -fglasgow-exts #-}

module PIL.Native.Coerce where
import PIL.Native.Types
import Control.Arrow
import Control.Exception
import Data.Dynamic
import Data.Typeable
import Data.ByteString.Base (ByteString(..))
import qualified Data.Map as Map
import qualified Data.Seq as Seq
import qualified Data.ByteString.Char8 as Str

{-| 

PIL.Native.Coerce

This module defines the functions for use with the types defined 
in PIL.Native.Types. 

See Also: 

  PIL.Native.Types
  PIL.Native.Prims

-}

nil :: Native
nil = toNative mkNil

is_nil :: Native -> Bool
is_nil (NError {})  = True
is_nil _            = False

emptySeq :: Native
emptySeq = toNative (empty :: NativeSeq)

emptyMap :: Native
emptyMap = toNative (empty :: NativeMap)

mkNil :: NativeError
mkNil = NonTermination

mkErr :: (Typeable a) => a -> NativeError
mkErr = DynException . toDyn

mkSeq :: [b] -> SeqOf b
mkSeq = Seq.fromList

mkMap :: [(String, a)] -> MapOf a
mkMap = Map.fromList . map (\(k, v) -> (mkStr k, v))

mkStr :: String -> NativeStr
mkStr = Str.pack

mkSub :: [String] -> [NativeLangExpression] -> NativeSub
mkSub params exps = MkSub
    { s_params = mkSeq (map mkStr params)
    , s_exps   = mkSeq exps
    , s_pad    = empty
    }

class IsPlural a key val | a -> key, a -> val where 
    isEmpty     :: a -> NativeBit
    size        :: a -> NativeInt
    reversed    :: a -> a
    exists      :: a -> key -> Bool
    empty       :: a
    indices     :: a -> [key]
    elems       :: a -> [val]
    append      :: a -> a -> a
    push        :: a -> SeqOf val -> a
    assocs      :: a -> [(key, val)]
    fromAssocs  :: [(key, val)] -> a
    splice      :: a -> Int -> a
    fetch       :: a -> key -> Maybe val
    insert      :: a -> key -> val -> a
    delete      :: a -> key -> a
    (!)         :: a -> key -> val
    (!) x k = maybe (error "index out of bounds") id $ fetch x k

instance IsPlural NativeStr NativeInt NativeStr where
    isEmpty    = Str.null
    size       = Str.length
    empty      = Str.empty
    exists (PS _ _ l) n = (n >= 0) && (n < l)
    indices    = \x -> [0 .. (Str.length x - 1)]
    elems (PS _ _ 0) = []
    elems (PS x s l) = (PS x s 1:elems (PS x (s+1) (l-1)))
    {-# INLINE elems #-}
    append     = Str.append
    reversed   = Str.reverse
    push       = \x xs -> Str.concat (x:Seq.toList xs)
    assocs     = zip [0..] . elems
    fromAssocs = Str.concat . map snd -- XXX wrong
    splice     = flip Str.drop
    fetch (PS p s l) n
        | n < 0     = fail "negative index"
        | n >= l    = fail "index out of bounds"
        | otherwise = return $ PS p (s + n) 1
    delete     = error "It doesn't make sense to delete from a string"
    insert     = error "XXX str.insert"

instance (Ord k, Show k) => IsPlural (Map.Map k v) k v where
    isEmpty    = Map.null
    size       = Map.size
    empty      = Map.empty
    indices    = Map.keys
    elems      = Map.elems
    exists     = flip Map.member
    append     = Map.union
    push       = error "It doesn't make sense to push into a hash"
    splice     = error "It doesn't make sense to splice from a hash"
    reversed   = error "It doesn't make sense to reverse from a hash"
    assocs     = Map.assocs
    fromAssocs = Map.fromList
    fetch      = flip Map.lookup
    delete     = flip Map.delete    
    insert     = \o k v -> Map.insert k v o
    (!) x k    = case Map.lookup k x of
        Just v  -> v
        Nothing -> error $ "Cannot find " ++ show k ++ " in map: " ++ show (indices x)

instance IsPlural (SeqOf a) NativeInt a where
    isEmpty      = Seq.null
    size         = Seq.length
    empty        = Seq.empty
    exists x n   = (n >= 0) && (n < size x)
    indices      = \x -> [0 .. size x - 1]
    elems        = Seq.toList
    append       = (Seq.><)
    push         = append
    reversed     = Seq.reverse
    splice       = flip Seq.drop
    assocs       = ([0..] `zip`) . elems
    fromAssocs   = Seq.fromList . map snd -- XXX wrong
    fetch x k    | k >= size x = Nothing
                 | otherwise   = Just (Seq.index x k)
    insert x k v | k == size x = (Seq.|>) x v
                 | otherwise   = Seq.update k v x
    delete       = error "It doesn't make sense to delete from an array"                 
    (!)          = Seq.index

class Show a => IsNative a where 
    toNative   :: a -> Native
    fromNative :: Native -> a
    toString   :: a -> String
    toString   = show

instance IsNative Native where
    toNative   = id
    fromNative = id
    toString (NError x)   = toString x
    toString (NBit x)     = toString x
    toString (NInt x)     = toString x
    toString (NNum x)     = toString x
    toString (NStr x)     = toString x
    toString (NSeq x)     = toString x
    toString (NMap x)     = toString x
    toString x            = castFail x

instance IsNative NativeBit where
    toNative = NBit
    fromNative (NError {})  = False      -- Errors are undefs are false
    fromNative (NBit x)     = x
    fromNative (NInt x)     = (x /= 0)
    fromNative (NNum x)     = (x /= 0)
    fromNative (NStr x)     = case size x of
        0   -> False
        1   -> (Str.head x /= '0')
        _   -> True
    fromNative (NSeq x)     = isEmpty x
    fromNative (NMap x)     = isEmpty x
    fromNative (NSub _)     = True
    fromNative (NObj _)     = True

instance IsNative NativeInt where
    toNative = NInt
    fromNative (NError {})  = 0
    fromNative (NBit x)     = fromEnum x
    fromNative (NInt x)     = x
    fromNative (NNum x)     = fromEnum x
    fromNative (NStr x)     = read (toString x)
    fromNative (NSeq x)     = size x
    fromNative (NMap x)     = size x
    fromNative x            = castFail x

instance IsNative NativeStr where
    toNative = NStr
    toString = Str.unpack -- unpackFromUTF8
    fromNative (NError {})  = empty
    fromNative (NBit x)     = if x then mkStr "1" else mkStr "0"
    fromNative (NInt x)     = mkStr $ toString x
    fromNative (NNum x)     = mkStr $ toString x
    fromNative (NStr x)     = x
    fromNative (NSeq x)     = Str.unwords $ map fromNative (elems x)
    fromNative (NMap x)     = Str.unlines $ map fromPair (assocs x)
        where
        fromPair (k, v) = Str.append k (Str.cons '\t' (fromNative v))
    fromNative x            = castFail x

instance IsNative NativeNum where
    toNative = NNum
    fromNative (NError {})  = 0
    fromNative (NBit x)     = if x then 1 else 0
    fromNative (NInt x)     = toEnum x
    fromNative (NNum x)     = x
    fromNative (NStr x)     = read (toString x)
    fromNative (NSeq x)     = toEnum (size x)
    fromNative (NMap x)     = toEnum (size x)
    fromNative x            = castFail x

instance IsNative NativeMap where
    toNative = NMap
    fromNative (NError {})  = empty
    fromNative (NMap x)     = x
    fromNative (NSeq x)     = Map.fromList (roll (Seq.toList x))
        where
        roll [] = []
        roll [_] = error "odd number of hash elements"
        roll (k:v:xs) = ((fromNative k, v):roll xs)
    fromNative x            = castFail x

instance IsNative NativeSub where
    toNative = NSub
    fromNative (NSub x)     = x
    fromNative x            = castFail x

instance IsNative NativeObj where
    toNative = NObj
    fromNative (NObj x)     = x
    fromNative x            = castFail x

instance IsNative NativeError where
    toNative = NError
    fromNative (NError x)   = x
    fromNative x            = mkErr x

instance IsNative Integer where
    toNative = toNative . fromEnum
    fromNative = toEnum . fromNative

instance IsNative Double where
    toNative = (toNative :: NativeNum -> Native) . uncurry encodeFloat . decodeFloat
    fromNative = uncurry encodeFloat . decodeFloat . (fromNative :: Native -> NativeNum)

instance IsNative (Either Integer Double) where
    toNative = either toNative toNative
    fromNative (NNum x) = (Right . uncurry encodeFloat . decodeFloat) x
    fromNative n        = (Left . fromNative) n

instance IsNative String where
    toNative = toNative . mkStr
    fromNative = Str.unpack . fromNative -- unpackFromUTF8 . fromNative

instance IsNative [Native] where
    toNative = NSeq . mkSeq
    fromNative = Seq.toList . (fromNative :: Native -> NativeSeq)

instance IsNative [(Native, Native)] where
    toNative = NMap . Map.fromList . map ((fromNative :: Native -> NativeStr) *** id) 
    fromNative = Map.assocs . Map.mapKeys (toNative :: NativeStr -> Native) . fromNative

instance IsNative [NativeStr] where
    toNative = (toNative :: NativeSeq -> Native) . mkSeq . map toNative
    fromNative = map fromNative . fromNative

instance (IsNative a) => IsNative (Maybe a) where
    toNative Nothing  = nil
    toNative (Just x) = toNative x
    fromNative (NError {}) = Nothing
    fromNative x           = Just (fromNative x)

instance (IsNative a) => IsNative (SeqOf a) where
    toNative = NSeq . fmap toNative
    fromNative (NError {})  = empty
    fromNative (NSeq x)     = fmap fromNative x
    fromNative (NMap x)     = mkSeq (fmap fromNative (unroll (Map.toAscList x)))
        where
        unroll [] = []
        unroll ((k, v):xs) = (toNative k:v:unroll xs)
    fromNative x            = castFail x

instance IsNative () where
    toNative () = nil
    fromNative (NError {}) = ()
    fromNative x = castFail x

instance IsNative [NativeObj] where
    toNative = NSeq . mkSeq . map toNative
    fromNative (NError {})  = []
    fromNative (NSeq x)     = elems $ fmap fromNative x
    fromNative x            = castFail x

castFail :: a -> b
castFail _ = error "cast fail"

failWith :: (Monad m, IsNative a) => String -> a -> m b
failWith msg s = fail $ msg ++ ": " ++ toString s
