{- Based on module LexerMessage from Helium (GPL license) -}

module Database.Design.Ampersand.Input.ADL1.LexerMessage
    ( LexerError(..)
    , LexerErrorInfo(..)
    , LexerWarning(..)
    , LexerWarningInfo(..)
    , keepOneTabWarning
    , showLexerErrorInfo
    , showLexerWarningInfo
    ) where

import Database.Design.Ampersand.Input.ADL1.FilePos(FilePos)
import qualified Database.Design.Ampersand.Input.ADL1.LexerTexts as Texts

-- | Defines a lexer error
data LexerError = LexerError FilePos LexerErrorInfo -- ^ The lexer file position and error information
    deriving(Show)

-- | Defines the different lexer error types
data LexerErrorInfo
    -- | The comment was not terminated
    = UnterminatedComment
    -- | The purpose statement was not terminated
    | UnterminatedPurpose
    -- | The atom was not terminated
    | UnterminatedAtom
    -- | The character was unexpected
    | UnexpectedChar Char
    -- | The string was not terminated
    | NonTerminatedString String
    -- | In UnexpectedClose, first char is the closing bracket we see, 
    --   second char is the closing bracket we would like to see first
    --   e.g. [(1,3]  =>  UnexpectedClose ']' ... ')'
    | TooManyClose Char
    -- | An unexpected closing bracket was found
    | UnexpectedClose Char FilePos Char
    -- | Brackets were not closed at the end of the file
    | StillOpenAtEOF [(FilePos, Char)]
    -- | DateTime not conforming to ISO 8601 format
    | ProblematicISO8601DateTime
    deriving(Show)

-- | Converts the error information into a list of error messages
showLexerErrorInfo :: LexerErrorInfo -- ^ The error information
                   -> [String] -- ^ The error messages
showLexerErrorInfo info =
    case info of
        UnterminatedComment          -> [ Texts.lexerUnterminatedComment               ]
        UnterminatedPurpose          -> [ Texts.lexerUnterminatedPurpose               ]
        UnterminatedAtom             -> [ Texts.lexerUnterminatedAtom                  ]
        UnexpectedChar c             -> [ Texts.lexerUnexpectedChar c                  ]
        NonTerminatedString _        -> [ Texts.lexerNonTerminatedString, correctStrings ]
        TooManyClose c                 -> [ Texts.lexerTooManyClose c ]
        UnexpectedClose c1 _ c2        ->   Texts.lexerUnexpectedClose c1 c2
        StillOpenAtEOF [b]             -> [ Texts.lexerStillOpenAtEOF [ show (snd b) ] ]
        StillOpenAtEOF bs              -> [ Texts.lexerStillOpenAtEOF (reverse (map (show.snd) bs)) ]
            -- 'reverse' because positions will be sorted and brackets are
            -- reported in reversed order
        ProblematicISO8601DateTime   -> [ Texts.lexerProblematicISO8601DateTime        ]
correctStrings :: String
correctStrings = Texts.lexerCorrectStrings

-- | Defines a lexer warning
data LexerWarning =
    LexerWarning FilePos LexerWarningInfo -- ^ The lexer file position and warning information

-- | Defines the different lexer warning types
data LexerWarningInfo
    = TabCharacter -- ^ Tab character was encountered
    | NestedComment FilePos  -- ^ Nested comment was encountered
    | UtfChar -- ^ The UTF BOM character was found
    | CommentOperator -- ^ Syntax coloring cannot handle names containing --

-- | Converts the warning information into a list of warning messages
showLexerWarningInfo :: LexerWarningInfo  -- ^ The warning information
                     -> [String] -- ^ The warning messages
showLexerWarningInfo info = 
    case info of
        TabCharacter                    -> Texts.lexerTabCharacter
        NestedComment _                 -> Texts.lexerNestedComment
        UtfChar                         -> Texts.lexerUtfChar
        CommentOperator                 -> Texts.lexerCommentOperator

-- | Generates a TabCharacter warning
keepOneTabWarning :: [LexerWarning] -- ^ The old warnings
                  -> [LexerWarning] -- ^ The new warnings
keepOneTabWarning = keepOneTab True
  where
    keepOneTab isFirst (warning@(LexerWarning _ TabCharacter):rest)
        | isFirst   = warning : keepOneTab False rest
        | otherwise = keepOneTab isFirst rest
    keepOneTab isFirst (warning:rest) = 
        warning : keepOneTab isFirst rest
    keepOneTab _ [] = []
