{-# LANGUAGE DeriveDataTypeable, MultiParamTypeClasses #-} 
-- You'll need these language extensions when defining a type you want to be in an IxSet


{-
  This file should be loaded into ghci for your personal experiments.
  Try looking at all the different values that start with "ex" and 
  hopefully you should get some definite intuition for using IxSet.
-}

import Happstack.Data.IxSet

-- We need to import Data.Map, Data.Set, and Data.Typeable in order to set up a type to work with IxSet  
import qualified Data.Map as Map
import Data.Map (Map)
import Data.Set (Set)
import Data.Typeable
import Data.Data

{- Indexing is done by dispatch over the actual type, so it isn't possible to
   have a working IxSet with multiple indexes of the same type.  The standard
   trick, then, is to use newtypes.
-}

newtype Id = Id Int
    deriving (Ord,Eq,Show,Typeable,Data)

newtype Val = Val Int
    deriving (Ord,Eq,Show,Typeable,Data)

newtype Name = Name String
    deriving (Ord,Eq,Show,Typeable,Data)

newtype Calc = Calc Int
    deriving (Ord,Eq,Data,Typeable)

{- Now we define the actual data type that we'll be storing in the IxSet 
-}

data Example1 = Example1 {
      uid :: Id,
      name :: Name,
      val :: Val,
      unindexed :: Int
      }
  deriving (Ord,Eq,Data,Typeable,Show)

{- In order to actually use the IxSet interface with a type,
   that type needs to be made an instance of Indexable.
   
   The actual instance is extremely straight forward, however,
   in that all we need is to make the list of Maps in empty
   contain an Ix (Map.empty :: Map a (Set Example)) for each type a
   that we want to index, which in this case means Id, Name, and Val.

   Now the calcs method is actually something quite interesting.  It
   provides a calculated index, not actually stored in the type,
   that you can use as an index just like the ones you define in the
   empty instance.  Notice that we need to include the 
-}

instance Indexable Example1 Calc where
    empty = IxSet [Ix (Map.empty :: Map Id (Set Example1)),
                   Ix (Map.empty :: Map Name (Set Example1)),
                   Ix (Map.empty :: Map Val (Set Example1)),
                   Ix (Map.empty :: Map Calc (Set Example1))]
    calcs e = let (Val v) = val e
                  u = unindexed e
              in Calc (u+v)

exEmpty :: IxSet Example1
exEmpty = empty

exInsert1 :: IxSet Example1
exInsert1 = insert (Example1 (Id 1) (Name "Foo") (Val 10) 0) empty

exInsert2 :: IxSet Example1
exInsert2 = insert (Example1 (Id 3) (Name "Bar") (Val 20) 3) $ 
            insert (Example1 (Id 2) (Name "Baz") (Val 10) 0) exInsert1

exDelete :: IxSet Example1
exDelete = delete (Example1 (Id 3) (Name "Bar") (Val 20) 3) exInsert2

-- There exist both fromList and fromSet functions for building
-- up IxSets from simpler containers.  fromList in particular is
-- convenient because of the syntax for lists.
exFromList :: IxSet Example1
exFromList = fromList [Example1 (Id 4) (Name "Blah") (Val 30) 5, 
                       Example1 (Id 5) (Name "Blah") (Val 40) 0, 
                       Example1 (Id 6) (Name "Blah") (Val 10) 0]

-- The (|||) operator is used for creating unions of IxSets.
-- Conversely the (&&&) operator is used for intersecting IxSets.
exUnion :: IxSet Example1
exUnion = exFromList ||| exInsert2

exQuery1 :: IxSet Example1
exQuery1 = exUnion @= Name "Blah"

exQuery2 :: IxSet Example1
exQuery2 = exUnion @< Val 40

exQuery3 :: IxSet Example1
exQuery3 = exUnion @>< ((Id 0),(Id 20))

-- the @+ function takes a list of indices and returns the IxSet that
-- matches one of the indices.  The @* function returns the IxSet that
-- matches all of the indices.  In this case, we pick out the examples
-- that have Id 1 or Id 6
exQuery4 :: IxSet Example1
exQuery4 = exUnion @+ [(Id 1),(Id 6)]

-- Again, we can treat the type Calc just as if it was actually contained
-- in Example1 for the purposes of queries and updates.
exCalcQuery :: IxSet Example1
exCalcQuery = exUnion @= Calc 35

{- The following query passes in a String to the query
   operator @=.  Since we have no index of type String,
   what do you expect to happen?  The behavior of the IxSet
   library in this case is going to be to return an empty IxSet.

   Something I think you should take away from this is that 
   the compiler can not check that you are providing
   a valid index to an IxSet.
-}
exBadQuery :: IxSet Example1
exBadQuery = exUnion @= "ham"

{- To modify a particular item in the ixset, you can use
   the updateIx function; however, it only works if the index is
   unique.  Otherwise, it'll only modify one of the items that
   matches the index.
-} 

exUpdateIx :: IxSet Example1
exUpdateIx = updateIx (Id 1) (Example1 (Id 1) (Name "Bar") (Val 20) 3) exUnion