module Helium.StaticAnalysis.Messages.Information where

import Top.Types
import Helium.Main.CompileUtils
import Helium.Parser.OperatorTable
import Helium.StaticAnalysis.Messages.Messages hiding (Constructor)
import Helium.Syntax.UHA_Syntax hiding (Fixity)
import Helium.Syntax.UHA_Utils
import Helium.Syntax.UHA_Range
import qualified Data.Map as M

type Fixity = (Int, Assoc)

data InfoItem
   = Function Name TpScheme (Maybe Fixity)
   | ValueConstructor Name TpScheme (Maybe Fixity)
   | TypeSynonym Name Int (Tps -> Tp) 
   | DataTypeConstructor Name Int [(Name, TpScheme)]
   | TypeClass String Class
   | NotDefined String

showInformation :: Bool -> [Option] -> ImportEnvironment -> IO ()
showInformation reportNotFound options importEnv =
   let items = concat [ makeInfoItem name | Information name <- options ]
   in showMessages items
 
 where
   makeInfoItem :: String -> [InfoItem]
   makeInfoItem string =
      let 
          notFound items = if null items && reportNotFound then [ NotDefined string ] else items
          
          function =
             case lookupWithKey (nameFromString string) (typeEnvironment importEnv) of
                Just (name, scheme) -> 
                   [Function name scheme (M.lookup name (operatorTable importEnv))]
                Nothing -> []
          
          constructor = 
             case lookupWithKey (nameFromString string) (valueConstructors importEnv) of
                Just (name, (_, scheme)) -> 
                   [ValueConstructor name scheme (M.lookup name (operatorTable importEnv))]
                Nothing     -> []

          synonyms = 
             case lookupWithKey (nameFromString string) (typeSynonyms importEnv) of
                Just (name, (i, f)) -> 
                   [TypeSynonym name i f]
                Nothing     -> [] 
      
          datatypeconstructor =
             case lookupWithKey (nameFromString string) (typeConstructors importEnv) of
                Just (name, (i,_)) | not (M.member name (typeSynonyms importEnv))  
                   -> [DataTypeConstructor name i (findValueConstructors name importEnv)]
                _  -> []
      
          typeclass = 
             case M.lookup string (classEnvironment importEnv) of
                Just cl -> [TypeClass string cl]
                Nothing -> []
      in 
         notFound (function ++ constructor ++ synonyms ++ datatypeconstructor ++ typeclass)

itemDescription :: InfoItem -> [String]
itemDescription infoItem =
   case infoItem of
   
      Function name ts _ -> 
         let tp = unqualify (unquantify ts)
             start | isOperatorName name = "operator"
                   | isFunctionType tp   = "function"
                   | otherwise           = "value"
         in [ "-- " ++ start ++ " " ++ show name ++ ", " ++ definedOrImported (getNameRange name) ]

      ValueConstructor name _ _ ->
         [ "-- value constructor " ++ show name ++ ", " ++ definedOrImported (getNameRange name) ]

      TypeSynonym name _ _ -> 
         [ "-- type synonym " ++ show name ++ ", " ++ definedOrImported (getNameRange name) ]

      DataTypeConstructor name _ _ ->
         [ "-- type constructor " ++ show name ++ ", " ++ definedOrImported (getNameRange name) ]
   
      TypeClass s _ ->
         [ " -- type class " ++ s ]
         
      NotDefined _ -> 
         [ ]

definedOrImported :: Range -> String
definedOrImported range
   | isImportRange range = "imported from " ++ show range
   | otherwise           = "defined at " ++ show range

showMaybeFixity :: Name -> Maybe Fixity -> MessageBlocks
showMaybeFixity name =
   let f (prio', associativity) = show associativity ++ " " ++ show prio' ++ " " ++ showNameAsOperator name
   in maybe [] ((:[]) . MessageString . f)

instance HasMessage InfoItem where 

   getMessage infoItem = 
      map (MessageOneLiner . MessageString) (itemDescription infoItem)
      ++
      case infoItem of
      
         Function name ts mFixity ->
            map MessageOneLiner
               ( MessageString (showNameAsVariable name ++ " :: " ++ show ts)
               : showMaybeFixity name mFixity
               )

         ValueConstructor name ts mFixity ->
            map MessageOneLiner
               ( MessageString (showNameAsVariable name ++ " :: " ++ show ts)
               : showMaybeFixity name mFixity
               )

         TypeSynonym name i f ->
            let tps  = take i [ TCon [c] | c <- ['a'..] ] 
                text = unwords ("type" : show name : map show tps ++ ["=", show (f tps)])
            in [ MessageOneLiner (MessageString text) ]

         DataTypeConstructor name i cons ->
            let tps     = take i [ TCon [c] | c <- ['a'..] ]
                text    = unwords ("data" : show name : map show tps)
                related = let f (name', ts) = "   " ++ showNameAsVariable name' ++ " :: " ++ show ts
                          in if null cons then [] else "   -- value constructors" : map f cons
            in map MessageOneLiner 
                  ( MessageString text
                  : map MessageString related
                  )

         TypeClass name (supers, theInstances) ->
            let f s     = s ++ " a"
                text    = "class " ++ showContextSimple (map f supers) ++ f name
                related = let ef (p, ps) = "   instance " ++ show (generalizeAll (ps .=>. p))
                          in if null theInstances then [] else "   -- instances" : map ef theInstances
            in map MessageOneLiner 
                  ( MessageString text
                  : map MessageString related
                  )
         
         NotDefined name ->
            map MessageOneLiner
               [ MessageString (show name ++ " not defined") ]

findValueConstructors :: Name -> ImportEnvironment -> [(Name, TpScheme)]
findValueConstructors name =
   let test = isName . fst . leftSpine . snd . functionSpine . unqualify . unquantify
       isName (TCon s) = s == show name
       isName _        = False
       toSchemeMap = M.map (\(_, scheme) -> scheme)
   in M.assocs . M.filter test . toSchemeMap . valueConstructors

lookupWithKey :: Ord key => key -> M.Map key a -> Maybe (key, a)
lookupWithKey key = M.lookup key . M.mapWithKey (,)