import os, re
from subprocess import Popen, PIPE, STDOUT
import color, default, method, method.shell, mode, tab
from lex import Grammar, PatternRule, RegionRule, PatternGroupRule, OverridePatternRule
from mode.python import StringGrammar2

class CommentGrammar(Grammar):
    rules = [
        PatternRule(r'data', r'(?:[^\*]|\*(?!/))+'),
    ]

class MacroGrammar(Grammar):
    rules = [
        PatternRule(r'spaces', r' +'),

        RegionRule(r'comment', r'/\*', CommentGrammar, r'\*/'),
        PatternRule(r'comment', r'//.*$'),

        PatternRule('name', r'(?:(?<=#define )) *[a-zA-Z_][a-zA-Z0-9_]*'),
        PatternRule(r"unop", r"!(?!=)|\+=|-=|\*=|/=|//=|%=|&=\|\^=|>>=|<<=|\*\*="),
        PatternRule(r'binop', r"\+|<>|<<|<=|<|-|>>|>=|>|\*\*|&|\*|\||/|\^|==|//|~|!=|%"),
        PatternRule(r"delimiter", r"->|\.|\(|\)|\[|\]|{|}|@|,|:|`|;|=|\?"),
        PatternRule(r"identifier", r"[a-zA-Z_][a-zA-Z0-9_]*"),
        PatternRule(r"integer", r"-?(?:0(?![x0-9])|[1-9][0-9]*|0[0-7]+|0[xX][0-9a-fA-F]+)[lL]?"),
        PatternRule(r"float", r"-?(?:[0-9]+\.[0-9]*|\.[0-9]+|(?:[0-9]|[0-9]+\.[0-9]*|\.[0-9]+)[eE][\+-]?[0-9]+)"),
        RegionRule(r'string', '"', StringGrammar2, '"'),
        PatternRule(r'char', r"'.'|'\\.'|'\\[0-7]{3}'"),
        PatternRule(r"continued", r"\\\n$"),
    ]

class CGrammar(Grammar):
    rules = [
        PatternRule(r'spaces', r' +'),

        PatternRule(r"delimiter", r"\.|\(|\)|\[|\]|{|}|@|,|:|`|;|=(?!=)|\?|->"),
        PatternRule(r'eol', r"\n$"),

        PatternGroupRule(r'structgroup', r'keyword', r'struct', r'spaces',
                         r' +', r'structname', r'[a-zA-Z_][a-zA-Z0-9_]*'),
        PatternGroupRule(r'enumgroup', r'keyword', r'enum', r'spaces',
                         r' +', r'enumname', r'[a-zA-Z_][a-zA-Z0-9_]*'),
        PatternRule(r'keyword', r"(?:auto|break|case|char|const|continue|default|double|do|else|enum|extern|float|for|goto|if|int|long|register|return|short|signed|sizeof|static|struct|switch|typedef|union|unsigned|void|volatile|while)(?![a-zA-Z_])"),

        PatternRule(r'function', r'[a-zA-Z_][a-zA-Z0-9_]*(?= *\()'),

        PatternRule(r'builtin', r"(?:NULL|TRUE|FALSE)"),
        PatternRule(r'label', r'[a-zA-Z_][a-zA-Z0-9_]*(?=:)'),

        RegionRule(r'macro', r'# *(?:assert|cpu|define|elif|else|endif|error|ident|ifdef|ifndef|if|import|include_next|line|machine|pragma_once|pragma|system|unassert|undef|warning)(?!=[a-zA-Z0-9_])', MacroGrammar, r'\n$'),

        RegionRule(r'comment', r'/\*', CommentGrammar, r'\*/'),
        PatternRule(r'comment', r'//.*$'),

        RegionRule(r'string', '"', StringGrammar2, '"'),

        PatternRule(r"unop", r"!(?!=)|\+=|-=|\*=|/=|//=|%=|&=\|\^=|>>=|<<=|\*\*="),
        PatternRule(r'binop', r"\+|<>|<<|<=|<|-|>>|>=|>|\*\*|&|\*|\||/|\^|==|//|~|!=|%"),
        PatternRule(r"integer", r"(?:0(?![x0-9])|[1-9][0-9]*|0[0-7]+|0[xX][0-9a-fA-F]+)[lL]?"),
        PatternRule(r"float", r"[0-9]+\.[0-9]*|\.[0-9]+|(?:[0-9]|[0-9]+\.[0-9]*|\.[0-9]+)[eE][\+-]?[0-9]+"),

        RegionRule(r'macrocomment', r'#if +(?:0|NULL|FALSE)', Grammar, r'#endif'),

        PatternRule(r'char', r"'.'|'\\.'|'\\[0-7]{3}'"),

        PatternGroupRule(r'includegrp', r'macro.start', r'# *include', r'spaces',
                         r' +', r'header', r'< *[-A-Za-z/0-9_.]+ *>|" *[-A-Za-z/0-9_.]+ *"',
                         'macro.end', r'\n$'),

        PatternRule(r'identifier', r"[a-zA-Z_][a-zA-Z0-9_]*"),

        OverridePatternRule(r'comment', r'/\* *@@:(?P<token>[.a-zA-Z0-9_]+):(?P<mode>[.a-zA-Z0-9_]+) *\*/$'),
        OverridePatternRule(r'comment', r'// *@@:(?P<token>[.a-zA-Z0-9_]+):(?P<mode>[.a-zA-Z0-9_]+) *$'),
    ]

class CTabber2(tab.StackTabber2):
    open_tokens        = {'delimiter': {'{': '}', '(': ')', '[': ']'}}
    close_tokens       = {'delimiter': {'}': '{', ')': '(', ']': '['}}
    control_tokens     = {'keyword': {'if': 1, 'else': 1, 'while': 1, 'do': 1, 'for': 1}}
    end_at_eof         = False
    end_at_tokens      = {'delimiter': {';': 1}}
    nocontinue_tokens  = {'delimiter': {';': 1}}
    start_free_tokens  = {'string.start': 'string.end'}
    end_free_tokens    = {'string.end': 'string.start'}
    start_macro_tokens = {'macro.start': 'macro.end'}
    end_macro_tokens   = {'macro.end': 'macro.start'}
    def is_base(self, y):
        if y == 0:
            return True
        tokens = self._get_tokens(y)

        # this assumes that people aren't gonna use these macros inside of
        # blocks, which is probably ok.
        t = tokens[0]
        if t.fqname() == 'macro.start' and t.string in ('#define', '#include'):
            return True
        
        # detecting function declarations is annoying; this assumes that people
        # won't put a variable type and name on different lines, but that they
        # might do that for function return type and name.
        #
        # unfortunately, valid function return types might include any of the
        # four types of tokens below
        decl = False
        for t in tokens:
            if t.name in ('keyword', 'identifier', 'structname', 'enumname'):
                decl = True
                continue
            if decl and t.name == 'function':
                break
            else:
                decl = False
                break
        return decl
    def _is_indent(self, t):
        return t.name == 'spaces'
    def _is_ignored(self, t):
        return t.fqname() in ('spaces', 'eol', 'comment', 'comment.start',
                              'comment.data', 'comment.null', 'comment.end')

class CCheckSyntax(method.shell.Exec):
    '''Build this C program (using the mode's make cmd)'''
    show_success = False
    args = []
    def _execute(self, w, **vargs):
        if w.application.config['c.syntax-rel-dir']:
            d = os.path.dirname(w.buffer.path)
            self._doit(w, w.buffer.path, w.application.config['c.syntax-cmd'],
                       cmdname='c-check-syntax', cmddir=d)
        else:
            self._doit(w, w.buffer.path, w.application.config['c.syntax-cmd'],
                       cmdname='c-check-syntax')

class CMake(method.shell.Exec):
    '''Build this C program (using the mode's make cmd)'''
    show_success = False
    args = []
    def _execute(self, w, **vargs):
        if w.application.config['c.make-rel-dir']:
            d = os.path.dirname(w.buffer.path)            
            self._doit(w, w.buffer.path, w.application.config['c.make-cmd'],
                       cmdname='c-make', cmddir=d)
        else:
            self._doit(w, w.buffer.path, w.application.config['c.make-cmd'],
                       cmdname='c-make')

class C(mode.Fundamental):
    modename    = 'C'
    extensions  = ['.c', '.h', '.cpp']
    #tabbercls   = CTabber
    tabbercls   = CTabber2
    grammar     = CGrammar
    opentokens  = ('delimiter',)
    opentags    = {'(': ')', '[': ']', '{': '}'}
    closetokens = ('delimiter',)
    closetags   = {')': '(', ']': '[', '}': '{'}
    colors      = {
        'macrocomment.start':   ('red', 'default', 'bold'),
        'macrocomment.null':    ('red', 'default', 'bold'),
        'macrocomment.end':     ('red', 'default', 'bold'),
        'macro':                ('blue', 'default', 'bold'),
        'macro.start':          ('blue', 'default', 'bold'),
        'macro.name':           ('yellow', 'default', 'bold'),
        'macro.null':           ('magenta', 'default', 'bold'),
        'macro.continued':      ('red', 'default', 'bold'),
        'macro.delimiter':      ('default', 'default', 'bold'),
        'macro.identifier':     ('yellow', 'default', 'bold'),
        'macro.bareword':       ('yellow', 'default', 'bold'),
        'macro.integer':        ('green', 'default', 'bold'),
        'macro.float':          ('green', 'default', 'bold'),
        'macro.char':           ('green', 'default', 'bold'),
        'macro.string.start':   ('green', 'default', 'bold'),
        'macro.string.escaped': ('magenta', 'default', 'bold'),
        'macro.string.octal':   ('magenta', 'default', 'bold'),
        'macro.string.null':    ('green', 'default', 'bold'),
        'macro.string.end':     ('green', 'default', 'bold'),
        'macro.end':            ('magenta', 'default', 'bold'),
        'include':              ('blue', 'default', 'bold'),
        'header':               ('green', 'default', 'bold'),
        #'structname':          ('yellow', 'default', 'bold'),
        #'enumname':            ('yellow', 'default', 'bold'),
        #'c_type':              ('green', 'default', 'bold'),
    }
    config = {
        'c.syntax-cmd':     "gcc -x c -fsyntax-only %(path)s",
        'c.syntax-rel-dir': False,
        'c.make-cmd':       "make",
        'c.make-rel-dir':   True,
    }
    lconfig = {
        'ignore-suffix': ['.o'],
    }
    actions = [CCheckSyntax, CMake]

    format = "%(flag)s  %(bname)-18s  (%(mname)s)  %(indent)s  %(cursor)s/%(mark)s  %(perc)s  [%(func)s]"

    def get_status_names(self):
        names = mode.Fundamental.get_status_names(self)
        c = self.window.logical_cursor()
        names['func'] = self.get_line_function(c.y)
        return names

    def __init__(self, w):
        mode.Fundamental.__init__(self, w)
        self.add_bindings('close-paren', (')',))
        self.add_bindings('close-brace', ('}',))
        self.add_bindings('close-bracket', (']',))
        self.add_bindings('c-check-syntax', ('C-c s',))
        self.add_bindings('c-make', ('C-c C-c',))

    def get_functions(self):
        #return self.context.get_names()
        return {}
    def get_function_names(self):
        #return self.context.get_name_list()
        return []
    def get_line_function(self, y):
        #return self.context.get_line_name(y)
        return None

install = C.install