'''
PyMOL Plugins Engine, Graphical Plugin Manager

(c) 2011-2012 Thomas Holder, PyMOL OS Fellow
License: BSD-2-Clause

'''

import sys
import Pmw
if sys.version_info[0] == 2:
    import Tkinter
    import tkSimpleDialog
else:
    import tkinter as Tkinter
    import tkinter.simpledialog as tkSimpleDialog
from .legacysupport import tkMessageBox, tkFileDialog
from . import pref_get

default_pad = {'padx': 5, 'pady': 5}
default_top = {'fill': 'x', 'anchor': 'n', 'side': 'top'}
default_top.update(default_pad)

def confirm_network_access():
    '''
    Popup dialog with network access notification (only once per session)
    '''
    self = confirm_network_access
    if self.ok < 0:
        tkMessageBox.showinfo('Info',
                'Network download has been disabled, sorry!')
        return False
    if self.ok > 0:
        return True
    self.ok = tkMessageBox.askyesno('Confirm', 'PyMOL will now download'
            ' executable code from the internet! Proceed?')
    return self.ok

# valid values: 1=never ask, 0=ask once per session, -1=network access disabled
confirm_network_access.ok = pref_get('network_access_ok', 0)

def manager_dialog():
    '''
    Create the Plugin Manager dialog (with Pmw)
    '''
    from .legacysupport import get_tk_root
    dialog = PluginManager(get_tk_root())

def plugin_info_dialog(parent, info):
    '''
    Shows a popup with plugin info (name, metadata, doc-string, ...)
    '''
    dialog = Pmw.MegaToplevel(parent,
            title = 'Info about plugin ' + info.name)
    grid = dialog.interior()

    highlighted_labels = ['Citation', 'Citation-Required']

    bg_important = '#ff6666'
    bg_notice = '#ffff99'

    def add_line(label, text):
        bg = None
        if label in highlighted_labels:
            bg = bg_important
        row = grid.grid_size()[1]
        Tkinter.Label(grid, text=label + ':', bg=bg).grid(row=row, column=0, sticky='nw', padx=5, pady=2)
        e = Tkinter.Entry(grid)
        e.insert(0, str(text))
        e.config(state='readonly')
        e.grid(row=row, column=1, sticky='nwe', padx=5, pady=2)

    if info.get_citation_required():
        Tkinter.Label(grid, text='This Plugin requires a citation. See below for details.',
                bg=bg_important, padx=10, pady=10).grid(columnspan=2, sticky='nesw')

    add_line('Name', info.name)
    if not info.is_temporary:
        add_line('Python Module Name', info.mod_name)
        add_line('Filename', info.filename)
    grid.columnconfigure(1, weight=1)

    metadata = info.get_metadata()
    for label, value in metadata.items():
        add_line(label, value)

    if not info.is_temporary:
        if not info.loaded:
            Tkinter.Label(grid, text='more information might be available after plugin is loaded',
                    bg=bg_notice, padx=10, pady=10).grid(columnspan=2, sticky='nesw')
        else:
            add_line('commands', ', '.join(info.commands))

    docstring = info.get_docstring()
    if docstring is not None:
        st = Pmw.ScrolledText(grid, text_wrap='none', text_padx = 4, text_pady = 4)
        st.appendtext(docstring.strip())
        st.configure(text_state='disabled')
        st.grid(columnspan=2, sticky='nesw', padx=5, pady=2)
        grid.rowconfigure(grid.grid_size()[1] - 1, weight=1)
    elif info.loaded or info.is_temporary:
        Tkinter.Label(grid, text='no documentation available',
                bg=bg_notice, padx=10, pady=10).grid(columnspan=2, sticky='nesw')

class PluginManager(Pmw.MegaToplevel):
    '''
    Graphical Plugin Manager Dialog
    '''

    def __init__(self, parent):
        self.super = self.__class__.__bases__[0]
        self.super.__init__(self, parent, title='Plugin Manager')

        self.minsize(600, 400)
        master = self.interior()

        # save button (only show if not in "instantsave" mode)
        from . import pref_get, pref_save
        def c_save():
            pref_save()
            b_save.configure(background=b_save_background)
        b_save = Tkinter.Button(master, text='Save Settings', command=c_save)
        b_save_background = b_save.cget('background')
        PluginManager.b_save = b_save
        if not pref_get('instantsave', True):
            b_save.pack(side='bottom', **default_pad)

        # main tabs
        notebook = Pmw.NoteBook(master)
        notebook.pack(fill='both', expand=1, padx=5, pady=5)

        self.page_installed(notebook)
        self.page_install_new(notebook)
        self.page_settings(notebook)
        self.page_about(notebook)

    def page_installed(self, notebook):
        page = notebook.add('Installed Plugins')

        # frames
        f_filter = Tkinter.Frame(page)
        f_installed = InstalledPluginsWidget(page)

        def filter_command():
            f_installed.filter(e_filter.getvalue())

        # filter items
        Tkinter.Checkbutton(f_filter, text='startup', variable=f_installed.v_fstartup, command=filter_command).pack(side='right')
        Tkinter.Checkbutton(f_filter, text='loaded', variable=f_installed.v_floaded, command=filter_command).pack(side='right')
        e_filter = Pmw.EntryField(f_filter, labelpos='w', label_text='Filter:', modifiedcommand=filter_command)
        e_filter.pack(side='left', expand=1, fill='x')

        # enable/disable all items
        f_all = Tkinter.Frame(page)
        Tkinter.Button(f_all, text='startup all', command=f_installed.startup_all).pack(side='left')
        Tkinter.Button(f_all, text='startup none', command=f_installed.startup_none).pack(side='left')

        # pack
        f_filter.pack(**default_top)
        f_all.pack(side='bottom', anchor='w', **default_pad)
        f_installed.pack(side='top', fill='both', expand=1, **default_pad)

        self.f_installed = f_installed

    def page_install_new(self, notebook):
        from pymol import Scratch_Storage, CmdException

        page = notebook.add('Install New Plugin')

        # local file

        def c_local():
            from .legacysupport import installPlugin, get_pmgapp
            installPlugin(get_pmgapp())
            self.f_installed.reload()

        w = Pmw.Group(page, tag_text='Install from local file')
        w.pack(**default_top)

        b_local = Tkinter.Button(w.interior(), text='Choose file...', command=c_local)
        b_local.pack(side='left', **default_pad)

        if confirm_network_access.ok < 0:
            Tkinter.Label(page,
                    text='Network access has been disabled').pack(expand=1)
            return

        # pymolwiki

        def fetchplugin():
            if not confirm_network_access():
                return

            from .installation import installPluginFromFile
            from .repository import fetchscript
            url = e_wiki.get()
            if not len(url):
                return
            import tempfile, shutil
            tmpdir = tempfile.mkdtemp()
            try:
                filename = fetchscript(url, tmpdir, False)
            except CmdException as e:
                tkMessageBox.showerror('Error', 'Fetching Plugin failed.\n' + str(e))
                return

            if filename:
                installPluginFromFile(filename, self.interior())
            shutil.rmtree(tmpdir)
            self.f_installed.reload()

        w = Pmw.Group(page, tag_text='Install from PyMOLWiki or any URL')
        w.pack(**default_top)
        w = w.interior()

        l_repo = Tkinter.Label(w,
                text='Paste a link to a script or plugin, or a PyMOLWiki url which then will be downloaded and '
                'scanned for scripts that extend the PyMOL API', justify='left', anchor='w', wraplength=500)
        l_repo.pack(**default_top)

        e_wiki = Pmw.EntryField(w, labelpos='w', label_text='URL:', command=fetchplugin)
        e_wiki.pack(side='left', fill='x', expand=1, **default_pad)
        b_wiki = Tkinter.Button(w, text='Fetch',
                command=fetchplugin)
        b_wiki.pack(side='right', **default_pad)

        # repository

        w = Pmw.Group(page, tag_text='Install from Repository')
        w.pack(fill='both', **default_pad)

        pw = Pmw.PanedWidget(w.interior(), orient='horizontal')

        pane_left = pw.add('left', min=.2)
        pane_right = pw.add('right', min=.2, max=.5)

        repo_tmp = Scratch_Storage()
        def selecmd_left():
            '''
            Get plugins listing for selected repository.
            '''
            if not confirm_network_access():
                return

            from .repository import guess
            sels = slb_left.getcurselection()
            if len(sels) == 0:
                slb_right.setlist(['- empty -'])
                return
            try:
                url = sels[0]
                repo_tmp.r = guess(url)
                slb_right.setlist(repo_tmp.r.list())
            except:
                slb_right.setlist(['- listing failed -'])

        def infocmd_right():
            '''
            Download file, parse for metadata, show info-popup and delete file
            '''
            from . import PluginInfo
            from .installation import get_name_and_ext, extract_zipfile, zip_extensions
            sels = slb_right.getcurselection()
            if len(sels) == 0:
                return
            import tempfile, shutil, os
            tmpdir = tempfile.mkdtemp()
            tmpdirs = [tmpdir]
            try:
                name = sels[0]
                filename = repo_tmp.r.copy(name, tmpdir)
                name, ext = get_name_and_ext(filename)
                if ext in zip_extensions:
                    tmpdir, dirnames = extract_zipfile(filename, ext)
                    tmpdirs.append(tmpdir)
                    name = dirnames[-1]
                    filename = os.path.join(os.path.join(tmpdir, *dirnames), '__init__.py')
                info = PluginInfo(name, filename)
                plugin_info_dialog(self.interior(), info)
            except:
                tkMessageBox.showinfo('Error', 'Could not get plugin info')
            finally:
                for tmpdir in tmpdirs:
                    shutil.rmtree(tmpdir)

        def selecmd_right():
            '''
            Download plugin from repository and install it.
            '''
            from .installation import installPluginFromFile, get_plugdir
            sels = slb_right.getcurselection()
            if len(sels) == 0:
                return
            plugdir = get_plugdir(self.interior())
            if not plugdir:
                return
            import tempfile, shutil, os
            tmpdir = tempfile.mkdtemp()
            try:
                for name in sels:
                    filename = repo_tmp.r.copy(name, tmpdir)
                    installPluginFromFile(filename, self.interior(), plugdir)
            except:
                err = str(sys.exc_info()[1])
                tkMessageBox.showinfo('Error', 'Could not install plugin ' + name + '\n\n' + err)
            finally:
                shutil.rmtree(tmpdir)
            self.f_installed.reload()

        slb_left = Pmw.ScrolledListBox(pane_left, items=(
                'http://pldserver1.biochem.queensu.ca/~rlc/work/pymol/',
                'https://github.com/Pymol-Scripts/Pymol-script-repo',

                # for testing
                'http://www.thomas-holder.de/projects/pymol/repository/',
                ),
            listbox_height=30, labelpos='nw', label_text='Repositories',
            selectioncommand=selecmd_left)

        slb_right = Pmw.ScrolledListBox(pane_right, items=(),
            listbox_selectmode=Tkinter.EXTENDED,
            listbox_height=30, labelpos='nw', label_text='Items',)

        repo_bb_left = Pmw.ButtonBox(pane_left)
        repo_bb_right = Pmw.ButtonBox(pane_right)

        def dummy_command():
            showinfo = tkMessageBox.showinfo
            showinfo('Dummy', 'Not implemented', parent=self.interior())

        def slb_left_setlist(items):
            slb_left.setlist(items)
            self.b_save.configure(background='red')

        def slb_left_add():
            url = tkSimpleDialog.askstring('Repository URL', 'Please enter Repository URL', parent=self.interior())
            if not url:
                return
            items = list(slb_left.get())
            items.append(url)
            slb_left_setlist(items)
            slb_left.setvalue([url]) # provide list to avoid unicode problem

        def slb_left_remove():
            v = slb_left.getvalue()
            items = list(slb_left.get())
            for item in v:
                items.remove(item)
            slb_left_setlist(items)

        repo_bb_left.add('Add ...', command=slb_left_add)
        repo_bb_left.add('Remove', command=slb_left_remove)
        repo_bb_right.add('Info', command=infocmd_right)
        repo_bb_right.add('Install', command=selecmd_right)

        repo_bb_left.pack(side='bottom', fill='x',)
        repo_bb_right.pack(side='bottom', fill='x',)

        slb_left.pack(fill='both', **default_pad)
        slb_right.pack(fill='both', **default_pad)
        pw.pack(fill='both')

    def page_settings(self, notebook):
        page = notebook.add('Settings')

        from . import get_startup_path, set_startup_path

        # plugin search path
        w = Pmw.Group(page, tag_text='Plugin override search path')
        w.pack(**default_top)

        def slb_path_setlist(items):
            set_startup_path(items)
            slb_path.setlist(items)
            self.b_save.configure(background='red')

        def slb_path_add():
            import os
            from .installation import get_default_user_plugin_path as userpath
            d = tkFileDialog.askdirectory(initialdir=userpath(), parent=self.interior())
            if not len(d):
                return
            if not os.path.exists(d):
                os.makedirs(d)
            items = list(slb_path.get())
            items.append(d)
            slb_path_setlist(items)
            slb_path.setvalue([d]) # provide list to avoid unicode problem

        def slb_path_remove():
            v = slb_path.getvalue()
            items = list(slb_path.get())
            for item in v:
                items.remove(item)
            slb_path_setlist(items)

        def slb_path_move(j=1):
            v = slb_path.getvalue()
            if len(v) == 0:
                return
            items = list(slb_path.get())
            i = items.index(v[0])
            if not (0 <= i+j < len(items)):
                return
            items.pop(i)
            items.insert(i+j, v[0])
            slb_path_setlist(items)
            slb_path.setvalue([v[0]]) # provide list to avoid unicode problem

        slb_path = Pmw.ScrolledListBox(w.interior(),
                items=get_startup_path(True), listbox_height=4)
        if slb_path.get(0):
            slb_path.setvalue([slb_path.get(0)]) # provide list to avoid unicode problem
        slb_path.pack(**default_top)

        bb_path = Pmw.ButtonBox(w.interior())
        bb_path.add('Add new directory...', command=slb_path_add)
        bb_path.add('Remove', command=slb_path_remove)
        bb_path.add('Move up', command=lambda: slb_path_move(-1))
        bb_path.add('Move down', command=slb_path_move)
        bb_path.pack(side='top', fill='x')

        l_path = Tkinter.Label(w.interior(),
                text='PyMOL restart required in order to find plugins in modified plugin search path')
        l_path.pack(**default_top)

        w = Pmw.Group(page, tag_text='Preferences (Read-Only)')
        w.pack(**default_top)
        w = w.interior()

        from . import preferences
        for row, key in enumerate(preferences):
            value = str(preferences[key])
            if len(value) > 100:
                value = value[:97] + '...'
            Tkinter.Label(w, text=key).grid(row=row, column=0, sticky='nw', padx=3, pady=2)
            e = Tkinter.Entry(w)
            e.insert(0, value)
            e.config(state='readonly')
            e.grid(row=row, column=1, sticky='nwe', padx=5, pady=2)
        w.columnconfigure(1, weight=1)

    def page_about(self, notebook):
        page = notebook.add('About')

        st = Pmw.ScrolledText(page, text_wrap='word')
        st.appendtext('Plugins are external modules which extend PyMOL\'s capabilities.\n\n'
                'Plugins can provide new commands and/or add menu items to the "Plugin" menu.\n\n'
                'For technical details, visit\n'
                'http://pymolwiki.org/index.php/PluginArchitecture\n'
                'http://pymolwiki.org/index.php/Script_Tutorial\n')
        st.configure(text_state='disabled')
        st.pack(fill='both', expand=1, **default_pad)

class InstalledPluginsWidget(Pmw.ScrolledFrame):
    '''
    Scrolled widget that shows all installed plugins.
    '''

    def __init__(self, parent):
        self.super = self.__class__.__bases__[0]
        self.super.__init__(self, parent,
                vscrollmode='static',
                horizflex='expand')

        # filter variables
        self.v_floaded  = Tkinter.BooleanVar(parent, False)
        self.v_fstartup = Tkinter.BooleanVar(parent, False)

        self.reload()

    def reload(self):
        from . import plugins

        for child in self.children():
            child.pack_forget()
            child.destroy()

        for info in sorted(plugins.values(), key=lambda i: i.name.lower()):
            PluginWidget(self, info)

        # pack children
        self.filter()

        self.bind_mousewheel()

    def children(self):
        return self.interior().winfo_children()

    def filter(self, text=''):
        '''
        Pack all children that match filter criteria.
        '''
        for child in self.children():
            child.pack_forget()

        for child in self.children():
            if text.lower() not in child.info.name.lower():
                continue
            if self.v_fstartup.get() and not child.info.autoload:
                continue
            if self.v_floaded.get() and not child.info.loaded:
                continue
            child.pack()

    def bind_mousewheel(self):
        '''
        Enable scrolling with mouse wheel.
        '''
        delta = 0.1

        def scrollUp(event=None):
            self.yview('scroll', -delta, 'pages')

        def scrollDown(event=None):
            self.yview('scroll', delta, 'pages')

        def bind_rec(other):
            if hasattr(other, 'interior'):
                other = other.interior()
            other.bind('<Button-4>', scrollUp)
            other.bind('<Button-5>', scrollDown)
            for child in other.winfo_children():
                bind_rec(child)
        bind_rec(self)

    def startup_all(self):
        for child in self.children():
            child.w_startup.select()
            if not child.info.autoload:
                child.c_startup(False)

    def startup_none(self):
        for child in self.children():
            child.w_startup.deselect()
            if child.info.autoload:
                child.c_startup(False)

class PluginWidget(Tkinter.Frame):
    '''
    Item of InstalledPluginsWidget that represents a installed plugin.
    '''

    def __init__(self, parent, info):
        self.info = info

        if hasattr(parent, 'interior'):
            parent = parent.interior()

        self.super = self.__class__.__bases__[0]
        self.super.__init__(self, parent,
                borderwidth=1,
                padx=5, pady=5,
                relief=Tkinter.SUNKEN)

        topmaster = self

        master = Tkinter.Frame(topmaster)
        master.pack(side=Tkinter.TOP, fill='x')

        w_title = Tkinter.Label(master, text=info.name)
        w_title.pack(side=Tkinter.LEFT)

        self.w_version = Tkinter.Label(master, text='', foreground='gray50')
        self.w_version.pack(side=Tkinter.LEFT)

        self.w_loadtime = Tkinter.Label(master, text='', foreground='gray50')
        self.w_loadtime.pack(side=Tkinter.RIGHT)

        master = Tkinter.Frame(topmaster)
        master.pack(side=Tkinter.TOP, fill='x')

        self.w_info = Tkinter.Button(master, text='Info', command=self.plugin_info)
        self.w_info.pack(side=Tkinter.LEFT)

        self.w_enable = Tkinter.Button(master, text='Load', command=self.plugin_load)
        self.w_enable.pack(side=Tkinter.LEFT)

        self.v_startup = Tkinter.BooleanVar(master, self.info.autoload)
        self.w_startup = Tkinter.Checkbutton(master, text='Load on startup',
                variable=self.v_startup, command=self.c_startup)
        self.w_startup.pack(side=Tkinter.LEFT)

        self.f_settings = Tkinter.Frame(master)
        self.f_settings.pack(side=Tkinter.LEFT)

        w_uninstall = Tkinter.Button(master, text='Uninstall', command=self.plugin_remove)
        w_uninstall.pack(side=Tkinter.RIGHT)

        self.status_update()

    def c_startup(self, askload=True):
        '''
        Callback for "autoload" checkbox.
        '''
        self.info.autoload = self.v_startup.get()
        if askload and self.info.autoload and not self.info.loaded:
            if tkMessageBox.askyesno('Confirm', 'Load plugin now?', parent=self):
                self.plugin_load()
        PluginManager.b_save.configure(background='red')

    def pack(self):
        self.super.pack(self, fill='x', anchor=Tkinter.N)

    def plugin_info(self):
        plugin_info_dialog(self, self.info)

    def plugin_load(self):
        self.info.load()
        self.status_update()

    def plugin_remove(self):
        if self.info.uninstall(self):
            self.pack_forget()
            self.destroy()

    def status_update(self):
        '''
        Update contents of this item that may have changed after loading plugin.
        '''
        version = self.info.get_version()
        if version:
            text = 'version ' + self.info.get_version()
            self.w_version.config(text=text)

        if self.info.loaded:
            text = 'Took %.3f seconds to load' % (self.info.loadtime)
            self.w_loadtime.config(text=text)
            self.w_enable.config(state=Tkinter.DISABLED)

            # add 'Settings' button
            if hasattr(self.info.module, 'settings_dialog') \
                    and not len(self.f_settings.winfo_children()):
                Tkinter.Button(self.f_settings, text='Settings',
                        command=self.info.module.settings_dialog).pack()

# vi:expandtab:smarttab:sw=4:nowrap
