# -*- coding: utf-8 -*-
#
# This file is part of EventGhost.
# Copyright © 2005-2016 EventGhost Project <http://www.eventghost.net/>
#
# EventGhost is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 2 of the License, or (at your option)
# any later version.
#
# EventGhost is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along
# with EventGhost. If not, see <http://www.gnu.org/licenses/>.

import wx
import re
import os
import ast
import sys
import base64
import compileall
import __builtin__

from cStringIO import StringIO
from zipfile import ZIP_DEFLATED, ZipFile


PLUGIN_PATH = 'C:\\ProgramData\\EventGhost\\plugins'
SAVE_PATH = ''
PLUGIN_DIR_LIST = os.listdir(PLUGIN_PATH)
INFO_FIELDS = [
    "name",
    "author",
    "version",
    "url",
    "guid",
    "description",
    "icon",
]

APP = wx.App()


def StripText(text):
    return re.sub(r'[\W]+', '', text)


class eg(object):
    def __init__(self):
        sys.modules['eg'] = self
        __builtin__.eg = self

eg = eg()


class Plugin(object):

    def __init__(self, path, sourceCode):
        self.sourceCode = sourceCode
        self.path = path
        self.pluginName = ''

        for line in sourceCode.split('\n'):
            line = line.strip()
            if not line.startswith('class '):
                continue

            found = line.endswith('eg.PluginBase):')
            if not found:
                found = line.endswith('eg.RawReceiverPlugin):')
            if not found:
                found = line.endswith('eg.PluginClass):')
            if found:
                self.pluginName = line[6:line.find('(')]

        setattr(sys.modules['eg'], 'RegisterPlugin', self.RegisterPlugin)

    def RegisterPlugin(
            self,
            name=None,
            description=None,
            kind="other",
            author="unknown author",
            version="unknown version",
            icon=None,
            canMultiLoad=False,
            createMacrosOnAdd=False,
            url=None,
            help=None,
            guid="",
            hardwareId="",
            **kwargs
    ):
        if name is None:
            name = self.pluginName
        if description is None:
            description = name

        self.help = help

        if help is not None:
            help = "\n".join([s.strip() for s in help.splitlines()])
            help = help.replace("\n\n", "<p>")
            description += "\n\n<p>" + help
        self.name = self.englishName = unicode(name)
        self.description = self.englishDescription = unicode(description)
        self.kind = unicode(kind)
        self.author = (
            unicode(", ".join(author)) if isinstance(author, tuple)
            else unicode(author)
        )
        self.version = unicode(version)
        self.url = unicode(url) if url else url  # Added by Pako
        self.guid = guid.upper()
        if not guid:
            print "missing guid in plugin: %s" % self.path
            self.guid = self.pluginName
        self.hardwareId = hardwareId.upper()
        # get the icon if any
        if icon is None:
            iconPath = os.path.join(self.path, "icon.png")
            if os.path.isfile(iconPath):
                from PIL import Image
                icon = Image.open(iconPath).convert("RGBA")
        else:
            from PIL import Image
            stream = StringIO(base64.b64decode(icon))
            icon = Image.open(stream).convert("RGBA")
            stream.close()

        if icon is not None:
            self.icon = base64.b64encode(str(icon.tobytes()))
        else:
            self.icon = icon

        self.canMultiLoad = canMultiLoad
        self.createMacrosOnAdd = createMacrosOnAdd

        raise RegisterPluginException


def Export(pluginInfo):
    pluginName = StripText(pluginInfo.englishName.replace("/", "-"))
    target = os.path.join(
        SAVE_PATH,
        '%s - %s.egplugin' % (
            pluginName,
            pluginInfo.version
        )
    )

    source = pluginInfo.path

    zipfile = ZipFile(target, "w", ZIP_DEFLATED)
    sourceCode = ''

    for fieldName in INFO_FIELDS:
        pluginData = getattr(pluginInfo, fieldName)
        sourceCode += fieldName + ' = %s\n'
        if pluginData:
            sourceCode %= repr(pluginData)
        else:
            sourceCode %= 'None'

    zipfile.writestr("info.py", sourceCode)
    for dirpath, dirnames, filenames in os.walk(source):
        for dirname in dirnames[:]:
            if dirname.startswith("."):
                dirnames.remove(dirname)
        for filename in filenames:
            ext = os.path.splitext(filename)[1]
            if (
                ext.lower() in (".pyc", ".pyo") and
                filename[:-1] in filenames
            ):
                continue
            src = os.path.join(dirpath, filename)
            dst = StripText(pluginName) + src.replace(source, '')
            zipfile.write(src, dst)

    zipfile.close()


def Import(pluginPath):
    files = os.listdir(pluginPath)

    if '__init__.py' in files:
        pluginInit = os.path.join(pluginPath, "__init__.py")
        plugin = open(pluginInit, "r")
        sourceCode = plugin.read()
        pluginInfo = Plugin(pluginPath, sourceCode)
        try:
            exec (sourceCode)
        except RegisterPluginException:
            pass
        except:
            return None
        plugin.close()
        compileall.compile_dir(pluginPath, ddir="UserPlugin", quiet=True)
        return pluginInfo


class SafeExecParser(object):
    @classmethod
    def Parse(cls, source):
        return cls().Visit(ast.parse(source))

    def Visit(self, node, *args):
        meth = getattr(self, 'Visit' + node.__class__.__name__)
        return meth(node, *args)

    def VisitAssign(self, node, parent):
        value = self.Visit(node.value)
        for target in node.targets:
            parent[self.Visit(target)] = value

    def VisitModule(self, node):
        mod = {}
        for child in node.body:
            self.Visit(child, mod)
        return mod

    def VisitName(self, node):
        if isinstance(node.ctx, ast.Load):
            if node.id in ("True", "False", "None"):
                return getattr(__builtin__, node.id)
        return node.id

    def VisitStr(self, node):
        return node.s


class RegisterPluginException(Exception):
    """
    RegisterPlugin will raise this exception to interrupt the loading
    of the plugin module file.
    """
    pass


class ExportPlugin(wx.Frame):

    def __init__(self):
        wx.Frame.__init__(
            self,
            None,
            -1,
            size=(400, 363),
            title='Export Plugin',
            style=(
                wx.MINIMIZE_BOX |
                wx.MAXIMIZE_BOX |
                wx.RESIZE_BORDER |
                wx.SYSTEM_MENU |
                wx.CAPTION |
                wx.CLOSE_BOX |
                wx.CLIP_CHILDREN
            )
        )

        choices = list(
            Import(os.path.join(PLUGIN_PATH, choice))
            for choice in PLUGIN_DIR_LIST
        )

        choices = list(
            [choice.name, choice] for choice in choices
            if choice is not None
        )

        sizer = wx.BoxSizer(wx.VERTICAL)

        choiceSizer = wx.BoxSizer(wx.HORIZONTAL)
        st = wx.StaticText(self, -1, 'Choose Plugin:')
        choiceCtrl = wx.Choice(
            self,
            -1,
            choices=list(choice[0] for choice in choices)
        )
        choiceSizer.Add(st, 0, wx.EXPAND | wx.ALL, 5)
        choiceSizer.Add(choiceCtrl, 0, wx.EXPAND | wx.ALL, 5)

        textFields = [
            'name',
            'description',
            'kind',
            'author',
            'version',
            'icon',
            'canMultiLoad',
            'createMacrosOnAdd',
            'url',
            'help',
            'guid',
            'hardwareId',
        ]

        infoValue = ''
        for attrName in textFields:
            infoValue += attrName + ' = \n'

        infoST = wx.StaticText(self, -1, 'Plugin Information')

        infoCtrl = wx.TextCtrl(
            self,
            -1,
            value=infoValue[:-1],
            style=(
                wx.TE_MULTILINE |
                wx.TE_DONTWRAP |
                wx.TE_READONLY
            ),
            size=(150, 215)
        )

        buttonRow = ButtonRow(self)

        sizer.Add(choiceSizer, 0, wx.EXPAND | wx.ALIGN_CENTER)
        sizer.Add(infoST, 0, wx.EXPAND | wx.ALIGN_CENTER | wx.ALL, 5)
        sizer.Add(infoCtrl, 0, wx.EXPAND | wx.ALIGN_CENTER | wx.ALL | 5)
        sizer.Add(buttonRow, 0, wx.ALIGN_RIGHT)

        def OnChoice(evt):
            selection = choiceCtrl.GetSelection()
            info = ''

            plugin = choices[selection][1]
            for attrName in textFields:
                info += attrName + ' = %s\n'
                attr = getattr(plugin, attrName)

                if isinstance(attr, basestring):
                    info %= repr(attr)
                else:
                    info %= str(attr)
            infoCtrl.SetValue(info[:-1])

            evt.Skip()
            self.Layout()
            self.Refresh()
        choiceCtrl.Bind(wx.EVT_CHOICE, OnChoice)

        def OnOk(evt):
            plugin = choices[choiceCtrl.GetSelection()][1]
            Export(plugin)
            evt.Skip()
        buttonRow.okButton.Bind(wx.EVT_BUTTON, OnOk)

        def OnCancel(evt):
            self.Show(False)
            self.Destroy()
            APP.ExitMainLoop()
        buttonRow.cancelButton.Bind(wx.EVT_BUTTON, OnCancel)
        self.Bind(wx.EVT_CLOSE, OnCancel)

        self.SetSizer(sizer)


class ButtonRow(wx.BoxSizer):
    def __init__(self, parent):

        wx.BoxSizer.__init__(self, wx.HORIZONTAL)

        buttonSizer = wx.StdDialogButtonSizer()

        self.okButton = okButton = wx.Button(
            parent,
            wx.ID_OK,
            'OK'
        )
        self.cancelButton = cancelButton = wx.Button(
            parent,
            wx.ID_CANCEL,
            'Cancel'
        )

        buttonSizer.AddButton(okButton)
        buttonSizer.AddButton(cancelButton)
        buttonSizer.Realize()
        okButton.SetDefault()

        self.Add((3, 3), 1)
        self.Add(buttonSizer, 0, wx.TOP | wx.BOTTOM, 6)
        self.Add((3, 3), 0)

if SAVE_PATH:
    export = ExportPlugin()
    export.Show(True)
    APP.MainLoop()
else:
    print 'You need to set the SAVE_PATH variable before use.'
