content: -- ----------------------------------------------------------------------------
  
  {- |
    Module     : Holumbus.Query.Intermediate
    Copyright  : Copyright (C) 2007, 2008 Timo B. Huebel
    License    : MIT
  
    Maintainer : Timo B. Huebel (tbh@holumbus.org)
    Stability  : experimental
    Portability: portable
    Version    : 0.3
  
    The data type for intermediate results occuring during query processing.
  
  -}
  
  -- ----------------------------------------------------------------------------
  
  {-# OPTIONS #-}
  
  module Holumbus.Query.Intermediate 
      (
      -- * The intermediate result type.
         Intermediate 
  
      -- * Construction
      , emptyIntermediate
  
      -- * Query
      , null
      , sizeIntermediate
  
      -- * Combine
      , union
      , difference
      , intersection  
      , unions
    
      -- * Conversion
      , fromList
      , toResult
      )
  where
  
  import qualified Data.List                as L
  import           Data.Map                 ( Map )
  import qualified Data.Map                 as M
  import           Data.Maybe
  
  import           Holumbus.Query.Result    hiding ( null )
  import           Holumbus.Index.Common    hiding ( toList
                                                   , fromList
                                                   )
  import           Prelude                  hiding ( null )
  
  -- ----------------------------------------------------------------------------
  
  -- | The intermediate result used during query processing.
  
  type Intermediate               = DocIdMap    IntermediateContexts
  type IntermediateContexts       = Map Context IntermediateWords
  type IntermediateWords          = Map Word    (WordInfo, Positions)
  
  -- ----------------------------------------------------------------------------
  
  -- | Create an empty intermediate result.
  
  emptyIntermediate               :: Intermediate
  emptyIntermediate               = emptyDocIdMap
  
  -- | Check if the intermediate result is empty.
  
  null                            :: Intermediate -> Bool
  null                            = nullDocIdMap
  
  -- | Returns the number of documents in the intermediate result.
  
  sizeIntermediate                :: Intermediate -> Int
  sizeIntermediate                = sizeDocIdMap
  
  -- | Merges a bunch of intermediate results into one intermediate result by unioning them.
  
  unions                          :: [Intermediate] -> Intermediate
  unions                          = L.foldl' union emptyIntermediate
  
  -- | Intersect two sets of intermediate results.
  
  intersection                    :: Intermediate -> Intermediate -> Intermediate
  intersection                    = intersectionWithDocIdMap combineContexts
  
  -- | Union two sets of intermediate results.
  
  union                           :: Intermediate -> Intermediate -> Intermediate
  union                           = unionWithDocIdMap combineContexts
  
  -- | Substract two sets of intermediate results.
  
  difference                      :: Intermediate -> Intermediate -> Intermediate
  difference                      = differenceDocIdMap
  
  -- | Create an intermediate result from a list of words and their occurrences.
  
  fromList                        :: Word -> Context -> RawResult -> Intermediate
  fromList t                      = genResultByDocument $
                                    \ p -> (WordInfo [t] 0.0, p)
  {-
  -- Beware! This is extremly optimized and will not work for merging arbitrary intermediate results!
  -- Based on resultByDocument from Holumbus.Index.Common.RawResult
  --
  -- and now also implemented by resultByDocument (uwe)
  
  fromList t c os                 = mapDocIdMap transform $
                                    unionsWithDocIdMap (flip $ (:) . head)
                                                       (map insertWords os)
    where
    insertWords (w, o)            = mapDocIdMap (\p -> [(w, (WordInfo [t] 0.0 , p))]) o   
    transform w                   = M.singleton c (M.fromList w)
  -- -}
  
  -- | Convert to a @Result@ by generating the 'WordHits' structure.
  
  toResult                        :: HolDocuments d c => d c -> Intermediate -> Result c
  toResult d im                   = Result (createDocHits d im) (createWordHits im)
  
  -- | Create the doc hits structure from an intermediate result.
  
  createDocHits                   :: HolDocuments d c => d c -> Intermediate -> DocHits c
  createDocHits d im              = mapWithKeyDocIdMap transformDocs im
      where
        transformDocs did ic      = let doc = fromMaybe (Document "" "" Nothing) (lookupById d did) in
                                    (DocInfo doc 0.0, M.map (M.map (\(_, p) -> p)) ic)
  
  -- | Create the word hits structure from an intermediate result.
  
  createWordHits :: Intermediate -> WordHits
  createWordHits im               = foldWithKeyDocIdMap transformDoc M.empty im
      where
        transformDoc d ic wh      = M.foldrWithKey transformContext wh ic
            where
              transformContext c iw wh'
                                  = M.foldrWithKey insertWord wh' iw
                  where
                    insertWord w (wi, pos) wh''
                                  = if terms wi == [""]
                                    then wh'' 
                                    else M.insertWith combineWordHits
                                         w
                                         (wi, M.singleton c (singletonDocIdMap d pos))
                                         wh''
  
  -- | Combine two tuples with score and context hits.
  
  combineWordHits                 :: (WordInfo, WordContextHits) ->
                                     (WordInfo, WordContextHits) -> (WordInfo, WordContextHits)
  combineWordHits (i1, c1) (i2, c2)
                                  = ( combineWordInfo i1 i2
                                    , M.unionWith (unionWithDocIdMap unionPos) c1 c2
                                    )
  
  -- | Combine two tuples with score and context hits.
  
  combineContexts                 :: IntermediateContexts -> IntermediateContexts -> IntermediateContexts
  combineContexts                 = M.unionWith (M.unionWith merge)
      where
        merge (i1, p1) (i2, p2)   = ( combineWordInfo i1 i2
                                    , unionPos p1 p2
                                    )
  
  -- | Combine two word informations.
  
  combineWordInfo                 :: WordInfo -> WordInfo -> WordInfo
  combineWordInfo (WordInfo t1 s1) (WordInfo t2 s2)
                                  = WordInfo (t1 ++ t2) (combineScore s1 s2)
  
  -- | Combine two scores (just average between them).
  
  combineScore                    :: Score -> Score -> Score
  combineScore s1 s2              = (s1 + s2) / 2.0
  
  -- ----------------------------------------------------------------------------
  