module Language.CSharp.Parser.Statement where

import Text.Parsec                       hiding (Empty)
import Language.CSharp.Lexer
import Language.CSharp.Syntax
import Language.CSharp.Parser.Utility
import Language.CSharp.Parser.Expression
import Language.CSharp.Parser.Type

pStatements :: P [Statement]
pStatements = many pStatement <?> "a statement"

pStatement :: P Statement
pStatement = choice 
    [ pIfThenElseStatement, pWhileStatement      , pDoStatement         
    , pForEachStatement   , pLockStatement       , pBlockStatement    
    , pLabeledStatement   , pEmptyStatement      , pBreakStatement      
    , pContinueStatement  , pReturnStatement     , pThrowStatement    
    , pYieldStatement     , pDeclarationStatement, pCheckedStatement    
    , pUncheckedStatement , pExpressionStatement , pUsingStatement
    , pSwitchStatement    , pGotoStatement       , pTry               
    , pForStatement       ]
    
pLabeledStatement :: P Statement
pLabeledStatement = try (Labeled <$> pIdentifier <* pColon <*> pStatement)

pDeclarationStatement :: P Statement
pDeclarationStatement = try (Declaration <$> pLocalVarDeclaration <* pSemi)

pLocalVarDeclaration :: P LocalVarDeclaration
pLocalVarDeclaration = 
    LocalVarDeclaration <$> pLocalVarType <*> sepBy1 pVariableDeclarator pComma

pLocalVarType :: P LocalVarType
pLocalVarType = VarType <$> pType <|> Var <$ pIdentifierKeyword "var" 

pVariableDeclarator :: P VariableDeclarator
pVariableDeclarator = do
    name        <- pIdentifier
    initializer <- optionMaybe (pEqualSign *> pVariableInitializer)
    return $ VariableDeclarator name initializer

pVariableInitializer :: P VariableInitializer
pVariableInitializer = 
    VariableInitializerExpression <$> pExpression       <|>
    VariableInitializerArray      <$> pArrayInitializer

pArrayInitializer :: P ArrayInitializer
pArrayInitializer = 
    ArrayInitializer <$> betweenCurly (sepBy pVariableInitializer pComma)

pBlockStatement :: P Statement
pBlockStatement = Block <$> betweenCurly pStatements

pEmptyStatement :: P Statement
pEmptyStatement = Empty <$ pSemi

pExpressionStatement :: P Statement
pExpressionStatement = ExpressionStatement <$> pExpression <* pSemi

pIfThenElseStatement :: P Statement
pIfThenElseStatement = do
    pToken TKWif
    guard     <- betweenParens pExpression
    trueBody  <- pStatement
    falseBody <- optionMaybe (pToken TKWelse *> pStatement)
    return $ IfThenElse guard trueBody falseBody

pWhileStatement :: P Statement
pWhileStatement = do
    pToken TKWwhile
    guard <- betweenParens pExpression
    body  <- pStatement
    return $ While guard body

pDoStatement :: P Statement
pDoStatement = do
    pToken TKWdo
    body <- pStatement
    pToken TKWwhile
    guard <- betweenParens pExpression <* pSemi
    return $ Do body guard

pForStatement :: P Statement
pForStatement = do
    pToken TKWfor
    pOParens
    initializer <- optionMaybe pForInitializer
    pSemi
    guard <- optionMaybe pExpression
    pSemi
    iterator <- optionMaybe (sepBy1 pExpression pComma)
    pCParens
    body <- pStatement
    return $ For initializer guard iterator body

pForInitializer :: P ForInitializer
pForInitializer = 
    ForInitializerDeclaration <$> pLocalVarDeclaration      <|>
    ForInitializerExpressions <$> sepBy1 pExpression pComma

pForEachStatement :: P Statement
pForEachStatement = do
    pToken TKWforeach
    pOParens
    ty <- pLocalVarType
    name <- pIdentifier
    pToken TKWin
    expr <- pExpression
    pCParens
    body <- pStatement
    return $ ForEach ty name expr body

pSwitchStatement :: P Statement
pSwitchStatement = do
    pToken TKWswitch
    expression <- betweenParens pExpression
    cases      <- betweenCurly (many (pSwitchCaseBlock <|> pSwitchDefaultBlock))
    return $ Switch expression cases

pSwitchCaseBlock :: P SwitchBlock
pSwitchCaseBlock = 
    LabeledBlock <$ pToken TKWcase <*> pExpression <* pColon <*> many pStatement
    
pSwitchDefaultBlock :: P SwitchBlock
pSwitchDefaultBlock = DefaultBlock <$ pToken TKWdefault <* pColon <*> many pStatement

pTry :: P Statement
pTry = do
    pToken TKWtry
    statements <- betweenCurly pStatements
    (catches, finally) <- choice 
        [ try ((,)   <$> many1 pCatch <* pToken TKWfinally <*> betweenCurly pStatements)
        , try ((,[]) <$> many1 pCatch)
        , try (([],) <$ pToken TKWfinally <*> betweenCurly pStatements) ]
    return $ Try statements catches finally

pCatch :: P Catch
pCatch = do
    pToken TKWcatch
    specifier  <- optionMaybe pExceptionSpecifier
    when       <- optionMaybe (pIdentifierKeyword "when" *> betweenParens pExpression)
    statements <- betweenCurly pStatements
    return $ Catch specifier when statements
    where
        pExceptionSpecifier = betweenParens (ExceptionSpecifier <$> pType <*> optionMaybe pIdentifier)

pGotoStatement :: P Statement
pGotoStatement = Goto <$> pGotoTarget <* pSemi

pGotoTarget :: P GotoTarget
pGotoTarget = choice
    [ try (GotoLabel <$ pToken TKWgoto <*> pIdentifier)
    , try (GotoCase <$ pToken TKWgoto <* pToken TKWcase <*> pExpression)
    , try (GotoDefault <$ pToken TKWgoto <* pToken TKWdefault) ]

pBreakStatement :: P Statement
pBreakStatement = Break <$ pToken TKWbreak <* pSemi

pContinueStatement :: P Statement
pContinueStatement = Continue <$ pToken TKWcontinue <* pSemi

pReturnStatement :: P Statement
pReturnStatement = Return <$ pToken TKWreturn <*> pMaybeExpression <* pSemi

pThrowStatement :: P Statement
pThrowStatement = Throw <$ pToken TKWthrow <*> pMaybeExpression <* pSemi

pLockStatement :: P Statement
pLockStatement = do
    pToken TKWlock
    expr <- betweenParens pExpression
    body <- pStatement
    return $ Lock expr body

pUsingStatement :: P Statement
pUsingStatement = do
    pToken TKWusing 
    resource <- betweenParens pResourceAquisition 
    body     <- pStatement
    return $ UsingStatement resource body

pResourceAquisition :: P ResourceAcquisition
pResourceAquisition = 
    ResourceAcquisitionVariable   <$> sepBy1 pVariableDeclarator pComma <|>
    ResourceAcquisitionExpression <$> pExpression             

pCheckedStatement :: P Statement
pCheckedStatement = try $
    CheckedStatement <$ pToken TKWchecked <*> betweenCurly pStatements
    
pUncheckedStatement :: P Statement
pUncheckedStatement = try $
    UncheckedStatement <$ pToken TKWunchecked <*> betweenCurly pStatements

pYieldStatement :: P Statement
pYieldStatement = pIdentifierKeyword "yield" *> (pYieldReturn <|> pYieldBreak)
    where
        pYieldReturn = Yield . Just  <$ pToken TKWreturn <*> pExpression <* pSemi
        pYieldBreak  = Yield Nothing <$ pToken TKWbreak <* pSemi