-- | This is the Haskell version of the Sphinx searchd client.

module Text.Search.Sphinx ( Configuration (..)
                          , query
                          , defaultConfig
                          ) where

import Network
import IO hiding (bracket)
import System
import Control.Exception
import Data.Binary.Get
import Data.Binary.Put (runPut, Put)
import Data.ByteString.Lazy hiding (pack, length, map, groupBy, head)
import Data.ByteString.Lazy.Char8 (pack)
import qualified Data.ByteString.Lazy as BS
import Data.Char (ord, chr)
import Data.Int (Int64)
import Prelude hiding (readList)
import Text.Search.Sphinx.Get
import Text.Search.Sphinx.Put
import Text.Search.Sphinx.Configuration
import Text.Search.Sphinx.Types (SearchResult)
import qualified Text.Search.Sphinx.Types as T

type Connection = (Handle, Configuration)

connect :: Configuration -> IO Connection
connect cfg = do connection <- connectTo (host cfg) (PortNumber $ fromIntegral $ port cfg)
                 bs         <- hGet connection 4
                 let version   = runGet getWord32be bs
                     myVersion = runPut (num 1)
                 hPut connection myVersion
                 return (connection, cfg)

addQuery :: Configuration -> String -> String -> String -> Put
addQuery cfg query index comment = do
    nums cfg [ offset
             , limit
             , T.matchMode . mode
             , T.rank      . ranker
             , T.sort      . sort]
    str (sortBy cfg)
    str query
    numList (weights cfg)
    str index
    num 1
    num64s cfg [minId, maxId]
    num 0 -- todo: pack len filters + filters
    enum (groupByFunc   cfg)
    str  (groupBy       cfg)
    num  (maxMatches    cfg)
    str  (groupSort     cfg)
    num  (cutoff        cfg)
    num  (retryCount    cfg)
    num  (retryDelay    cfg)
    str  (groupDistinct cfg)
    num 0 -- anchor point: todo
    stringIntList (indexWeights cfg)
    num (maxQueryTime cfg)
    stringIntList (fieldWeights cfg)
    str comment

-- | The 'query' function queries the Sphinx daemon.
query :: Configuration -- ^ The configuration
      -> String        -- ^ The indexes, "*" means every index
      -> String        -- ^ The query string
      -> IO SearchResult
query config indexes s = do
    conn <- connect config
    let q = addQuery config s indexes ""
    results <- runQueries (fst conn) q 1
    return $ head results -- We only do one query, so we always have one SearchResult

runQueries :: Handle -> Put -> Int -> IO [SearchResult]
runQueries conn q numQueries = do
    let req = runPut (makeRunQuery q numQueries)
    hPut conn req
    hFlush conn
    getResponse conn numQueries

makeRunQuery query numQueries =  do
  cmd T.ScSearch
  verCmd T.VcSearch
  num $ fromEnum $ BS.length (runPut query) + 4
  num numQueries

getResponse conn numResults = do
  header <- hGet conn 8
  let x@(status, version, len) = readHeader header
  response <- hGet conn (fromIntegral len)
  return $ runGet (numResults `times` getResult) response