import time

import re

import keyword

import __builtin__

from Tkinter import *

from Delegator import Delegator

from configHandler import idleConf



DEBUG = False



def any(name, alternates):

    "Return a named group pattern matching list of alternates."

    return "(?P<%s>" % name + "|".join(alternates) + ")"



def make_pat():

    kw = r"\b" + any("KEYWORD", keyword.kwlist) + r"\b"

    builtinlist = [str(name) for name in dir(__builtin__)

                                        if not name.startswith('_')]

    # self.file = file("file") :

    # 1st 'file' colorized normal, 2nd as builtin, 3rd as string

    builtin = r"([^.'\"\\#]\b|^)" + any("BUILTIN", builtinlist) + r"\b"

    comment = any("COMMENT", [r"#[^\n]*"])

    sqstring = r"(\b[rRuU])?'[^'\\\n]*(\\.[^'\\\n]*)*'?"

    dqstring = r'(\b[rRuU])?"[^"\\\n]*(\\.[^"\\\n]*)*"?'

    sq3string = r"(\b[rRuU])?'''[^'\\]*((\\.|'(?!''))[^'\\]*)*(''')?"

    dq3string = r'(\b[rRuU])?"""[^"\\]*((\\.|"(?!""))[^"\\]*)*(""")?'

    string = any("STRING", [sq3string, dq3string, sqstring, dqstring])

    return kw + "|" + builtin + "|" + comment + "|" + string +\

           "|" + any("SYNC", [r"\n"])



prog = re.compile(make_pat(), re.S)

idprog = re.compile(r"\s+(\w+)", re.S)

asprog = re.compile(r".*?\b(as)\b")



class ColorDelegator(Delegator):



    def __init__(self):

        Delegator.__init__(self)

        self.prog = prog

        self.idprog = idprog

        self.asprog = asprog

        self.LoadTagDefs()



    def setdelegate(self, delegate):

        if self.delegate is not None:

            self.unbind("<<toggle-auto-coloring>>")

        Delegator.setdelegate(self, delegate)

        if delegate is not None:

            self.config_colors()

            self.bind("<<toggle-auto-coloring>>", self.toggle_colorize_event)

            self.notify_range("1.0", "end")



    def config_colors(self):

        for tag, cnf in self.tagdefs.items():

            if cnf:

                self.tag_configure(tag, **cnf)

        self.tag_raise('sel')



    def LoadTagDefs(self):

        theme = idleConf.GetOption('main','Theme','name')

        self.tagdefs = {

            "COMMENT": idleConf.GetHighlight(theme, "comment"),

            "KEYWORD": idleConf.GetHighlight(theme, "keyword"),

            "BUILTIN": idleConf.GetHighlight(theme, "builtin"),

            "STRING": idleConf.GetHighlight(theme, "string"),

            "DEFINITION": idleConf.GetHighlight(theme, "definition"),

            "SYNC": {'background':None,'foreground':None},

            "TODO": {'background':None,'foreground':None},

            "BREAK": idleConf.GetHighlight(theme, "break"),

            "ERROR": idleConf.GetHighlight(theme, "error"),

            # The following is used by ReplaceDialog:

            "hit": idleConf.GetHighlight(theme, "hit"),

            }



        if DEBUG: print 'tagdefs',self.tagdefs



    def insert(self, index, chars, tags=None):

        index = self.index(index)

        self.delegate.insert(index, chars, tags)

        self.notify_range(index, index + "+%dc" % len(chars))



    def delete(self, index1, index2=None):

        index1 = self.index(index1)

        self.delegate.delete(index1, index2)

        self.notify_range(index1)



    after_id = None

    allow_colorizing = True

    colorizing = False



    def notify_range(self, index1, index2=None):

        self.tag_add("TODO", index1, index2)

        if self.after_id:

            if DEBUG: print "colorizing already scheduled"

            return

        if self.colorizing:

            self.stop_colorizing = True

            if DEBUG: print "stop colorizing"

        if self.allow_colorizing:

            if DEBUG: print "schedule colorizing"

            self.after_id = self.after(1, self.recolorize)



    close_when_done = None # Window to be closed when done colorizing



    def close(self, close_when_done=None):

        if self.after_id:

            after_id = self.after_id

            self.after_id = None

            if DEBUG: print "cancel scheduled recolorizer"

            self.after_cancel(after_id)

        self.allow_colorizing = False

        self.stop_colorizing = True

        if close_when_done:

            if not self.colorizing:

                close_when_done.destroy()

            else:

                self.close_when_done = close_when_done



    def toggle_colorize_event(self, event):

        if self.after_id:

            after_id = self.after_id

            self.after_id = None

            if DEBUG: print "cancel scheduled recolorizer"

            self.after_cancel(after_id)

        if self.allow_colorizing and self.colorizing:

            if DEBUG: print "stop colorizing"

            self.stop_colorizing = True

        self.allow_colorizing = not self.allow_colorizing

        if self.allow_colorizing and not self.colorizing:

            self.after_id = self.after(1, self.recolorize)

        if DEBUG:

            print "auto colorizing turned",\

                  self.allow_colorizing and "on" or "off"

        return "break"



    def recolorize(self):

        self.after_id = None

        if not self.delegate:

            if DEBUG: print "no delegate"

            return

        if not self.allow_colorizing:

            if DEBUG: print "auto colorizing is off"

            return

        if self.colorizing:

            if DEBUG: print "already colorizing"

            return

        try:

            self.stop_colorizing = False

            self.colorizing = True

            if DEBUG: print "colorizing..."

            t0 = time.clock()

            self.recolorize_main()

            t1 = time.clock()

            if DEBUG: print "%.3f seconds" % (t1-t0)

        finally:

            self.colorizing = False

        if self.allow_colorizing and self.tag_nextrange("TODO", "1.0"):

            if DEBUG: print "reschedule colorizing"

            self.after_id = self.after(1, self.recolorize)

        if self.close_when_done:

            top = self.close_when_done

            self.close_when_done = None

            top.destroy()



    def recolorize_main(self):

        next = "1.0"

        while True:

            item = self.tag_nextrange("TODO", next)

            if not item:

                break

            head, tail = item

            self.tag_remove("SYNC", head, tail)

            item = self.tag_prevrange("SYNC", head)

            if item:

                head = item[1]

            else:

                head = "1.0"



            chars = ""

            next = head

            lines_to_get = 1

            ok = False

            while not ok:

                mark = next

                next = self.index(mark + "+%d lines linestart" %

                                         lines_to_get)

                lines_to_get = min(lines_to_get * 2, 100)

                ok = "SYNC" in self.tag_names(next + "-1c")

                line = self.get(mark, next)

                ##print head, "get", mark, next, "->", repr(line)

                if not line:

                    return

                for tag in self.tagdefs.keys():

                    self.tag_remove(tag, mark, next)

                chars = chars + line

                m = self.prog.search(chars)

                while m:

                    for key, value in m.groupdict().items():

                        if value:

                            a, b = m.span(key)

                            self.tag_add(key,

                                         head + "+%dc" % a,

                                         head + "+%dc" % b)

                            if value in ("def", "class"):

                                m1 = self.idprog.match(chars, b)

                                if m1:

                                    a, b = m1.span(1)

                                    self.tag_add("DEFINITION",

                                                 head + "+%dc" % a,

                                                 head + "+%dc" % b)

                            elif value == "import":

                                # color all the "as" words on same line, except

                                # if in a comment; cheap approximation to the

                                # truth

                                if '#' in chars:

                                    endpos = chars.index('#')

                                else:

                                    endpos = len(chars)

                                while True:

                                    m1 = self.asprog.match(chars, b, endpos)

                                    if not m1:

                                        break

                                    a, b = m1.span(1)

                                    self.tag_add("KEYWORD",

                                                 head + "+%dc" % a,

                                                 head + "+%dc" % b)

                    m = self.prog.search(chars, m.end())

                if "SYNC" in self.tag_names(next + "-1c"):

                    head = next

                    chars = ""

                else:

                    ok = False

                if not ok:

                    # We're in an inconsistent state, and the call to

                    # update may tell us to stop.  It may also change

                    # the correct value for "next" (since this is a

                    # line.col string, not a true mark).  So leave a

                    # crumb telling the next invocation to resume here

                    # in case update tells us to leave.

                    self.tag_add("TODO", next)

                self.update()

                if self.stop_colorizing:

                    if DEBUG: print "colorizing stopped"

                    return



    def removecolors(self):

        for tag in self.tagdefs.keys():

            self.tag_remove(tag, "1.0", "end")



def main():

    from Percolator import Percolator

    root = Tk()

    root.wm_protocol("WM_DELETE_WINDOW", root.quit)

    text = Text(background="white")

    text.pack(expand=1, fill="both")

    text.focus_set()

    p = Percolator(text)

    d = ColorDelegator()

    p.insertfilter(d)

    root.mainloop()



if __name__ == "__main__":

    main()

