{-| Module      :  Messages
    License     :  GPL

    Maintainer  :  helium@cs.uu.nl
    Stability   :  experimental
    Portability :  portable

    Datatype to represent error messages. One abstraction is the datatype
    MessageBlock, which contains (atomic) pieces of information that are
    reported in the error messages such as types, ranges and code fragments.
-}

module Helium.StaticAnalysis.Messages.Messages where

import Helium.Syntax.UHA_Syntax
import Helium.Syntax.UHA_Range
import Helium.Syntax.UHA_Utils ()
import Top.Types 

import Helium.Utils.OneLiner
import Helium.Utils.Similarity (similar)
import Helium.Utils.Utils      (internalError)
import Data.List       (sortBy, partition)
import Data.Char       (toUpper)
import Data.Function

type Message       = [MessageLine] 

data MessageLine   = MessageOneLiner  MessageBlock
                   | MessageTable     [(Bool, MessageBlock, MessageBlock)]  -- Bool: indented or not
                   | MessageHints     String MessageBlocks

type MessageBlocks = [MessageBlock]               
data MessageBlock  = MessageString       String
                   | MessageRange        Range
                   | MessageType         TpScheme
                   | MessagePredicate    Predicate                                      
                   | MessageOneLineTree  OneLineTree
                   | MessageCompose      MessageBlocks                   

class HasMessage a where
   getRanges            :: a -> [Range]
   getMessage           :: a -> Message  
   
   -- default definitions
   getRanges            _ = []

instance (HasMessage a, HasMessage b) => HasMessage (Either a b) where
   getRanges  = either getRanges  getRanges
   getMessage = either getMessage getMessage

instance Substitutable MessageLine where

   sub |-> ml = case ml of   
                   MessageOneLiner mb -> MessageOneLiner (sub |-> mb)
                   MessageTable table -> MessageTable [ (b, sub |-> mb1, sub |-> mb2) | (b, mb1, mb2) <- table ]
                   MessageHints s mbs -> MessageHints s (sub |-> mbs)

   ftv ml = case ml of
               MessageOneLiner mb -> ftv mb
               MessageTable table -> ftv [ [mb1, mb2] | (_, mb1, mb2) <- table ]
               MessageHints _ mbs -> ftv mbs
                                       
instance Substitutable MessageBlock where

   sub |-> mb = case mb of
                   MessageType tp       -> MessageType (sub |-> tp)
                   MessagePredicate p   -> MessagePredicate (sub |-> p)
                   MessageCompose mbs   -> MessageCompose (sub |-> mbs) 
                   _                    -> mb

   ftv mb = case mb of         
               MessageType tp       -> ftv tp
               MessagePredicate p   -> ftv p           
               MessageCompose mbs   -> ftv mbs
               _                    -> []       

-------------------------------------------------------------
-- Smart row constructors for tables

infixl 1 <:>, >:>    -- very low priority

-- do not indent
(<:>) :: String -> MessageBlock -> (Bool, MessageBlock, MessageBlock)
s <:> mb = (False, MessageString s, mb)

-- indented row
(>:>) :: String -> MessageBlock -> (Bool, MessageBlock, MessageBlock)
s >:> mb = (True, MessageString s, mb)

-------------------------------------------------------------
-- Misc

data Entity = TypeSignature
            | TypeVariable
            | TypeConstructor
            | Definition
            | Constructor
            | Variable
            | Import
            | ExportVariable
            | ExportModule
            | ExportConstructor
            | ExportTypeConstructor
            | Fixity
    deriving Eq

sortMessages :: HasMessage a => [a] -> [a]
sortMessages = let f x y = compare (getRanges x) (getRanges y)
               in sortBy f
               
sortNamesByRange :: Names -> Names
sortNamesByRange names =
   let tupleList = [ (name, getNameRange name) | name <- names ]
       (xs,ys)   = partition (isImportRange . snd) tupleList
   in map fst (sortBy (compare `on` snd ) ys ++ xs)

-- The first argument indicates whether numbers up to ten should be
-- printed "verbose"
ordinal :: Bool -> Int -> String
ordinal b i
    | i >= 1 && i <= 10 && b = table !! (i - 1)
    | i >= 0                 = show i ++ extension i
    | otherwise              = internalError "Messages.hs"
                                             "ordinal"
                                             "can't show numbers smaller than 0"
    where
        table =
            [ "first", "second", "third", "fourth", "fifth", "sixth","seventh"
            , "eighth", "ninth", "tenth"
            ]
        extension j
            | j > 3 && i < 20 = "th"
            | j `mod` 10 == 1 = "st"
            | j `mod` 10 == 2 = "nd"
            | j `mod` 10 == 3 = "rd"
            | otherwise       = "th"
    
showNumber :: Int -> String
showNumber i | i <= 10 && i >=0 = list !! i
             | otherwise        = show i
   where list = [ "zero", "one", "two", "three", "four", "five"
                , "six", "seven", "eight", "nine", "ten" 
                ]
    
prettyOrList :: [String] -> String
prettyOrList []  = ""
prettyOrList [s] = s
prettyOrList xs  = foldr1 (\x y -> x++", "++y) (init xs) ++ " or "++last xs

prettyAndList :: [String] -> String
prettyAndList []  = ""
prettyAndList [s] = s
prettyAndList xs  = foldr1 (\x y -> x++", "++y) (init xs) ++ " and "++last xs

prettyNumberOfParameters :: Int -> String
prettyNumberOfParameters 0 = "no parameters"
prettyNumberOfParameters 1 = "1 parameter"
prettyNumberOfParameters n = show n++" parameters"

capitalize :: String -> String
capitalize []     = []
capitalize (x:xs) = toUpper x : xs

findSimilar :: Name -> Names -> Names
findSimilar n = filter (\x -> show n `similar` show x)

instance Show Entity where
   show entity = 
      case entity of
         TypeSignature   -> "type signature"
         TypeVariable    -> "type variable"
         TypeConstructor -> "type constructor"
         Definition      -> "definition"
         Constructor     -> "constructor"
         Variable        -> "variable"
         Import          -> "import"
         ExportVariable  -> "exported variable"
         ExportModule    -> "exported module"
         ExportConstructor
                         -> "exported constructor"
         ExportTypeConstructor
                         -> "exported type constructor"
         Fixity          -> "infix declaration"