    {-# 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 HackageURL = String

    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