{-# LANGUAGE DeriveDataTypeable, RecordWildCards #-}

module Hoogle.Type.Item where

import General.Base
import General.Util
import Hoogle.Store.All
import Hoogle.Type.Docs
import Hoogle.Type.TagStr
import Hoogle.Type.TypeSig
import Data.Generics.Uniplate


type Input = ([Fact], [TextItem])

data ItemKind = PackageItem
              | ModuleItem
              | FunctionItem
              | DataCtorItem
              | TypeCtorItem
              | TypeSynonymItem
              | ClassItem
              | InstanceItem
              | UnclassifiedItem
              deriving (Data,Typeable,Show,Eq,Enum)

instance NFData ItemKind where
    rnf = rnf . fromEnum

data TextItem = TextItem
    {itemLevel :: Int -- 0 = package, 1 = module, >2 = entry
    ,itemKind :: ItemKind
    ,itemKey :: String -- how i should be searched for (name for most things, last module component for modules)
    ,itemName :: String -- what is the full text representation of me (key for most things, A.B.C for modules)
    ,itemType :: Maybe TypeSig
    ,itemDisp :: TagStr -- TagColor 0 for result type, TagColor 1.. for arg types, TagBold for name
    ,itemURL :: URL
    ,itemDocs :: String
    ,itemPriority :: Int -- priority, 0 is highest priority
    }
    deriving Show

data Fact
    = FactAlias TypeSig TypeSig
    | FactInstance TypeSig
    | FactDataKind String Int
    | FactClassKind String Int
    | FactCtorType String String -- Ctor, Data
      deriving Show


-- Invariant: locations will not be empty
data Entry = Entry
    {entryLocations :: [(URL, [Once Entry])]
    ,entryKind :: ItemKind
    ,entryLevel :: Int
    ,entryName :: String
    ,entryText :: TagStr
    ,entryDocs :: Docs
    ,entryPriority :: Int
    ,entryKey :: String -- used only for rebuilding combined databases
    ,entryType :: Maybe TypeSig -- used only for rebuilding combined databases
    }
    deriving (Eq, Typeable)

instance NFData Entry where
    rnf ent@(Entry a b c d e f g h i) = rnf (map (second $ map (f . fromOnce)) a,b,c,d,e,f,g,h,i)
        where f ent2 = if entryUnique ent == entryUnique ent2 then () else rnf ent2


-- | Figure out what makes this entry different from others
entryUnique Entry{..} = (entryName, entryText, entryDocs, entryKey, entryType)


-- | Join two entries that are equal under entryUnique
entryJoin e1 e2 = e1
    {entryPriority = min (entryPriority e1) (entryPriority e2)
    ,entryLocations = nubOn (map (entryName . fromOnce) . snd) $ concatMap entryLocations $
        if entryScore e1 < entryScore e2 then [e1,e2] else [e2,e1]}


entryURL e = head $ map fst (entryLocations e) ++ [""]


data EntryView = FocusOn String -- characters in the range should be focused
               | ArgPosNum Int Int -- argument a b, a is remapped to b
                 deriving (Eq, Show)


renderEntryText :: [EntryView] -> TagStr -> TagStr
renderEntryText view = transform f
    where
        cols = [(b+1,a+1) | ArgPosNum a b <- view]
        strs = [map toLower x | FocusOn x <- view]

        f (TagColor i x) = maybe x (`TagColor` x) $ lookup i $ [(0,0)|cols/=[]] ++ cols
        f (TagBold (Str xs)) = TagBold $ Tags $ g xs
        f x = x

        g xs | ss /= [] = TagEmph (Str a) : g b
            where ss = filter (`isPrefixOf` map toLower xs) strs
                  (a,b) = splitAt (maximum $ map length ss) xs
        g (x:xs) = Str [x] : g xs
        g [] = []


-- TODO: EntryScore is over-prescriptive, and not overly useful
--       Have name and type scores to it themselves, using name only
--       to break ties when merging
-- the entry priority
-- the name of the entry, in lower case
-- the name of the entry
data EntryScore = EntryScore Int String String
                  deriving (Eq,Ord)


entryScore :: Entry -> EntryScore
entryScore e = EntryScore (entryPriority e) (map toLower $ entryName e) (entryName e)

instance Show Entry where
    show = showTagText . entryText

instance Store Entry where
    put (Entry a b c d e f g h i) = put9 a b c d e f g h i
    get = get9 Entry


instance Store Fact where
    put (FactAlias x y)     = putByte 0 >> put2 x y
    put (FactInstance x)    = putByte 1 >> put1 x
    put (FactDataKind x y)  = putByte 2 >> put2 x y
    put (FactClassKind x y) = putByte 3 >> put2 x y
    put (FactCtorType x y)  = putByte 4 >> put2 x y

    get = do i <- getByte
             case i of
                0 -> get2 FactAlias
                1 -> get1 FactInstance
                2 -> get2 FactDataKind
                3 -> get2 FactClassKind
                4 -> get2 FactCtorType

instance Store ItemKind where
    put PackageItem      = putByte 0
    put ModuleItem       = putByte 1
    put FunctionItem     = putByte 2
    put DataCtorItem     = putByte 4
    put TypeCtorItem     = putByte 5
    put TypeSynonymItem  = putByte 6
    put ClassItem        = putByte 7
    put InstanceItem     = putByte 8
    put UnclassifiedItem = putByte 9

    get = do i <- getByte
             case i of
                0 -> get0 PackageItem
                1 -> get0 ModuleItem
                2 -> get0 FunctionItem
                3 -> get0 FunctionItem
                4 -> get0 DataCtorItem
                5 -> get0 TypeCtorItem
                6 -> get0 TypeSynonymItem
                7 -> get0 ClassItem
                8 -> get0 InstanceItem
                9 -> get0 UnclassifiedItem