# -*- coding: UTF-8 -*-
#    This file is part of arena.
#   
#    arena is free software: you can redistribute it and/or modify it
#    under the terms of the GNU General Public License as published by the
#    Free Software Foundation; either version 3 of the License or, at your
#    option, any later version.
#
#    arena is distributed in the hope that it will be useful, but
#    WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABLITY
#    or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
#    License for more details.
#
#    You should have received a copy of the GNU General Public License along
#    with arena.  If not, see <http://www.gnu.org/licenses/>. 

## @file
#  This file defines a class that parses a Markdown-style source stream.

from Library.Parse.Parsers import perrNoOutput
from Library.Parse.SourceParsers import ASourceParser
from Library.Parse.SymbolTables import Scope, ASymbolTable
from Library.Parse.Scan.SourceScanners import ASourceScanner
from Library.Parse.Lex.Languages import TokenType
from Plugins.Content.Parse.Lex.Languages import AMarkdownLanguage
from Plugins.Content.Parse.Semantic.MarkdownStatements import AMarkdownStatement
from Plugins.Content.Parse.Semantic.MarkdownPreStatements import \
    APrescannedMarkdownStatement
from Library.IO.StringStreams import AStringStream

from Plugins.Content.PerUser.Arena.Attachments import AnArenaAttachment

## Events generated by AMarkdownParser
mdpEvents = {
    # Before the source is pre-scanned
    "prescan": None
}

## Token types that should not be muted 
mdpUncensoredTokens = (
  TokenType.identifier, TokenType.endOfLine
)

## A parser that parses wiki markup and returns HTML output.
class AMarkdownParser(ASourceParser):
    ## Constructor
    def __init__(Self, **params):        
        params.update(Language = AMarkdownLanguage())
        ASourceParser.__init__(Self, **params)
        
        # Establish the global symbol table
        Self._Symbols[Scope.globals] = ASymbolTable(Scope.globals)
        
        if "Content" in Self._params:
            Self._Content = Self._params["Content"]
            del Self._params["Content"]
        
    ## Initializer
    def init(Self):
        result = ASourceParser.init(Self)
        
        if result:
            # Establish private attributes
            # The post being parsed
            Self._Content = None
            # A list of attachments referenced from the post
            Self._Attachments = {}
            
            # Establish public attributes
            # The line numbers where setext headers occur
            Self._postfixHeaderLines = {}
            
            # Update the events generated by the class
            Self._events.update(mdpEvents)
            
        return result
        
    ## Destructor
    def __del__(Self):
        # Free the header lines
        del Self._postfixHeaderLines
        
        ASourceParser.__del__(Self)
    
    ## Retrieve the next token from the source    
    def NextToken(Self):
        # Mute the current token, if indicated, so that it does not show up
        # in the parser output
        if Self._Token is not None:
            Self._Token.silent = Self._Token.code() not in mdpUncensoredTokens
            
        return ASourceParser.NextToken(Self)
        
    ## Pre-scan the source looking for link identifiers
    def prescan(Self):
        Event = Self.generate("prescan")
        if Event.handled():
            del Event
            return
            
        del Event
        
        # Construct a temporary stream to store output from prescanning
        FirstPass = AStringStream()
                # Swap a temporary output stream into place
        PreviousOutput = Self.OutputTo(FirstPass)
        
        # Set up a scanner
        Self._Scanner = ASourceScanner(Self._Source, Self._Language)
        # Start scanning
        Self.NextToken()
        Statement = None
        
        # Parse statements until the end of the stream
        while Self._Token is not None:
            if Self._Token.code() == TokenType.endOfStream:
                break
                
            # Free any previous statement
            if Statement is not None:
                del Statement
                Statement = None
                
                if Self._Token.code() == TokenType.endOfStream:
                    break
                
            # Parse the next statement
            Statement = APrescannedMarkdownStatement.ParsedFrom(Self)  
            Statement.parse() 
            
        # Destroy the scanner
        del Self._Scanner
        # Free all tokens
        Self._PreviousToken = None
        Self._Token = None
    
        FirstPass.moveTo(0)
        del Self._Source
        
        # The second pass will use the output from prescanning as its input
        Self._Source = FirstPass
        Self.OutputTo(PreviousOutput)
        
    ## Parse the current token
    def parseCurrentToken(Self):            
        # Parse the next statement
        Statement = AMarkdownStatement.ParsedFrom(Self)    
        Statement.parse()    
          
        # Output the statement value
        if Statement.value() is not None:
            Statement.printTo(Self._Output)            
        
        # Free the statement
        del Statement
                            
    ## Execute the parser.
    def parse(Self):
        # Check for a valid output stream
        if Self._Output is None:
            raise Self.fatalError(perrNoOutput)
            
        # Pre-scan for link identifiers
        Self.prescan()
        #Self._Output.write(Self._Source.toString())
        #Self._Language.Opcodes().printTo(Self._Output)
        #return 0
        
        # Construct a scanner
        Self._Scanner = ASourceScanner(Self._Source, Self._Language)        
        
        # Parse statements until the end of the stream
        Self.parseUntil(TokenType.endOfStream)
            
        return 0
            
    ## Mark a header line
    def markPostfixHeaderLine(Self, level):
        # Store the header line (always the line ABOVE the current line)
        Self._postfixHeaderLines[Self.line() - 1] = level

    ## Determine whether the current line is a postfix header line
    def postfixHeaderLevel(Self):
        if Self.line() in Self._postfixHeaderLines.keys():
            return Self._postfixHeaderLines[Self.line()]
            
        return 0
        
    ## Record the preamble parsed from the post
    def recordPreamble(Self):
        if Self._Content is not None:
            Self._Content.recordPreamble(Self._Output.toString())
            
    ## Retrieve an attachment referenced in the post
    def Attachment(Self, attachmentName):
        # See if the named attachment is already cached
        if attachmentName not in Self._Attachments:
            # Fetch the attachment from the data source
            Attachment = AnArenaAttachment(None,
                Mediator=Self._Content.Mediator()
            )
            
            Attachment.fetch(
                fieldNames = ("icon", "clientPath", "description"),
                criteria = [
                    (("{field:attachmentName} = {}", attachmentName))
                ]
            )
            
            Self._Attachments[attachmentName] = None
            
            if ("clientPath" in Attachment) and \
                (Attachment["clientPath"] != ""):
                    Self._Attachments[attachmentName] = Attachment
        
        return Self._Attachments[attachmentName]
        
    ## Retrieve a URL that will allow users to upload an attachment
    def newAttachmentLink(Self, attachmentName):
        result = ""
        
        Attachment = AnArenaAttachment({"attachmentName": attachmentName},            
            Mediator=Self._Content.Mediator()
        )
        
        result = Attachment.uploadLink()
        del Attachment
        
        return result
        
    # Properties ---------------------------------------------------------------
    ## Retrieve the content handler that provided content to the parser
    def Content(Self):
        return Self._Content
