{-# OPTIONS_GHC -fno-warn-orphans #-}
{-| Module      :  HeuristicsInfo
    License     :  GPL

    Maintainer  :  helium@cs.uu.nl
    Stability   :  experimental
    Portability :  portable
    
    Contains instance declarations. he type graph heuristics can be deployed 
	using the additional information that is stored by the Helium compiler for 
	each type constraint
-}

module Helium.StaticAnalysis.Heuristics.HeuristicsInfo where

import Helium.StaticAnalysis.Miscellaneous.ConstraintInfo
import Helium.StaticAnalysis.Heuristics.RepairHeuristics
import Helium.StaticAnalysis.Heuristics.TieBreakerHeuristics
import Helium.StaticAnalysis.Heuristics.OnlyResultHeuristics
import Helium.StaticAnalysis.Heuristics.UnifierHeuristics
import Helium.StaticAnalysis.Miscellaneous.DoublyLinkedTree
import Helium.Utils.OneLiner
import Helium.StaticAnalysis.Miscellaneous.UHA_Source
import Helium.Syntax.UHA_Syntax
import Helium.StaticAnalysis.Messages.Messages
import Helium.StaticAnalysis.Messages.HeliumMessages ()
import Helium.StaticAnalysis.Messages.TypeErrors
import Helium.Utils.Utils (internalError)
import Top.Types
import Top.Implementation.TypeGraph.Heuristic
import Data.Maybe
import Data.Char
import qualified Data.Map as M

instance HasTrustFactor ConstraintInfo where
   trustFactor cinfo =
      let ntFactor = case (self . attribute . localInfo) cinfo of
                        UHA_Pat  _ -> 3.0
                        UHA_Decl _ -> 3.0
                        UHA_FB   _ -> 3.0
                        _          -> 1.0
      in product (ntFactor : [ factor | HasTrustFactor factor <- properties cinfo ])

instance HasDirection ConstraintInfo where
   isTopDown cinfo = or [ True | FolkloreConstraint <- properties cinfo ]

instance MaybeImported ConstraintInfo where
   maybeImportedName cinfo = 
      case [ name | IsImported name <- properties cinfo ] of
         []  -> Nothing
         n:_ -> Just (show n)

instance HasTwoTypes ConstraintInfo where
   getTwoTypes = typepair

instance MaybeLiteral ConstraintInfo where
   maybeLiteral cinfo = 
      let literalType x = 
             case x of 
                Literal_Int    _ _ -> "Int"
                Literal_Char   _ _ -> "Char"
                Literal_String _ _ -> "String"
                Literal_Float  _ _ -> "Float"
      in case (self . attribute . localInfo) cinfo of
            UHA_Expr (Expression_Literal _ literal ) -> Just (literalType literal)
            UHA_Pat  (Pattern_Literal    _ literal ) -> Just (literalType literal)
            _                                        -> Nothing

instance IsPattern ConstraintInfo where
   isPattern cinfo = 
      case (self . attribute . localInfo) cinfo of 
         UHA_Pat _ -> True
         _         -> False

instance MaybeApplication ConstraintInfo where
   maybeNumberOfArguments = 
      fmap (length . snd) . maybeApplicationEdge
      
   maybeApplicationEdge cinfo = 
      let list = [ (b, zip (map self infoTrees) (map fromJust tps))
                 | ApplicationEdge b infoTrees <- properties cinfo
                 , let tps = map assignedType infoTrees
                 , all isJust tps
                 ]
      in case list of 
            []      -> Nothing
            tuple:_ -> Just tuple

instance MaybeUnaryMinus ConstraintInfo where
   maybeUnaryMinus cinfo = 
      case (self . attribute . localInfo) cinfo of
         UHA_Expr (Expression_InfixApplication _
              (MaybeExpression_Just _)
              (Expression_Variable _ name)
              (MaybeExpression_Just (Expression_Literal _ literal)))
            | show name == "-" ->
                 case literal of
                    Literal_Int _ s -> Just (Left (read s))
                    _               -> Nothing
            | show name == "-." ->
                 case literal of
                    Literal_Float _ s -> Just (Right (read s))
                    _                 -> Nothing 
         _  -> Nothing
   
instance MaybeNegation ConstraintInfo where
   maybeNegation cinfo = 
      case (self . attribute . localInfo) cinfo of
         UHA_Expr (Expression_Negate      _ _) -> Just True
         UHA_Expr (Expression_NegateFloat _ _) -> Just False
         _                                     -> Nothing

instance IsExprVariable ConstraintInfo where -- misleading name?
   isExprVariable cinfo =
      case (self . attribute . localInfo) cinfo of
         UHA_Expr (Expression_Variable _ _) -> 
            not $ null [ () | InstantiatedTypeScheme _ <- properties cinfo ]
         _ -> False
      
   isEmptyInfixApplication cinfo =
      case (self . attribute . localInfo) cinfo of
         UHA_Expr (Expression_InfixApplication _ MaybeExpression_Nothing _ MaybeExpression_Nothing) -> True
         _  -> False

instance IsFunctionBinding ConstraintInfo where
   isExplicitlyTyped cinfo = 
      or [ True | ExplicitTypedBinding <- properties cinfo ]
      
   maybeFunctionBinding cinfo = 
      case [ t | FuntionBindingEdge t <- properties cinfo ] of
         []  -> Nothing
         t:_ -> Just t 
   
instance IsTupleEdge ConstraintInfo where
   isTupleEdge cinfo = 
     case (self . attribute . localInfo) cinfo of
         UHA_Expr (Expression_Tuple _ _) -> True
         UHA_Pat  (Pattern_Tuple _ _)    -> True
         _                               -> False
      
instance WithHints ConstraintInfo where
  addHint descr str = addProperty (WithHint (descr, MessageString str))
  typeErrorForTerm  = specialApplicationTypeError

instance IsUnifier ConstraintInfo where
   typeErrorForUnifier = specialUnifierTypeError
   isUnifier cinfo = 
      case [ (u, t) | Unifier u t <- properties cinfo ] of
         []  -> Nothing
         t:_ -> Just t

makeUnifier :: Name -> String -> M.Map Name Tp -> InfoTree -> Property
makeUnifier name location' environment infoTree = 
   let unifier = maybe (-1) (head . ftv) (M.lookup name environment)
       tuple   = ("variable of "++location', attribute (findVariableInPat name infoTree), "variable")
   in Unifier unifier tuple
 
specialApplicationTypeError :: (Bool,Bool) -> Int -> OneLineTree -> (Tp,Tp) -> Range -> ConstraintInfo -> ConstraintInfo
specialApplicationTypeError (isInfixApplication,isPatternApplication) argumentNumber termOneLiner (t1, t2) range cinfo =
   let typeError = TypeError [range] [oneLiner] table []
       oneLiner  = MessageOneLiner (MessageString ("Type error in " ++ location cinfo))
       table     = [ description1     <:> MessageOneLineTree (oneLinerSource source1)
                   , description2     <:> MessageOneLineTree (oneLinerSource source2)
                   , "type"           >:> MessageType functionType
                   , description3     <:> MessageOneLineTree termOneLiner
                   , "type"           >:> MessageType (toTpScheme t1)
                   , "does not match" >:> MessageType (toTpScheme t2)
                   ]
       (description1, source1, source2) =
          case convertSources (sources cinfo) of
             [(d1,s1), (_, s2)] -> (d1, s1, s2)
             _ -> internalError "ConstraintInfo" "specialApplicationTypeError" "expected two elements in list"
       description2 
          | isPatternApplication   = "constructor"
          | not isInfixApplication = "function"
          | otherwise =  
               case show (MessageOneLineTree (oneLinerSource source2)) of
                  c:_ | isLower c -> "function"
                      | isUpper c -> "constructor"
                  _               -> "operator"
       functionType = toTpScheme (fst (typepair cinfo))
       description3
          | isInfixApplication = if argumentNumber == 0 then "left operand" else "right operand"
          | otherwise          = ordinal False (argumentNumber + 1) ++ " argument"
   in setTypeError typeError (setTypePair (t1, t2) cinfo)
   
specialUnifierTypeError ::  (Tp, Tp) -> (ConstraintInfo, ConstraintInfo) -> ConstraintInfo
specialUnifierTypeError (t1, t2) (info1, info2) =
   let typeError = TypeError [range] [oneLiner] table hints
       range     = rangeOfSource source 
       oneLiner  = MessageOneLiner (MessageString ("Type error in " ++ loc1))
       table     = [ description <:> maybeAddLocation source
                   , descr1      <:> source1
                   , "type"      >:> MessageType (toTpScheme t1)
                   , descr2      <:> source2
                   , "type"      >:> MessageType (toTpScheme t2)
                   ]
       description = descriptionOfSource source
       (loc1, localInfo', descr1) = snd (fromJust (isUnifier info1))
       (_   ,_         , descr2) = snd (fromJust (isUnifier info2))
       source = self localInfo'
       (source1, source2) = 
          let f (src, msrc) = maybeAddLocation (fromMaybe src msrc)
          in (f (sources info1), f (sources info2))
       hints = [] -- [("because", MessageString "these two types cannot be unified")]
   in setTypeError typeError (setTypePair (t1,t2 ) info1)

skip_UHA_FB_RHS :: InfoTree -> InfoTree
skip_UHA_FB_RHS tree = 
   case self (attribute tree) of
      UHA_FB _  -> maybe tree skip_UHA_FB_RHS (parent tree) 
      UHA_RHS _ -> maybe tree skip_UHA_FB_RHS (parent tree)
      _        -> tree
   
findVariableInPat :: Name -> InfoTree -> InfoTree
findVariableInPat name tree = 
   case children tree of
      [] -> tree
      cs -> let p x = case self (attribute x) of
                         UHA_Pat pat -> hasVariable name pat
                         _ -> False
            in case filter p cs of
                  [] -> tree
                  child:_ -> findVariableInPat name child

hasVariable :: Name -> Pattern -> Bool
hasVariable name pattern =
   case pattern of
      Pattern_Variable _ n -> name == n
      Pattern_As _ n pat   -> name == n || hasVariable name pat
      Pattern_Parenthesized _ pat -> hasVariable name pat
      Pattern_InfixConstructor _ pat1 _ pat2 -> hasVariable name pat1 || hasVariable name pat2
      Pattern_Constructor _ _ pats -> any (hasVariable name) pats
      Pattern_List _ pats -> any (hasVariable name) pats
      Pattern_Tuple _ pats -> any (hasVariable name) pats
      _ -> False

maybeAddLocation :: UHA_Source -> MessageBlock
maybeAddLocation src
   | match = 
        MessageCompose 
           [ MessageOneLineTree (oneLinerSource src)
           , MessageString " at "
           , MessageRange (rangeOfSource src)
           ]
   | otherwise =  
        MessageOneLineTree (oneLinerSource src)

 where match =
          case src of
             UHA_Expr (Expression_Variable _ _) -> True
             UHA_Pat  (Pattern_Variable _ _)    -> True
             _ -> False
