type HashIndex = VStr

class (Typeable a) => HashClass a where
    hash_iType :: a -> Type
    hash_iType = const $ mkType "Hash"
    hash_fetch       :: a -> Eval VHash
    hash_fetch hv = do
        keys <- hash_fetchKeys hv
        vals <- mapM (hash_fetchVal hv) keys
        let ps = keys `zip` vals
        return (length ps `seq` Map.fromList ps)
    hash_store       :: a -> VHash -> Eval ()
    hash_store hv vals = do
        hash_clear hv
        forM_ (Map.assocs vals) $ \(key, val) -> do
            hash_storeVal hv key val
    hash_fetchElem   :: a -> HashIndex -> Eval (IVar VScalar) -- autovivify
    hash_fetchElem hv key = do
        return $ proxyScalar (hash_fetchVal hv key) (hash_storeVal hv key)
    hash_storeElem   :: a -> HashIndex -> IVar VScalar -> Eval () -- binding
    hash_storeElem hv idx sv = do
        val <- readIVar sv
        hash_storeVal hv idx val
    hash_fetchVal    :: a -> HashIndex -> Eval Val
    hash_fetchVal hv key = do
        rv <- hash_existsElem hv key
        if rv then readIVar =<< hash_fetchElem hv key
              else return undef
    hash_storeVal    :: a -> HashIndex -> Val -> Eval ()
    hash_storeVal hv key val = do
        sv <- hash_fetchElem hv key
        writeIVar sv val
    hash_fetchKeys   :: a -> Eval [HashIndex]
    hash_fetchKeys hv = do
        vals <- hash_fetch hv
        return $ Map.keys vals
    hash_fetchSize   :: a -> Eval Int
    hash_fetchSize hv = do
        vals <- hash_fetch hv
        return $ Map.size vals
    hash_deleteElem  :: a -> HashIndex -> Eval ()
    hash_existsElem  :: a -> HashIndex -> Eval VBool
    hash_existsElem hv idx = do
        keys <- hash_fetchKeys hv
        return $ idx `elem` keys
    hash_clear       :: a -> Eval ()
    hash_clear hv = do
        keys <- hash_fetchKeys hv
        mapM_ (hash_deleteElem hv) keys
    hash_isEmpty     :: a -> Eval VBool
    hash_isEmpty hv = do
        keys <- hash_fetchKeys hv
        return $ null keys 
    hash_clone :: a -> STM a
    hash_clone = return

instance HashClass (IVar VPair) where
    hash_iType = const $ mkType "Pair"
    hash_fetch pv = do
        (k, v)  <- readIVar pv
        str     <- fromVal k
        return $ Map.singleton str v
    hash_fetchVal pv idx = do
        (k, v)  <- readIVar pv
        str     <- fromVal k
        if str == idx
            then return v
            else return undef
    hash_storeVal _ _ _ = retConstError undef
    hash_deleteElem _ _ = retConstError undef

instance HashClass VHash where
    hash_iType = const $ mkType "Hash::Const"
    hash_fetch = return
    hash_fetchKeys = return . Map.keys
    hash_fetchVal hv idx = return $ Map.findWithDefault undef idx hv
    hash_clear _ = retConstError undef
    hash_store _ _ = retConstError undef
    hash_storeVal _ _ _ = retConstError undef
    hash_storeElem _ _ _ = retConstError undef
    hash_deleteElem _ _ = retConstError undef

instance HashClass IHashEnv where
    hash_iType = const $ mkType "Hash::Env"
    hash_fetch _ = do
        envs <- io getEnvironment
        return . Map.map (VStr . decodeUTF8) $ Map.fromList envs
    hash_fetchVal _ key = tryIO undef $ do
        str <- getEnv key
        return $ fromMaybe VUndef (fmap (VStr . decodeUTF8) str)
    hash_storeVal _ key val = do
        str <- fromVal val
        io $ setEnv key (encodeUTF8 str) True
    hash_existsElem _ key = tryIO False $ do
        str <- getEnv key
        return (isJust str)
    hash_deleteElem _ key = do
        io $ unsetEnv key

encodeKey, decodeKey :: HashIndex -> HashIndex
encodeKey x = x
decodeKey x = x

instance HashClass IHash where
    hash_clone hv = do
        ps  <- unsafeIOToSTM $ H.toList hv
        ps' <- forM ps $ \(k, sv) -> do
            sv' <- cloneIVar sv
            return (k, sv')
        unsafeIOToSTM $ H.fromList H.hashString ps'
    hash_fetch hv = do
        ps  <- io $ H.toList hv
        ps' <- forM ps $ \(k, sv) -> do
            val <- readIVar sv
            return (decodeKey k, val)
        return (length ps' `seq` Map.fromList ps')
    hash_fetchKeys hv = do
        fmap (map (decodeKey . fst)) (io $ H.toList hv)
    hash_fetchElem hv idx = do
        let idx' = encodeKey idx
        r <- io $ H.lookup hv idx'
        case r of
             Just sv -> return sv
             Nothing -> do
                sv <- newScalar undef
                io $ H.insert hv idx' sv
                return sv
    hash_storeElem hv idx sv = do
        io $ H.insert hv (encodeKey idx) sv
    hash_deleteElem hv idx = do
        io $ H.delete hv (encodeKey idx)
        return ()
    hash_existsElem hv idx = do
        io $ fmap isJust (H.lookup hv (encodeKey idx))

instance HashClass PerlSV where
    hash_iType = const $ mkType "Hash::Perl"
    hash_fetchVal sv key = do
        keySV   <- fromVal $ castV key
        perl5EvalApply "sub { $_[0]->{$_[1]} }" [sv, keySV]
    hash_clear sv = do
        perl5EvalApply "sub { undef %{$_[0]} }" [sv]
        return ()
    hash_storeVal sv key val = do
        keySV   <- fromVal $ castV key
        valSV   <- fromVal val
        perl5EvalApply "sub { $_[0]->{$_[1]} = $_[2] }" [sv, keySV, valSV]
        return ()
    hash_fetchKeys sv = do
        keysSV  <- perl5EvalApply "sub { join $/, keys %{$_[0]} }" [sv]
        keysStr <- fromVal keysSV
        return $ lines keysStr
    hash_deleteElem sv key = do
        keySV   <- fromVal $ castV key
        perl5EvalApply "sub { delete $_[0]->{$_[1]} }" [sv, keySV]
        return ()
    hash_isEmpty sv = do
        fromVal =<< perl5EvalApply "sub { !!%{$_[0]} }" [sv]
