module FM where
import List

type FiniteMap key elt = [(key,elt)] in
 emptyFM,unitFM,listToFM,addToFM,addToFM_C,plusFM,plusFM_C,delFromFM,delListFromFM,
 sizeFM,isEmptyFM,elemFM,lookupFM,foldFM,mapFM,filterFM,fmToList,keysFM,eltsFM,minusFM

-- Building
emptyFM :: FiniteMap key elt
emptyFM = []

unitFM :: key -> elt -> FiniteMap key elt
unitFM key elt = [(key,elt)]

listToFM :: [(key,elt)] -> FiniteMap key elt
listToFM l = l

-- Adding to 

addToFM :: Ord key => FiniteMap key elt -> key -> elt -> FiniteMap key elt
addToFM [] key elt = [(key,elt)]
addToFM ((k,e):xs) key elt = if (key == k) then 
                               (k,elt):xs
                             else
                               (k,e):(addToFM xs key elt)

addToFM_C :: Ord key => (elt -> elt -> elt) 
                  -> FiniteMap key elt -> key -> elt -> FiniteMap key elt

addToFM_C f [] key elt = [(key,elt)]
addToFM_C f ((k,e):xs) key elt = if key == k then 
                                   (k,f e elt):xs
                                 else
                                   (k,e):(addToFM_C f xs key elt)


delFromFM       :: Ord key => FiniteMap key elt -> key   -> FiniteMap key elt
delFromFM fm k = deleteBy (\(k',_) (k,_) -> k == k') (k,undefined) fm

delListFromFM   :: Ord key => FiniteMap key elt -> [key] -> FiniteMap key elt
delListFromFM fm ks = foldl delFromFM fm ks
-- Combining

	-- Bind right argument over left
plusFM :: Ord key => FiniteMap key elt -> FiniteMap key elt -> FiniteMap key elt
plusFM fm1 fm2 = foldr (\(k,e) fm -> addToFM fm k e) fm1 fm2
 
	-- Combines bindings for the same thing with given function
plusFM_C :: Ord key => (elt -> elt -> elt)
                 -> FiniteMap key elt -> FiniteMap key elt -> FiniteMap key elt
plusFM_C f fm1 fm2 = foldr (\(k,e) fm -> addToFM_C f fm k e) fm1 fm2

--Interrogating

sizeFM :: FiniteMap key elt -> Int
sizeFM fm = length fm

isEmptyFM :: FiniteMap key elt -> Bool
isEmptyFM [] = True
isEmptyFM (x:xs) = False

elemFM :: Ord key => key -> FiniteMap key elt -> Bool
elemFM key fm = key `elem` (keysFM fm)

lookupFM :: Ord key => FiniteMap key elt -> key -> Maybe elt
lookupFM [] key = Nothing
lookupFM ((k,e):fm) key = if key == k then 
                            Just e
                          else
                            lookupFM fm key



--Mapping,Folding,Filtering

foldFM :: (key -> elt -> a -> a) -> a -> FiniteMap key elt -> a
foldFM f k fm = foldr (\(x,y)-> f x y) k fm 

mapFM :: (key -> elt1 -> elt2) -> FiniteMap key elt1 -> FiniteMap key elt2
mapFM f fm = map (\(x,y) -> (x,f x y)) fm

filterFM :: (key -> elt -> Bool) -> FiniteMap key elt 
         -> FiniteMap key elt
filterFM f fm = filter (\(a,b) -> f a b) fm

-- Listifying
fmToList :: FiniteMap key elt -> [(key,elt)]
fmToList xs = xs

keysFM :: FiniteMap key elt -> [key]
keysFM fm = map fst fm

eltsFM :: FiniteMap key elt -> [elt]
eltsFM fm = map snd fm

intersectFM     :: Ord key => FiniteMap key elt -> FiniteMap key elt -> FiniteMap key elt 
intersectFM fm1 fm2 = filterFM (\k e -> k `elemFM` fm2) fm1

intersectFM_C   :: Ord key => (elt -> elt -> elt)
                           -> FiniteMap key elt -> FiniteMap key elt -> FiniteMap key elt 
intersectFM_C f fm1 fm2 = 
 foldFM (\k e fm -> case lookupFM fm2 k of
		     Nothing -> fm
		     Just e' -> addToFM fm k (f e e')) emptyFM fm1 

minusFM         :: Ord key => FiniteMap key elt -> FiniteMap key elt -> FiniteMap key elt
                   -- (minusFM a1 a2) deletes from a1 any bindings which are bound in a2
minusFM fm1 fm2 = 
 let ks = keysFM fm2
 in filterFM (\k _ -> not $ k `elem` ks) fm1 
