import utils
import subprocess
import os
import sys
import tempfile
import psutil
import logging
import rez_modifier_dialog
import StringIO
from rez.resolved_context import ResolvedContext
from Qt import QtCore, QtWidgets, QtGui
from wizart.desktop import utils
import main_window

log = logging.getLogger("wizart_desktop")


class AppLaunchTask(QtCore.QThread):
    def __init__(self):
        super(AppLaunchTask, self).__init__()
        self.proc = None
        self.returncode = None
        self.start_cmd = None
        self.start_env = None
        self.start_cwd = None
        self.rez_context = None
        self.show_shell = False
        self.redirect_output = False
        self.log = logging.getLogger("wizart_desktop")

    def run(self):
        kwargs = {}
        if self.show_shell is False:
            kwargs['startupinfo'] = utils.get_subprocess_startup_info()
        else:
            kwargs['detached'] = True
        if self.redirect_output:
            stdout_handle, stdout_path = tempfile.mkstemp()
            kwargs['stdout'] = stdout_handle
            kwargs['stderr'] = stdout_handle
        self.proc = self.rez_context.execute_shell(command=self.start_cmd,
                                                   parent_environ=self.start_env,
                                                   shell='cmd',
                                                   block=False,
                                                   **kwargs
                                                   )
        prefix_ = "[%s PID:%d] " % (os.path.basename(self.start_cmd[0]), self.proc.pid)
        if self.redirect_output:
            log_watcher = main_window.DesktopWindow.instance().log_watcher
            log_watcher.add_file(prefix_, stdout_path)
            self.returncode = self.proc.wait()
            if self.returncode is not None:
                self.log.info(prefix_ + " Exit with code:%d" % self.returncode)
            if stdout_handle:
                log_watcher.remove_file(prefix_)
                os.close(stdout_handle)
                

    def kill_proc(self):
        if self.proc and self.returncode is None and self.proc.poll() is None:
            prefix_ = "[%s PID:%d]" % (os.path.basename(self.start_cmd[0]), self.proc.pid)
            self.log.info("Kill %s" % prefix_)
            parent = psutil.Process(self.proc.pid)
            for child in parent.children(recursive=True):
                child.kill()
            parent.kill()
            self.proc = None
            self.returncode = None


class Application(QtCore.QObject):
    icon = ":wizart-animation.png"
    name = "default_app"
    shortcut_icon = "app_icon.ico"
    label = "Default App"
    def __init__(self, *args, **kwargs):
        super(Application, self).__init__(*args, **kwargs)
        self.log = logging.getLogger("wizart_desktop")

    @classmethod
    def get_subclasses(cls):
        return set(cls.__subclasses__()).union([s for c in cls.__subclasses__() for s in c.get_subclasses()])
    
    def launch(self, cmd_args = None):
        raise NotImplementedError()
    
    def is_allowed_for_user_hook(self):
        """
        For now we just remove from visibility if user don't have enough permissions
        """
        return True
    
    def create_desktop_shortcut(self):
        import app_icons
        desktop_folder = os.path.join(os.environ["USERPROFILE"], "Desktop")
        cmd_str = "%s --launch_app %s" % (utils.get_windows_desktop_executable_path(), self.name)
        desktop_path = os.path.join(desktop_folder, self.label + ".exe")
        if not os.path.exists(desktop_path):
            self.log.info("%s" % app_icons.get(self.shortcut_icon, "app_icon.ico"))
            utils.create_app_shortcut(desktop_path, cmd_str, app_icons.get(self.shortcut_icon, "app_icon.ico"))
            self.log.info("Created desktop shortcut for app '%s', at path '%s'" % (self.label, desktop_path))
        else:
            self.log.error("Cant create desktop shortcut for app '%s', because file exists '%s'" % (self.label, desktop_path))

    def create_desktop_shortcut_mimedata(self):
        import app_icons, tempfile
        mimeData = QtCore.QMimeData()
        temp_dir = tempfile.mkdtemp()
        cmd_str = "%s --launch_app %s" % (utils.get_windows_desktop_executable_path(), self.name)
        file_path = os.path.join(temp_dir, self.label + ".exe")
        utils.create_app_shortcut(file_path, cmd_str, app_icons.get(self.shortcut_icon, "app_icon.ico"))
        mimeData.setUrls([QtCore.QUrl.fromLocalFile(file_path)])
        return mimeData


class RezApplication(Application):
    external = True
    command = None
    show_shell = False
    redirect_output = False #performance penalty
    command_default_argument = None
    command_arguments = []
    executable_name = "default_app.exe"
    location_changed = QtCore.Signal()
    rez_packages = []
    rez_modifiers = []
    rez_modifier_is_used = False
    rez_modifier_changed = QtCore.Signal()
    extension = []
    def __init__(self, *args, **kwargs):
        super(Application, self).__init__(*args, **kwargs)
        self.launch_task = AppLaunchTask()
        self.log = logging.getLogger("wizart_desktop")
        self.load_rez_modifier()

    def scan_for_software(self):
        result = False
        self.log.info("Starting scan for '%s'")
        for entry in self.search_list:
            self.log.info("Scan for '%s' at path '%s'" % (self.label, entry))
            if os.path.exists(entry):
                self.set_executable_location(entry)
                self.log.info("Found '%s' at path '%s' " % (self.label, entry))
                result = True
                break
        if not result:
            self.log.warning("Not found '%s' at default locations" % self.label)
        return result

    def get_executable_location(self):
        from Qt import QtCore
        settings = QtCore.QSettings("Wizart Animation", "Desktop")
        return settings.value("apps/%s/location" % self.name)

    def set_executable_location(self, value):
        from Qt import QtCore
        settings = QtCore.QSettings("Wizart Animation", "Desktop")
        settings.setValue("apps/%s/location" % self.name, value)
        self.location_changed.emit()

    def set_executable_location_diag(self):
        filter_str = ";;".join(self.executable_name) if isinstance(self.executable_name, list) else self.executable_name
        path, _ = QtWidgets.QFileDialog.getOpenFileName(caption="Set Executable Path for '%s'" % self.label, filter=filter_str)
        if path:
            self.set_executable_location(path)

    def start_arguments_hook(self, env, executable_cmd):
        """
        Used for special cases, like max, to always extend process start arguments
        """
        return [executable_cmd]

    def environment_hook(self, project, env):
        """
        Used when app want to modify environment variables before start
        """
        pass

    def pre_launch_hook(self):
        """
        Used when app need to do smth before launch, e.g. detect malware
        """
        pass

    def save_rez_modifier(self):
        utils.save_rez_modifier(self.name,
                                self.rez_modifiers,
                                self.rez_modifier_is_used)

    def load_rez_modifier(self):
        self.rez_modifiers, self.rez_modifier_is_used = \
            utils.load_rez_modifier(self.name)

    def show_rez_modifier_dialog(self, request=None):
        if request is None:
            request = ResolvedContext(self.rez_packages)
        text_packages = ""
        for package in request.resolved_packages:
            text_packages += package.qualified_package_name + "\n"
        dialog = rez_modifier_dialog.RezModifierDialog(self.name, 
                                                       text_packages,
                                                       self.rez_modifiers,
                                                       self.rez_modifier_is_used)
        if dialog.exec_():
            self.rez_modifiers = dialog.rez_modifiers
            self.rez_modifier_is_used = dialog.rez_modifier_is_used
            self.save_rez_modifier()
            self.rez_modifier_changed.emit()
            if self.rez_modifiers != []:
                self.log.info("For app %(name)s sets REZ modifiers: %(modifiers)s" % dict(
                name=self.name, modifiers=" ".join(x for x in self.rez_modifiers)))

    def launch(self, cmd_args=None, debug_env = False):
        self.pre_launch_hook()
        import argparse
        parser = argparse.ArgumentParser(prog=self.executable_name, prefix_chars="+")
        parser.add_argument('+p', "++patch", type=str, nargs='*', metavar="PKG",
                            help="run the tool in a patched environment")

        opts, cmd_args = parser.parse_known_args(cmd_args)

        try:
            _env = os.environ.copy()
            _env.pop("PYTHONHOME", None)
            _env.pop("PYTHONPATH", None)
            _env['WIZART_DESKTOP'] = os.path.dirname(sys.executable)
            if hasattr(sys, 'freelance_build'):
                sync_path = utils.get_sync_path()
                if sync_path is None:
                    self.log.warning("Wizart Root Folder is not set!")
                elif not os.path.exists(sync_path):
                    self.log.warning(
                        "Wizart Root Folder pointing to folder '%s' that not exists!" % sync_path)
                _env['W'] = str(sync_path)
            else:
                _env['W'] = "W:"

            from rez.config import config
            _env["REZ_PACKAGES_PATH"] = ";".join(config.packages_path)

            project = main_window.DesktopWindow.instance().get_project()
            if project:
                self.log.info("Set project variables for project '%s'" % project.label)
                _env.update(project.env)
                _env["PROJECT_NAME"] = project.name
                _env["PROJECT_ROOT_PATH"] = project.root_path.replace("$W", _env["W"])
                _env["PROJECT_LABEL"] = project.label
                if project.is_series is True:
                    _env["PROJECT_IS_SERIES"] = "1"

                workspace_path = main_window.DesktopWindow.instance().get_workspace_path()
                if workspace_path:
                    if os.path.isdir(workspace_path):
                        _env["WORKSPACE"] = workspace_path.encode()
                        workspace_file = os.path.join(
                            workspace_path, 'workspace.json')
                        if os.path.isfile(workspace_file):
                            _env["WREF_RESOLVER_WORKSPACE"] = workspace_file.encode()
                        else:
                            self.log.warning('Workspace file not found')
                    else:
                        self.log.warning('The specified workspace folder was not found')
                else:
                    self.log.info('Workspace not set')

            self.environment_hook(project, _env)
            if self.external is True:
                executable_location = str(self.get_executable_location())
                if not os.path.exists(executable_location):
                    self.log.info("App %(name)s location not exist for path:'%(path)s'" % dict(
                        name=self.name, path=executable_location))
                    return
                #make quotues if we contain shells to make window shell happy
                executable_cmd = '"{}"'.format(executable_location)
            else:
                executable_location = ''
                executable_cmd = self.command

            request = self.rez_packages
            if self.rez_modifier_is_used:
                if self.rez_modifier_is_used and self.rez_modifiers != []:
                    self.log.info("App %(name)s use REZ Modifiers: %(modifiers)s" % dict(
                                   name=self.name, 
                                   modifiers=" ".join(x for x in self.rez_modifiers)))
                    if opts.patch is None:
                        opts.patch = self.rez_modifiers
                    else:
                        opts.patch += self.rez_modifiers
            if opts.patch is not None:
                import rez.utils.patching
                request = rez.utils.patching.get_patched_request(
                    request, opts.patch)
            self.log.info("REZ Resolve app context query: '{:s}'".format(request) )
            rez_context = ResolvedContext(request)
            info_buf = StringIO.StringIO()
            rez_context.print_info(buf=info_buf, verbosity = True)
            self.log.info("REZ Resolved app context: {:s}".format(info_buf.getvalue()))
            info_buf.close()

            self.launch_task.rez_context = rez_context
            self.launch_task.redirect_output = self.redirect_output
            cmd_start_arg_list = self.start_arguments_hook(
                rez_context.get_environ(_env), executable_cmd)
            cmd_start_arg_list += self.command_arguments
            if self.launch_task.isRunning():
                self.launch_task.terminate()
            if debug_env is True:
                #start shell in app environment useful for debugging
                self.launch_task.start_cmd = ["CMD.exe"]
            else:
                if cmd_args:
                    self.launch_task.start_cmd = cmd_start_arg_list + cmd_args
                else:
                    self.launch_task.start_cmd = cmd_start_arg_list
                    if self.command_default_argument is not None:
                        self.launch_task.start_cmd.append(
                            self.command_default_argument)

            self.launch_task.start_env = _env
            if self.external is True:
                self.launch_task.start_cwd = os.path.dirname(
                    executable_location)
            else:
                self.launch_task.start_cwd = None
            if debug_env is True:
                 self.launch_task.show_shell = True
            else:
                self.launch_task.show_shell = self.show_shell
            
            self.launch_task.start()
            self.log.info("Started app %(name)s from path:'%(cmd)s'" % dict(
                name=self.name, cmd=" ".join(self.launch_task.start_cmd)))

        except Exception as e:
            self.log.exception("Failed to start %s: " % self.name)


class WebLinkApplication(Application):
    rez_packages = []
    url = "http://google.com"
    def __init__(self, *args, **kwargs):
        super(WebLinkApplication, self).__init__(*args, **kwargs)

    def launch(self, cmd_args = None):
        result = QtGui.QDesktopServices.openUrl(QtCore.QUrl(self.url))
        if result is False:
            self.log.error("failed to open url in browser '{:s}'".format(self.url) )
        else:
            self.log.info("opened url in browser '{:s}'".format(self.url) ) 
