module ImmutableDict =
   open System.Collections.Generic
   open System.Collections.Immutable

   /// The empty dictionary.
   let empty<'Key,'T> = 
      ImmutableDictionary<'Key,'T>.Empty :> IImmutableDictionary<'Key,'T>

   /// Tests whether the collection has any elements.
   let isEmpty (table:IImmutableDictionary<'Key,'T>) =
      table.Count = 0

   /// Returns a new dictionary with the binding added to the given dictionary.
   let add key value (table:IImmutableDictionary<'Key,'T>) = 
      table.Add(key,value)
   
   /// Removes an element from the collection.
   let remove key (table:IImmutableDictionary<'Key,'T>) = 
      table.Remove(key)
      
   /// Tests if an element is in collection.
   let containsKey key (table:IImmutableDictionary<'Key,'T>) = 
      table.ContainsKey(key)
   
   /// Looks up an element in the collection.
   let find key (table:IImmutableDictionary<'Key,'T>) = 
      match table.TryGetKey(key) with
      | true, value -> value
      | false, _ -> raise (KeyNotFoundException())
   
   /// Looks up an element in the collection, returning 
   /// a Some value if the element is in the domain of the map, 
   /// or None if not.
   let tryFind key (table:IImmutableDictionary<'Key,'T>) =
      match table.TryGetKey(key) with
      | true, value -> Some value
      | false, _ -> None

   /// Returns an array of all key/value pairs in the mapping. 
   let toArray (table:IImmutableDictionary<'Key,'T>) =
      [|for pair in table -> pair.Key, pair.Value|]

   /// Returns a new collection made from the given bindings.
   let ofSeq (elements:('Key * 'T) seq) =
      seq { for (k,v) in elements -> KeyValuePair(k,v) }
      |> empty.AddRange
     
   /// Creates a new collection whose elements are the results of applying 
   /// the given function to each of the elements of the collection. 
   let map (mapping:'Key -> 'T -> 'U) (table:IImmutableDictionary<'Key,'T>) =
      seq { for pair in table -> KeyValuePair<'Key,'U>(pair.Key, mapping pair.Key pair.Value) }
      |> empty.AddRange
   
   /// Creates a new collection containing only the bindings for which the given predicate returns true.
   let filter (predicate:'Key -> 'T -> bool) (table:IImmutableDictionary<'Key,'T>) =
      seq { for pair in table do if predicate pair.Key pair.Value then yield pair }
      |> empty.AddRange

   /// Returns true if the given predicate returns true for one of the bindings in the collection.
   let exists (predicate:'Key -> 'T -> bool) (table:IImmutableDictionary<'Key,'T>) =
      table |> Seq.exists (fun pair -> predicate pair.Key pair.Value)

   /// Returns true if the given predicate returns true for all of the bindings in the collection.
   let forall (predicate:'Key -> 'T -> bool) (table:IImmutableDictionary<'Key,'T>) =
      table |> Seq.forall (fun pair -> predicate pair.Key pair.Value)

   /// Evaluates the function on each mapping in the collection. 
   /// Returns the key for the first mapping where the function returns true.
   let findKey (predicate:'Key -> 'T -> bool) (table:IImmutableDictionary<'Key,'T>) =
      table |> Seq.find (fun pair -> predicate pair.Key pair.Value) |> fun pair -> pair.Key

   /// Evaluates the function on each mapping in the collection. 
   /// Returns the key for the first mapping where the function returns true.
   let pick (chooser:'Key -> 'T -> 'U option) (table:IImmutableDictionary<'Key,'T>) =
      table |> Seq.pick (fun pair -> chooser pair.Key pair.Value)

   /// Searches the collection looking for the first element where the given function 
   /// returns a Some value.
   let tryPick (chooser:'Key -> 'T -> 'U option) (table:IImmutableDictionary<'Key,'T>) =
      table |> Seq.tryPick (fun pair -> chooser pair.Key pair.Value)

   /// Applies the given function to each binding in the collection
   let iter (action:'Key -> 'T -> unit) (table:IImmutableDictionary<'Key,'T>) =
      table |> Seq.iter (fun pair -> action pair.Key pair.Value)
  
   /// Folds over the bindings in the collection
   let fold (folder:'State -> 'Key -> 'T -> 'State) (state:'State) (table:IImmutableDictionary<'Key,'T>) =
      table |> Seq.fold (fun acc pair -> folder acc pair.Key pair.Value) state