# -*- 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 extends AMarkdownStatement to allow it to handle code blocks that
#  require syntax highlighting.

from Plugins.Content.Parse.Semantic.MarkdownStatements import \
    AMarkdownTabBlock
from Plugins.Content.Parse.Lex.Languages import MarkdownOpcode, \
    MarkdownCharacterTypes

from Library.Parse.Parsers import perrNoOutput
from Library.Parse.SourceParsers import ASourceParser
from Library.Parse.Lex.Languages import TokenType, CharacterType
from Library.Parse.Scan.SourceScanners import ASourceScanner
from Library.IO.StringStreams import AStringStream
from Library.IO.XML.XMLTags import AnXMLTag, AnXMLWrapperTag
from Library.String.Utilities import String

## A custom opcode for our new parser instance
class CustomOpcode:
    ## The opcode that flags a code block for syntax handling
    opShebang = TokenType.special + 0x2301

## Set up a new opcode that flags a code block for syntax handling
HighlightOpcode = {
    "#!": CustomOpcode.opShebang
}
    
## The prefix added to language plugins before they are loaded
ashLanguagePluginPrefix = "Content.Parse.Lex."
    
## A special parser that provides syntax highlighting
class ASyntaxHighlighter(ASourceParser):
    ## Constructor
    def __init__(Self, **params):
        ASourceParser.__init__(Self, **params)
        
        if "Mediator" in Self._params:
            Self._Mediator = Self._params["Mediator"]
            
            del Self._params["Mediator"]
            
        if "language" in Self._params:
            Self._language = Self._params["language"]
            del Self._params["language"]
            
    ## Initializer
    def init(Self):
        result = ASourceParser.init(Self)
        
        if result:
            # Establish private attributes
            # The mediator driving display of content
            Self._Mediator = None
            # The name of the language to load
            Self._language = None
            
        return result
    
    ## Parse the current token
    def parseCurrentToken(Self):  
        Span = AnXMLWrapperTag("span")
        
        # Prevent the token from being written
        Self._Token.silent = True
        
        # Highlight keywords
        if Self._Token.code() == TokenType.keyword:
            Span["class"] = "code_keyword"
            Span.text = Self._Token.literal()
            
        # Highlight operators
        elif Self._Token.code() == TokenType.operator:
            Span["class"] = "code_operator"
            Span.text = Self._Token.literal() 
            
        # Highlight type identifiers
        elif Self._Token.code() == TokenType.typeIdentifier:
            Span["class"] = "code_type_identifier"
            Span.text = Self._Token.literal()
            
        # Highlight line comments
        elif Self._Token.literal() in Self._Language.Rule("comment/line"):
            Span["class"] = "code_comment_line"
            Span.text = Self._Token.literal()
            
            Self.NextToken()
            
            while Self._Token.code() != TokenType.endOfLine:
                if Self._Token.code() == TokenType.endOfStream:
                    break
                    
                Span.text += Self._Token.literal()
                Self.NextToken()
                
            # Unmute the EOL or EOF token
            Self._Token.silent = False
                
        # Highlight block comments
        elif Self._Token.literal() in Self._Language.Rule("comment/block/begin"):
            Span["class"] = "code_comment_block"
            Span.text = Self._Token.literal()
            
            Self.NextToken()
            
            while Self._Token.literal() not in Self._Language.Rule("comment/block/end"):
                if Self._Token.code() == TokenType.endOfStream:
                    break
                    
                Span.text += Self._Token.literal()
                Self.NextToken()
                
            Span.text += Self._Token.literal() 
            # Silence the token, since it is part of the span text
            Self._Token.silent = True                     
            
        # Highlight symbols
        elif Self._Token.code() == TokenType.special:
            Span["class"] = "code_symbol"
            Span.text = Self._Token.literal()
            
        # Highlight numbers
        elif Self._Token.code() == TokenType.number:
            Span["class"] = "code_number"
            Span.text = Self._Token.literal()
            
        # Highlight strings
        elif Self._Token.code() == TokenType.string:
            stringBeginning = Self._Token.literal()
            
            Span["class"] = "code_string"
            Span.text = Self._Token.literal()
            
            Self.NextToken()
            
            while Self._Token.literal() != stringBeginning:
                if Self._Token.code() in (TokenType.endOfLine, TokenType.endOfStream):
                    break
                    
                # Check for escape characters
                if Self._Token.literal() in Self._Language.Rule("string/escape"):
                    EscapeSpan = AnXMLWrapperTag("span")
                    EscapeSpan["class"] = "code_string_escape"
                    EscapeSpan.text = Self._Token.literal()
                    Span.text += EscapeSpan.toString()
                    
                else:
                    Span.text += Self._Token.literal()
                
                Self.NextToken()
                
            Span.text += Self._Token.literal()
            # Silence the token, since it is now part of the span text
            Self._Token.silent = True
            
        # Output the token verbatim
        else:
            Self._Token.silent = False
            
        if String.isEmpty(Span.text) == False:
            # Output the span
            Span.printTo(Self._Output)
            
        # Output the token code, if indicated
        if Self._Token.silent == False:
            Self._Output.write(Self._Token.literal())
        
        Self.NextToken()
                            
    ## Execute the parser.
    def parse(Self):
        # Check for a valid output stream
        if Self._Output is None:
            raise Self.fatalError(perrNoOutput)
            
        if String.isEmpty(Self._language):
            return -1
            
        #Self._Output.write(Self._Source.toString())
        #Self._Language.Opcodes().printTo(Self._Output)
        #return 0
        
        # Load our language
        Self._Language = Self._Mediator.loadPlugin(
            ashLanguagePluginPrefix + Self._language,
            Self._language
        )
        
        if Self._Language is not None:
            Self._Language = Self._Language()
        
        # Construct a scanner
        Self._Scanner = ASourceScanner(Self._Source, Self._Language)        
        
        # Parse statements until the end of the stream
        Self.parseUntil(TokenType.endOfStream)
            
        return 0    

## A custom Markdown tab block
class ACustomMarkdownTabBlock(AMarkdownTabBlock):
    ## Determine if the class will handle the specified token
    @classmethod
    def handlerFor(Self, tokenCode, **params):
        # Check with subclasses first
        Result = Self.subclassHandlerFor(tokenCode, **params)
        
        if Result is None:        
            if tokenCode == MarkdownOpcode.dlTab:
                Result = Self(**params)          
            
        return Result
        
    # Parse the statement
    def parse(Self):
        Event = Self.generate("parse")
        if Event.handled():
            del Event
            return
            
        del Event
                
        # Cache the previous token
        previousToken = Self._Parser.PreviousToken().code()
        # Set up whitespace equivalent to a tab length
        whitespace = " " * Self._Language.tabSize()
        # Whether or not the block is highlighted
        highlightBlock = False
        # The name of the language parser used to highlight the block
        highlightLanguage = ""
            
        # Were we just at the end of a line or do we have more than one 
        # consecutive tab?
        if (previousToken == TokenType.endOfLine) or \
          (previousToken == MarkdownOpcode.dlTab):
            # We are in a code block
            CodeBlock = AnXMLWrapperTag("code")
            CodeBlockText = AStringStream()
            PreviousOutput = Self.OutputTo(CodeBlockText)
            lineCount = 1
            
            # Print literals until the end of the line                
            while Self._Parser.Token().code() != TokenType.endOfStream:
                if Self._Parser.Token().code() == TokenType.endOfStream:
                    break
                 
                if Self._Parser.Token().code() != MarkdownOpcode.dlTab:
                    Self._Output.write(Self._Parser.Token().literal())
                    
                else:
                    Self._Output.write(whitespace)
                 
                Self._Parser.NextToken()                 
                 
                # Check for the opcode that indicates the block should be
                # highlighted
                if Self._Parser.Token().code() == CustomOpcode.opShebang:
                    highlightBlock = True
                    # Get the next token, which will indicate the language to 
                    # use
                    Self._Parser.NextToken()
                    highlightLanguage = Self._Parser.Token().literal()
                    
                    # Get the token after the language specifier
                    Self._Parser.NextToken()
                    
                if Self._Parser.Token().code() == TokenType.endOfLine:
                    #Break = AnXMLTag("br")
                    #Break.printTo(Self._Output)
                    #del Break
                    
                    # Check whether the previous token was the end of line
                    if Self._Parser.PreviousToken().code() == TokenType.endOfLine:
                        # Check whether the next line is indented
                        Tok = Self._Parser.PeekToken()
                        if Tok.code() in (TokenType.space, MarkdownOpcode.dlTab):
                            continue
                        
                        # Highlight the block, ignoring errors
                        if (highlightBlock) and (len(highlightLanguage) > 0):
                            try:
                                CodeBlockText.write(MarkdownCharacterTypes[CharacterType.endOfStream])
                                CodeBlockText.moveTo(0)
                            
                                Highlighter = ASyntaxHighlighter(
                                    Mediator = Self._Parser.Content().Mediator(),
                                    language = highlightLanguage,
                                    Source = CodeBlockText)
                                    
                                HighlightedText = AStringStream()
                                Highlighter.OutputTo(HighlightedText)
                                Highlighter.parse()
                                
                                CodeBlockText = HighlightedText
                                
                            except:
                                pass                            
                        
                        CodeBlock.text = CodeBlockText.toString()
                        # Remove the extraneous first line from highlighted blocks
                        if highlightBlock:
                            CodeBlock.text = "\n".join(CodeBlock.text.splitlines()[1:])
                            
                        del CodeBlockText
                        Self.OutputTo(PreviousOutput)
                        PreBlock = AnXMLWrapperTag("pre")
                        PreBlock["class"] = "quote"
                        PreBlock.text = CodeBlock.toString()
                        del CodeBlock
                        PreBlock.printTo(Self._Output)
                        del PreBlock
                        
                        break
                            
        else:
            # Output the token
            Self._Output.write(whitespace)
            Self._Parser.NextToken()
        
