{-# LANGUAGE StandaloneDeriving #-}

-- ----------------------------------------------------------------------------
{- |
  Module     : Hunt.Query.Result
  Copyright  : Copyright (C) 2007 Timo B. Huebel
  License    : MIT

  Maintainer : Timo B. Huebel (tbh@holumbus.org)
  Stability  : experimental
  Portability: portable

  The data type for results of Holumbus queries.

  The result of a query is defined in terms of two partial results,
  the documents containing the search terms and the words which
  are possible completions of the search terms.
-}
-- ----------------------------------------------------------------------------

module Hunt.Query.Result
  (
  -- * Result data types
    Result(..)
  , DocHits
  , DocContextHits
  , DocWordHits
  , WordHits
  , WordContextHits
  , WordDocHits
  , DocInfo(..)
  , WordInfo(..)
  , WordInfoAndHits(..)
  , Score
  , Weight
  , Boost
  , DocBoosts

  -- * Construction
  , emptyResult

  -- * Query
  , null
  , sizeDocHits
  , sizeWordHits
  , maxScoreDocHits
  , maxScoreWordHits
  , getDocuments

  -- * Transform
  , setDocScore
  , setWordScore
  )
where

import           Prelude              hiding (null)

import qualified Data.List            as L
import           Data.Map             (Map)
import qualified Data.Map             as M
import           Data.Text            (Text)

import           Hunt.Common
import qualified Hunt.Common.DocIdMap as DM

-- ------------------------------------------------------------

-- | The combined result type for Holumbus queries.
data Result e
  = Result
    { docHits  :: DocHits e  -- ^ The documents matching the query.
    , wordHits :: WordHits   -- ^ The words which are completions of the query terms.
    }
deriving instance Show e => Show (Result e)


-- | Information about an document.
data DocInfo e
    = DocInfo
      { document :: e      -- ^ The document itself.
      , docBoost :: Weight -- ^ The document weight, init with @1.0@
      , docScore :: Score  -- ^ The score for the document (initial score for all documents is @0.0@).
      }

instance Show (DocInfo e) where
    show (DocInfo _d b s) = "DocInfo d " ++ show b ++ " " ++ show s

-- | Information about a word.
data WordInfo
    = WordInfo
      { terms     :: Terms -- ^ The search terms that led to this very word.
      , wordScore :: Score -- ^ The frequency of the word in the document for a context.
      }
    deriving (Eq, Show)

instance Monoid WordInfo where
    mempty
        = WordInfo [] noScore
    mappend (WordInfo t1 s1) (WordInfo t2 s2)
        = WordInfo (t1 `L.union` t2) (s1 <> s2)

data WordInfoAndHits
    = WIH WordInfo WordContextHits
      deriving (Eq, Show)

instance Monoid WordInfoAndHits where
    mempty
        = WIH mempty M.empty
    mappend (WIH wi1 wh1) (WIH wi2 wh2)
        = WIH (wi1 <> wi2) (M.unionWith (DM.unionWith (+)) wh1 wh2)

-- XXX: a list for now - maybe useful for testing
-- | Boosting of a single document.
type Boost           = Score

-- | Document boosting.
type DocBoosts       = DocIdMap Score

-- | A mapping from a document to it's score and the contexts where it was found.
type DocHits e       = DocIdMap (DocInfo e, DocContextHits)

-- | A mapping from a context to the words of the document that were found in this context.
type DocContextHits  = Map Context DocWordHits

-- | A mapping from a word of the document in a specific context to it's positions.
type DocWordHits     = Map Word Positions

-- | A mapping from a word to it's score and the contexts where it was found.
type WordHits        = Map Word WordInfoAndHits

-- | A mapping from a context to the documents that contain the word that were found in this context.
type WordContextHits = Map Context WordDocHits

-- | A mapping from a document containing the word to the positions of the word.
type WordDocHits     = DocBoosts

-- | The original search terms entered by the user.
type Terms           = [Text]

-- ------------------------------------------------------------

-- | Create an empty result.
emptyResult :: Result e
emptyResult = Result DM.empty M.empty

-- | Query the number of documents in a result.
sizeDocHits :: Result e -> Int
sizeDocHits = DM.size . docHits

-- | Query the number of documents in a result.
sizeWordHits :: Result e -> Int
sizeWordHits = M.size . wordHits

-- | Query the maximum score of the documents.
maxScoreDocHits :: Result e -> Score
maxScoreDocHits = DM.foldr (\(di, _) r -> max (docScore di) r) 0.0 . docHits

-- | Query the maximum score of the words.
maxScoreWordHits :: Result e -> Score
maxScoreWordHits = M.foldr (\ (WIH wi _) r -> max (wordScore wi) r) 0.0 . wordHits

-- | Test if the result contains anything.
null :: Result e -> Bool
null = DM.null . docHits

-- | Set the score in a document info.
setDocScore :: Score -> DocInfo e -> DocInfo e
setDocScore s di = di { docScore = s }

-- | Set the score in a word info.
setWordScore :: Score -> WordInfo -> WordInfo
setWordScore s wi = wi { wordScore = s }

-- | Extract all documents from a result.
getDocuments :: Result e -> [e]
getDocuments r = map (document . fst . snd) . DM.toList $ docHits r

-- ------------------------------------------------------------