module Text.Boomerang.Error where
import Control.Monad.Error (Error(..))
import Data.Data (Data, Typeable)
import Data.List (intercalate, sort, nub)
import Text.Boomerang.Prim
import Text.Boomerang.Pos
data ErrorMsg
    = SysUnExpect String
    | EOI         String
    | UnExpect    String
    | Expect      String
    | Message     String
      deriving (Eq, Ord, Read, Show, Typeable, Data)
messageString :: ErrorMsg -> String
messageString (Expect s)         = s
messageString (UnExpect s)       = s
messageString (SysUnExpect s)    = s
messageString (EOI s)            = s
messageString (Message s)        = s
data ParserError pos = ParserError (Maybe pos) [ErrorMsg]
    deriving (Eq, Ord, Typeable, Data)
type instance Pos (ParserError p) = p
instance ErrorPosition (ParserError p) where
    getPosition (ParserError mPos _) = mPos
instance Error (ParserError p) where
    strMsg s = ParserError Nothing [Message s]
mkParserError :: pos -> [ErrorMsg] -> [Either (ParserError pos) a]
mkParserError pos e = [Left (ParserError (Just pos) e)]
infix  0 <?>
(<?>) :: PrinterParser (ParserError p) tok a b -> String -> PrinterParser (ParserError p) tok a b
router <?> msg = 
    router { prs = Parser $ \tok pos ->
        map (either (\(ParserError mPos errs) -> Left $ ParserError mPos ((Expect msg) : errs)) Right) (runParser (prs router) tok pos) }
condenseErrors :: (Ord pos) => [ParserError pos] -> ParserError pos
condenseErrors errs = 
    case bestErrors errs of
      [] -> ParserError Nothing []
      errs'@(ParserError pos _ : _) ->
          ParserError pos (nub $ concatMap (\(ParserError _ e) -> e) errs')
showErrorMessages :: String -> String -> String -> String -> String -> [ErrorMsg] -> String
showErrorMessages msgOr msgUnknown msgExpecting msgUnExpected msgEndOfInput msgs
    | null msgs = msgUnknown
    | otherwise = intercalate ("; ") $ clean $  [showSysUnExpect, showUnExpect, showExpect, showMessages]
    where
      isSysUnExpect (SysUnExpect {}) = True
      isSysUnExpect _                = False
      isEOI (EOI {})                 = True
      isEOI _                        = False
      isUnExpect (UnExpect {})       = True
      isUnExpect _                   = False
      isExpect (Expect {})           = True
      isExpect _                     = False
      (sysUnExpect,msgs1) = span (\m -> isSysUnExpect m || isEOI m) (sort msgs)
      (unExpect   ,msgs2) = span isUnExpect msgs1
      (expect     ,msgs3) = span isExpect msgs2
      showExpect      = showMany msgExpecting expect
      showUnExpect    = showMany msgUnExpected unExpect
      showSysUnExpect 
          | null sysUnExpect = ""
          | otherwise        = 
              let msg = head sysUnExpect
              in msgUnExpected ++ " " ++ 
                     if (isEOI msg) then msgEndOfInput ++ " " ++ (messageString $ head sysUnExpect)
                                    else messageString $ head sysUnExpect
      showMessages      = showMany "" msgs3
      showMany pre msgs = case clean (map messageString msgs) of
                            [] -> ""
                            ms | null pre  -> commasOr ms
                               | otherwise -> pre ++ " " ++ commasOr ms
      commasOr []         = ""
      commasOr [m]        = m
      commasOr ms         = commaSep (init ms) ++ " " ++ msgOr ++ " " ++ last ms
      commaSep            = seperate ", " . clean
      seperate   _ []     = ""
      seperate   _ [m]    = m
      seperate sep (m:ms) = m ++ sep ++ seperate sep ms
      clean               = nub . filter (not . null)
instance (Show pos) => Show (ParserError pos) where
    show e = showParserError show e
showParserError :: (pos -> String) 
               -> ParserError pos  
               -> String
showParserError showPos (ParserError mPos msgs) =
        let posStr = case mPos of
                       Nothing -> "unknown position"
                       (Just pos) -> showPos pos
        in "parse error at " ++ posStr ++ ": " ++ (showErrorMessages "or" "unknown parse error" "expecting" "unexpected" "end of" msgs)