#! /usr/bin/env python

##    """
##    Copyright(C) 2011 The Board of Trustees of the University of Illinois.
##    All rights reserved.
##
##    Developed by:   Roger D. Serwy
##                    University of Illinois
##
##    Permission is hereby granted, free of charge, to any person obtaining
##    a copy of this software and associated documentation files (the
##    "Software"), to deal with the Software without restriction, including
##    without limitation the rights to use, copy, modify, merge, publish,
##    distribute, sublicense, and/or sell copies of the Software, and to
##    permit persons to whom the Software is furnished to do so, subject to
##    the following conditions:
##
##    + Redistributions of source code must retain the above copyright
##      notice, this list of conditions and the following disclaimers.
##    + Redistributions in binary form must reproduce the above copyright
##      notice, this list of conditions and the following disclaimers in the
##      documentation and/or other materials provided with the distribution.
##    + Neither the names of Roger D. Serwy, the University of Illinois, nor
##      the names of its contributors may be used to endorse or promote
##      products derived from this Software without specific prior written
##      permission.
##
##    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
##    OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
##    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
##    IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR
##    ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
##    CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
##    THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE SOFTWARE.
##
##
##    """



# This module hotpatches EditorWindow.py to load idlex extensions properly

from __future__ import print_function
import sys

from idlexlib.extensionManager import extensionManager

import idlelib
import os
import __main__
import imp
import traceback
import re

from idlelib import macosxSupport

version = "1.18"   # IdleX version

IDLE_DEFAULT_EXT = []    # list of default extensions that IDLE has


if sys.version < '3':
    from StringIO import StringIO
    from Tkinter import *
    import Tkinter as tkinter
    import tkFileDialog
    import tkMessageBox
else:
    from io import StringIO
    from tkinter import *
    import tkinter
    import tkinter.filedialog as tkFileDialog
    import tkinter.messagebox as tkMessageBox


from idlelib.configHandler import idleConf, IdleConfParser

ansi_re = re.compile(r'\x01?\x1b\[(.*?)m\x02?')
def strip_ansi(s):
    return ansi_re.sub("", s)


def install_idlex_manager():
    """ install IDLEX extension manager into IDLE """

    # 2011-11-15 Bugfix - change the user config file names for IdleX
    # to avoid a problem on Windows where pythonw.exe refuses to run
    # idle.pyw when an error occurs. However python.exe runs idle.py just fine.
    # See http://bugs.python.org/issue13582
    u = idleConf.userCfg
    for key, value in list(u.items()):
        # add "idlex-" to user config file names
        fullfile = value.file
        directory, filename = os.path.split(fullfile)
        if filename.startswith('idlex-'):
            new_filename = filename
        else:
            new_filename = 'idlex-' + filename
        new_fullfile = os.path.join(directory, new_filename)
        value.file = new_fullfile
        value.Load()

    mod = extensionManager.load_extension('idlexManager')
    mod.extensionManager = extensionManager
    mod.version = version
    mod.update_globals()


    # add idlex to the extension list
    e = idleConf.userCfg['extensions']
    if not e.has_section('idlexManager'):
        e.add_section('idlexManager')
    e.set('idlexManager', 'enable', '1')


def _printExt():
    a = []
    for i in idleConf.defaultCfg['extensions'].sections():
        if i.endswith('_cfgBindings') or i.endswith('_bindings'):
            continue
        a.append(i)
    print('Extensions: %s' % a)



###########################################################################
##
## HOTPATCHING CODE
##
###########################################################################

def fix_tk86():
    tkinter._Tk = tkinter.Tk
    def wrapper(func, name):
        Tcl_Obj = tkinter._tkinter.Tcl_Obj
        def f(*args, **kwargs):
            #print(name, 'wrapped', args, kwargs)
            #t = [i for i in args if isinstance(i, Tcl_Obj)]
            #for i in t:
            #    print(name, 'FOUND arg:', repr(i), type(i), str(i))

            args = [i if not isinstance(i, Tcl_Obj) else str(i)
                    for i in args]
            for key, value in kwargs.items():
                if isinstance(value, Tcl_Obj):
                    #print(name, 'FOUND kwarg:', key, value)
                    kwargs[key] = str(value)
            return func(*args, **kwargs)
        return f

    class TkReflector(object):
        def __init__(self, tk):
            self.tk = tk
        def __getattribute__(self, name):
            a = getattr(object.__getattribute__(self, 'tk'), name)
            if name in ['splitlist']:
            #if hasattr(a, '__call__'):
                return wrapper(a, name)
            else:
                return a

    class TkFix(tkinter.Tk):
        def __init__(self, *args, **kwargs):
            tkinter._Tk.__init__(self, *args, **kwargs)
            self.__tk = self.tk
            version = self.tk.call('info', 'patchlevel')
            if version.startswith('8.6'):
                self.tk = TkReflector(self.__tk)

    tkinter.Tk = TkFix



def _hotpatch():
    # Fix numerous outstanding IDLE issues...
    import idlelib.EditorWindow


    EditorWindowOrig = idlelib.EditorWindow.EditorWindow
    class EditorWindow(EditorWindowOrig):


        _invalid_keybindings = []  # keep track of invalid keybindings encountered
        _valid_keybindings = []

        # Work around a bug in IDLE for handling events bound to menu items.
        # The <<event-variables>> are stored globally, not locally to
        # each editor window. Without this, toggling a checked menu item
        # in one editor window toggles the item in ALL editor windows.
        # Issue 13179
        def __init__(self, flist=None, filename=None, key=None, root=None):
            if flist is not None:
                flist.vars = {}
            EditorWindowOrig.__init__(self, flist, filename, key, root)

        # FIXME: Do not transfer custom keybindings if IDLE keyset is set to default

        # Fix broken keybindings that has plagued IDLE for years.
        # Issue 12387, 4765, 13071, 6739, 5707, 11437
        def apply_bindings(self, keydefs=None):  # SUBCLASS to catch errors
            #return EditorWindowOrig.apply_bindings(self, keydefs)
            if keydefs is None:
                keydefs = self.Bindings.default_keydefs
            text = self.text
            text.keydefs = keydefs
            invalid = []
            for event, keylist in keydefs.items():
                for key in keylist:
                    try:
                        text.event_add(event, key)
                    except TclError as err:
                        #print(' Apply bindings error:', event, key)
                        invalid.append((event, key))
            if invalid:  # notify errors
                self._keybinding_error(invalid)


        def RemoveKeybindings(self):  # SUBCLASS to catch errors
            "Remove the keybindings before they are changed."
            EditorWindow._invalid_keybindings = []
            # Called from configDialog.py
            self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
            for event, keylist in keydefs.items():
                for key in keylist:
                    try:
                        self.text.event_delete(event, key)
                    except Exception as err:
                        print(' Caught event_delete error:', err)
                        print(' For %s, %s' % (event, key))
                        pass

            for extensionName in self.get_standard_extension_names():
                xkeydefs = idleConf.GetExtensionBindings(extensionName)
                if xkeydefs:
                    for event, keylist in xkeydefs.items():
                        for key in keylist:
                            try:
                                self.text.event_delete(event, key)
                            except Exception as err:
                                print(' Caught event_delete error:', err)
                                print(' For %s, %s' % (event, key))
                                pass


        def _keybinding_error(self, invalid):
            """ Create an error message about keybindings. """

            new_invalid = [i for i in invalid if i not in EditorWindow._invalid_keybindings]
            if new_invalid:
                msg = ['There are invalid key bindings:', '']
                for ev, k in new_invalid:
                    while ev[0] == '<' and ev[-1] == '>':
                        ev = ev[1:-1]
                    msg.append('Action:%s' % ev)
                    msg.append('Key:%s' % k)
                    msg.append('')

                msg.extend(['Please reconfigure these bindings.'])
                def errormsg(msg=msg):
                    tkMessageBox.showerror(title='Invalid Key Bindings',
                                           message='\n'.join(msg),
                                           master=self.top,
                                           parent=self.top)

                EditorWindow._invalid_keybindings.extend(new_invalid)
                self.top.after(100, errormsg)

        def load_standard_extensions(self):
            for name in self.get_standard_extension_names():

                try:
                    if name in extensionManager.IDLE_EXTENSIONS:
                        self.load_extension(name)
                    else:
                        self.load_idlex_extension(name)
                except:
                    print("Failed to load extension", repr(name))
                    import traceback
                    traceback.print_exc()

        def load_idlex_extension(self, name):
            # import from idlex
            mod = extensionManager.load_extension(name)
            if mod is None:
                print("\nFailed to import IDLEX extension: %s" % name)
                return

            cls = getattr(mod, name)
            keydefs = idleConf.GetExtensionBindings(name)
            if hasattr(cls, "menudefs"):
                self.fill_menus(cls.menudefs, keydefs)
            ins = cls(self)

            self.extensions[name] = ins
            if keydefs:
                self.apply_bindings(keydefs)
                for vevent in keydefs.keys():
                    methodname = vevent.replace("-", "_")
                    while methodname[:1] == '<':
                        methodname = methodname[1:]
                    while methodname[-1:] == '>':
                        methodname = methodname[:-1]
                    methodname = methodname + "_event"
                    if hasattr(ins, methodname):
                        self.text.bind(vevent, getattr(ins, methodname))

    idlelib.EditorWindow.EditorWindow = EditorWindow

def macosx_workaround():
    # restore "Options" menu on MacOSX
    if not macosxSupport.runningAsOSXApp():
        return

    def restore(menu_specs):
        c = [a for a,b in menu_specs]
        if "options" not in c:
            menu_specs.insert(-2, ("options", "Options"))

    import idlelib.EditorWindow
    restore(idlelib.EditorWindow.EditorWindow.menu_specs)

    import idlelib.PyShell
    restore(idlelib.PyShell.PyShell.menu_specs)


class devnull:
    # For pythonw.exe on Windows...
    def __init__(self):
        pass
    def write(self, *args, **kwargs):
        pass
    def __getattr__(self, *args, **kwargs):
        return self.write

def pythonw_workaround():
    # Work around a bug in pythonw.exe that prevents IdleX from starting.
    if sys.stderr is None:
        sys.stderr = devnull()
    if sys.stdout is None:
        sys.stdout = devnull()


def main():

    pythonw_workaround()
    _hotpatch()
    fix_tk86()

    try:
        macosx_workaround()
    except:
        pass  # Tk on OSX should not be this fragile...

    install_idlex_manager()

    extensionManager.load_idlex_extensions()

    # Force a reset on Bindings...
    # Without this, user reconfiguration of the key bindings within IDLE may
    # generate an error on MultiCall unbind.
    import idlelib.Bindings
    idlelib.Bindings.default_keydefs = idleConf.GetCurrentKeySet()

    import idlelib.PyShell
    idlelib.PyShell.main()

if __name__ == '__main__':
    # start up IDLE with IdleX
    main()