import re

import color, method, mode
from lex import Grammar, Rule, PatternRule, RegionRule, PatternMatchRule

class StringGrammar1(Grammar):
    rules = [
        PatternRule('data', '[^"&]+'),
        PatternRule('escaped', '&[a-z]+;'),
    ]
class StringGrammar2(Grammar):
    rules = [
        PatternRule('data', r"[^'&]+"),
        PatternRule('escaped', '&[a-z]+;'),
    ]

class CDataGrammar(Grammar):
    rules = [PatternRule('data', r'(?:[^\]]|\](?!\])|\]\](?!>))+')]
class CommentGrammar(Grammar):
    rules = [PatternRule('data', '(?:[^-]|-(?!-)|--(?!>))+')]
class TagGrammar(Grammar):
    rules = [
        PatternRule('attrname', '[a-zA-Z_][a-zA-Z0-9_]+(?==)'),
        PatternRule('namespace', '[a-zA-Z_]+(?=:)'),
        PatternRule('name', '[a-zA-Z_][a-zA-Z0-9_]*'),
        PatternRule('delimiter', '[:/=]'),
        RegionRule('string', '"', StringGrammar1, '"'),
        RegionRule('string', "'", StringGrammar2, "'"),
        PatternRule('spaces', ' +'),
        PatternRule('eol', r'\n'),
    ]
class MetadataGrammar(Grammar):
    rules = [PatternRule('meta', r'\?(?:xml)?')] + TagGrammar.rules
class DoctypeGrammar(Grammar):
    rules = [PatternRule('doctype', '!DOCTYPE')] + TagGrammar.rules

class XMLGrammar(Grammar):
    rules = [
        # TODO: how does cdata work again?
        PatternRule('data', r'[^<& \n]+'),
        PatternRule('spaces', ' +'),
        PatternRule('xml.entity', '&[a-z]+;'),
        PatternRule('eol', r'\n'),
        PatternMatchRule('x', '(<)(/)([a-zA-Z_][a-zA-Z0-9_]*)(>)',
                         'xml.tag.start', 'xml.tag.delimiter', 'xml.tag.name',
                         'xml.tag.end'),
        RegionRule('xml.tag', r'<(?![\?!])', TagGrammar, '/?>'),
        RegionRule('xml.tag', r'<(?![\?!])', TagGrammar, '/?>'),
        RegionRule('comment', '<!--', CommentGrammar, '-->'),
        RegionRule('xml.tag', r'<(?=\?)', MetadataGrammar, r'\?>'),
        RegionRule('xml.tag', '<(?!!)', DoctypeGrammar, '>'),
        RegionRule('xml.cdata', r'<!\[CDATA\[', CDataGrammar, r'\]\]>'),
    ]

class XmlValidate(method.shell.Exec):
    '''Valid the buffer's contents as valid XML.'''
    show_success = True
    args         = []
    def _execute(self, w, **vargs):
        self._doit(w, w.buffer.path, 'xmlwf %(path)r', cmdname='xml-validate')

class XmlCreateTag(method.Method):
    '''Create an opening and closing tag'''
    args = [method.Argument('tagname', prompt="Tag Name: ",
                            help="Create an open/close tag pair")]
    def _execute(self, w, **vargs):
        t = vargs['tagname']
        w.insert_string_at_cursor("<%s>" % t)
        p = w.logical_cursor()
        w.insert_string_at_cursor("</%s>" % t)
        w.goto(p)

class XmlCreateComment(method.Method):
    '''Create an opening and closing tag'''
    def _execute(self, w, **vargs):
        w.insert_string_at_cursor("<!-- ")
        p = w.logical_cursor()
        w.insert_string_at_cursor(" -->")
        w.goto(p)

class XmlCreateCdata(method.Method):
    '''Create an opening and closing tag'''
    def _execute(self, w, **vargs):
        w.insert_string_at_cursor("<![CDATA[")
        p = w.logical_cursor()
        w.insert_string_at_cursor("]]>")
        w.goto(p)

class XML(mode.Fundamental):
    name       = 'XML'
    extensions = ['.xml', '.xml.in', '.xsl', '.xsd']
    detection  = [re.compile(r'^<\?xml')]
    grammar    = XMLGrammar
    colors     = {
        'xml.tag.meta':      ('magenta', 'default', 'bold'),
        'xml.tag.doctype':   ('magenta', 'default', 'bold'),
        'xml.tag.start':     ('default', 'default', 'bold'),
        'xml.tag.namespace': ('magenta', 'default', 'bold'),
        'xml.tag.name':      ('blue', 'default', 'bold'),
        'xml.tag.attrname':  ('cyan', 'default', 'bold'),
        'xml.tag.end':       ('default', 'default', 'bold'),
        'xml.entity':        ('magenta', 'default', 'bold'),
        'xml.cdata.start':   ('magenta', 'default', 'bold'),
        'xml.cdata.data':    ('green', 'default', 'bold'),
        'xml.cdata.end':     ('magenta', 'default', 'bold'),
    }
    actions = [XmlValidate, XmlCreateTag, XmlCreateComment, XmlCreateCdata]
    _bindings = {
        'xml-create-tag': ('M-t',),
    }
install = XML.install