{-# LANGUAGE ViewPatterns, TupleSections, ScopedTypeVariables, DeriveDataTypeable, ForeignFunctionInterface, GADTs #-}

module Output.Names(writeNames, searchNames) where

import Data.List.Extra
import qualified Data.ByteString.Char8 as BS
import qualified Data.ByteString.Unsafe as BS
import qualified Data.Vector.Storable as V
import General.Str
import Foreign.Ptr
import Foreign.Marshal
import Foreign.C.String
import Foreign.C.Types
import Control.Exception
import System.IO.Unsafe
import Data.Maybe

import Input.Item
import General.Util
import General.Store

foreign import ccall text_search_bound :: CString -> IO CInt

foreign import ccall text_search :: CString -> Ptr CString -> CInt -> Ptr CInt -> IO CInt


data NamesSize a where NamesSize :: NamesSize Int deriving Typeable
data NamesItems a where NamesItems :: NamesItems (V.Vector TargetId) deriving Typeable
data NamesText a where NamesText :: NamesText BS.ByteString deriving Typeable

writeNames :: StoreWrite -> [(Maybe TargetId, Item)] -> IO ()
writeNames store xs = do
    let (ids, strs) = unzip [(i, [' ' | isUpper1 name] ++ lower name) | (Just i, x) <- xs, name <- itemNamePart x]
    let b = bstr0Join $ strs ++ ["",""]
    bound <- BS.unsafeUseAsCString b $ \ptr -> text_search_bound ptr
    storeWrite store NamesSize $ fromIntegral bound
    storeWrite store NamesItems $ V.fromList ids
    storeWrite store NamesText b

itemNamePart :: Item -> [String]
itemNamePart (IModule x) = [last $ splitOn "." $ strUnpack x]
itemNamePart x = maybeToList $ strUnpack <$> itemName x

searchNames :: StoreRead -> Bool -> [String] -> [TargetId]
-- very important to not search for [" "] or [] since the output buffer is too small
searchNames store exact (filter (/= "") . map trim -> xs) = unsafePerformIO $ do
    let vs = storeRead store NamesItems
    -- if there are no questions, we will match everything, which exceeds the result buffer
    if null xs then pure $ V.toList vs else do
        let tweak x = bstrPack $ [' ' | isUpper1 x] ++ lower x ++ "\0"
        bracket (mallocArray $ storeRead store NamesSize) free $ \result ->
            BS.unsafeUseAsCString (storeRead store NamesText) $ \haystack ->
                withs (map (BS.unsafeUseAsCString . tweak) xs) $ \needles ->
                    withArray0 nullPtr needles $ \needles -> do
                        found <- c_text_search haystack needles (if exact then 1 else 0) result
                        xs <- peekArray (fromIntegral found) result
                        pure $ map ((vs V.!) . fromIntegral) xs

{-# NOINLINE c_text_search #-} -- for profiling
c_text_search a b c d = text_search a b c d
