"""
Drag-n-drop this into a maya viewport to install packages

This is copied from:
https://github.com/Nathanieljla/cg3d-maya-core/blob/main/src/cg3dguru/utils/drop_installer.py

If you want to modify this script for your own purposes it's better
to pull a copy from the source location.
"""

__author__ = "Nathaniel Albright"
__email__ = "developer@3dcg.guru"

VERSION = (0, 9, 0)
__version__ = '.'.join(map(str, VERSION))



import os
import re
import time
import platform
import sys
import webbrowser
import base64
#import math
#from datetime import datetime, timedelta
import glob
import tempfile
import shutil
import sys
import subprocess
from os.path import expanduser
import zipfile
from functools import partial
import site

try:
    #python3
    from urllib.request import urlopen
except:
    #python2
    from urllib import urlopen

try:
    #python2
    reload
except:
    #python3
    from importlib import reload

try:
    import maya.utils
    import maya.cmds
    from maya import OpenMayaUI as omui
    
    from PySide2.QtCore import *
    from PySide2.QtWidgets import *
    from PySide2.QtGui import *
    from shiboken2 import wrapInstance
    MAYA_RUNNING = True
except ImportError:
    MAYA_RUNNING = False
    

RESOURCES = None
    
class Platforms(object):
    OSX = 0,
    LINUX = 1,
    WINDOWS = 2
    
    @staticmethod
    def get_name(enum_value):
        if enum_value == Platforms.OSX:
            return 'osx'
        elif enum_value == Platforms.LINUX:
            return 'linux'
        else:
            return 'windows'
        
        
        
class Commandline(object):
    @staticmethod        
    def get_python_version():
        """Get the running version of python as a tuple of 3 ints"""
        pmax, pmin, patch =  sys.version.split(' ')[0].split('.')
        return( int(pmax), int(pmin), int(patch))
    
    
    @staticmethod
    def get_platform():
        result = platform.platform().lower()
        if 'darwin' in result:
            return Platforms.OSX
        elif 'linux' in result:
            return Platforms.LINUX
        elif 'window' in result:
            return Platforms.WINDOWS
        else:
            raise ValueError('Unknown Platform Type:{0}'.format(result))
    
    
    @staticmethod    
    def run_shell_command(cmd, description):
        #NOTE: don't use subprocess.check_output(cmd), because in python 3.6+ this error's with a 120 code.
        print('\n{0}'.format(description))
        print('Calling shell command: {0}'.format(cmd))

        proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        stdout, stderr = proc.communicate()
        stdout = stdout.decode()
        stderr = stderr.decode()
        
        print(stdout)
        print(stderr)
        if proc.returncode:
            raise Exception('Command Failed:\nreturn code:{0}\nstderr:\n{1}\n'.format(proc.returncode, stderr))
        
        return(stdout, stderr)
    
        
    @staticmethod
    def get_python_paths():
        """Returns maya's python path and location of a global pip
        
        Note: The pip path might not exist on the system.
        """
        python_path = ''
        pip_path = ''
        pmax, pmin, patch = Commandline.get_python_version()
        platform = Commandline.get_platform()
        
        version_str = '{0}.{1}'.format(pmax, pmin)
        if platform == Platforms.WINDOWS:
            python_path = os.path.join(os.getenv('MAYA_LOCATION'), 'bin', 'mayapy.exe')
            if pmax > 2:
                #python3 pip path
                pip_path = os.path.join(os.getenv('APPDATA'), 'Python', 'Python{0}{1}'.format(pmax, pmin), 'Scripts', 'pip{0}.exe'.format(version_str))
            else:
                #python2 pip path
                pip_path = os.path.join(os.getenv('APPDATA'), 'Python', 'Scripts', 'pip{0}.exe'.format(version_str))

        elif platform == Platforms.OSX:
            python_path = '/usr/bin/python'
            pip_path = os.path.join( expanduser('~'), 'Library', 'Python', version_str, 'bin', 'pip{0}'.format(version_str) )
     
        elif platform == Platforms.LINUX:
            python_path = os.path.join(os.getenv('MAYA_LOCATION'), 'bin', 'mayapy')
            pip_path = os.path.join( expanduser('~'), '.local', 'bin', 'pip{0}'.format(version_str) )
             
        return (python_path, pip_path)
    
    
    
    @staticmethod
    def get_command_string():
        """returns a commandline string for launching pip commands
        
        If the end-user is on linux then is sounds like calling pip from Mayapy
        can cause dependencies issues when using a default python install.
        So if the user is on osX or windows OR they're on linux and don't
        have python installed, then we'll use "mayapy -m pip" else we'll
        use the pipX.exe to run our commands.        
        """
        python_path, pip_path = Commandline.get_python_paths()
        platform = Commandline.get_platform()        

        command = '{0}&-m&pip'.format(python_path)
        global_pip = False
        if platform == Platforms.LINUX:
            try:
                #I don't use "python" here, because on windows that opens the MS store vs. erroring.
                #No clue what it might do on linux
                Commandline.run_shell_command(['py'], 'Checking python install')
                command = pip_path
                global_pip = True
            except:
                #Python isn't installed on linux, so the default command is good
                pass
            
        return (command,  global_pip)

                
    @staticmethod
    def pip_install(package, pip_args = [], *args, **kwargs):
        pip_command, global_pip = Commandline.get_command_string()
        cmd_str = ('{0}&install&{1}').format(pip_command, package)
        args = cmd_str.split('&') + pip_args
        stdout, stderr = Commandline.run_shell_command(args, 'PIP:Installing Package')
        
        return stdout
    
    
    @staticmethod
    def pip_uninstall(package, pip_args = [], *args, **kwargs):
        pip_command, global_pip = Commandline.get_command_string()
        cmd_str = ('{0}&uninstall&{1}').format(pip_command, package)
        args = cmd_str.split('&') + pip_args
        stdout, stderr = Commandline.run_shell_command(args, 'PIP:Installing Package')
        
        return stdout
    

    @staticmethod
    def pip_list(pip_args = [], *args, **kwargs):
        pip_command, global_pip  = Commandline.get_command_string()
        cmd_str = ('{0}&list').format(pip_command)
        args = cmd_str.split('&') + pip_args
        stdout, stderr = Commandline.run_shell_command(args, 'PIP:Listing Packages')
        
        return stdout    


    @staticmethod
    def pip_show(repo_name, pip_args = [], *args, **kwargs):
        pip_command, global_pip  = Commandline.get_command_string()
        cmd_str = ('{0}&show&{1}').format(pip_command, repo_name)
        args = cmd_str.split('&') + pip_args
        stdout, stderr = Commandline.run_shell_command(args, 'PIP:Show Package Info')
        
        return stdout        
        

        
class ModuleDefinition(object):
    """A .mod file can have multiple entries.  Each definition equates to one entry"""
    
    MODULE_EXPRESSION = r"(?P<action>\+|\-)\s*(MAYAVERSION:(?P<maya_version>\d{4}))?\s*(PLATFORM:(?P<platform>\w+))?\s*(?P<module_name>\w+)\s*(?P<module_version>\d+\.?\d*.?\d*)\s+(?P<module_path>.*)\n(?P<defines>(?P<define>.+(\n?))+)?"
        
    def __init__(self, module_name, module_version,
                 maya_version = '', platform = '',
                 action = '+', module_path = '',
                 defines = [],
                 *args, **kwargs):
        
        self.action = action
        self.module_name = module_name
        self.module_version = module_version
        
        self.module_path = r'.\{0}'.format(self.module_name)
        if module_path:
            self.module_path = module_path

        self.maya_version = maya_version
        if self.maya_version is None:
            self.maya_version = ''
        
        self.platform = platform
        if self.platform is None:
            self.platform = ''
        
        self.defines = defines
        if not self.defines:
            self.defines = []
        
    def __str__(self):
        return_string = '{0} '.format(self.action)
        if self.maya_version:
            return_string += 'MAYAVERSION:{0} '.format(self.maya_version)
            
        if self.platform:
            return_string += 'PLATFORM:{0} '.format(self.platform)
            
        return_string += '{0} {1} {2}\n'.format(self.module_name, self.module_version, self.module_path)
        for define in self.defines:
            if define:
                return_string += '{0}\n'.format(define.rstrip('\n'))
         
        return_string += '\n'    
        return return_string



class ModuleManager(QThread):
    """Used to edit .mod files quickly and easily."""
    
    def __init__(self, module_name, module_version, package_name='',
                 include_site_packages = False):
        
        QThread.__init__(self)
        self.install_succeeded = False
        
        self._module_definitions = []
        self.module_name = module_name
        self.module_version = module_version
        
        self.package_name = package_name
        if not self.package_name:
            self.package_name = self.module_name
        
        self.maya_version = self.get_app_version()
        self.platform = Commandline.get_platform()
        
        self.max, self.min, self.patch = Commandline.get_python_version()
        
        #common locations
        self._version_specific = self.is_version_specific()  
        self.app_dir = os.getenv('MAYA_APP_DIR')
        self.install_root = self.get_install_root()
        self.relative_module_path = self.get_relative_module_path()
        self.module_path = self.get_module_path()
        self.icons_path = self.get_icon_path()
        self.presets_path = self.get_presets_path()
        self.scripts_path = self.get_scripts_path()
        self.plugins_path = self.get_plugins_path()
        
        self.site_packages_path = self.get_site_package_path()
        if not include_site_packages:
            self.site_packages_path = ''
            
        self.package_install_path = self.get_package_install_path()
        
        #Non-Maya python and pip paths are needed for installing on linux (and OsX?)
        self.python_path, self.pip_path = Commandline.get_python_paths()
        self.command_string, self.uses_global_pip = Commandline.get_command_string()
     
    
    def __del__(self):
        #TODO: Determine why I put a wait on this fuction 
        self.wait()
 
       
    @staticmethod
    def get_app_version():
        """What version of Maya is this?"""
        return int(str(maya.cmds.about(apiVersion=True))[:4])
    
    
    @staticmethod
    def get_platform_string(platform):
        """Convert the current Platform value to a Module string"""
        if platform is Platforms.OSX:
            return 'mac'
        elif platform is Platforms.LINUX:
            return 'linux'
        else:
            return 'win64'
    
    
    @staticmethod
    def make_folder(folder_path):
        print(folder_path)
        
        if not os.path.exists(folder_path):
            os.makedirs(folder_path)


    @staticmethod
    def get_ui_parent():
        return wrapInstance( int(omui.MQtUtil.mainWindow()), QMainWindow )      
   
    
    @staticmethod
    def package_installed(package_name):
        """returns True if the repo is already on the system"""
        
        return Commandline.pip_list().find(package_name) != -1
    

    @staticmethod
    def package_outdated(package_name):
        """Check to see if a local package is outdated
        
        Checks to see the local pacakge is out-of-date.  This will always
        be true with remote packages that are from Git, but will be accurate
        with packages registered on PyPi. Since package_outdated()
        assumes the package exists before checking make sure you you first
        check the existance of the package with package_installed() before
        checking the outdated status.
        
        Returns:
        --------
        bool
            True if missing or outdated, else False
        """
        #TODO: get version checking to work with git packages.
        #https://stackoverflow.com/questions/11560056/pip-freeze-does-not-show-repository-paths-for-requirements-file
        #https://github.com/pypa/pip/issues/609
        #it looks like we'd have to install packages with pip -e for this to work,
        #but then we can't install to a target dir. I'm getting errors about
        #trying to install in maya/src, but --user doesn't work either.

        #I'm using --uptodate here, because both --uptodate and --outdated
        #will be missing the package if the pacakage isn't registered with PyPi
        #so -uptodate is easier to verify with than -o with remote package that
        # might or might not be registered with PyPi
        
              
        result = Commandline.pip_list(pip_args =['--uptodate'])
        outdated = result.find(package_name) == -1
        if outdated:
            return True
        else:
            return False
    

    def install_remote_package(self, package_name = '', to_module = True):
        if not package_name:
            package_name = self.get_remote_package()
        
        #https://stackoverflow.com/questions/39365080/pip-install-editable-with-a-vcs-url
        #github = r'https://github.com/Nathanieljla/fSpy-Maya.git'
        
        pip_args = []
        if to_module:
            pip_args = [
                #r'--user', 
                #r'--editable=git+{0}#egg={1}'.format(github, self.repo_name), 
                r'--target={0}'.format(self.scripts_path), 
            ]
        Commandline.pip_install(package_name, pip_args)
    
    
    def get_remote_package(self):
        """returns the github or PyPi name needed for installing"""
        maya.cmds.error( "No Package name/github path defined.  User needs to override Module_manager.get_remote_package()" )

        
    def __ensure_pip_exists(self):
        """Make sure OS level pip is installed
        
        This is written to work with all platforms, but
        I've updated this to only run when we're on linux
        because it sounds like that's the only time it's needed
        """
        
        if not self.uses_global_pip:
            print("Using Maya's PIP")
            return
        
        if os.path.exists(self.pip_path):
            print('Global PIP found')
            return
        
        tmpdir = tempfile.mkdtemp()
        get_pip_path = os.path.join(tmpdir, 'get-pip.py')
        print(get_pip_path)
        
        if self.platform == Platforms.OSX:
            #cmd = 'curl https://bootstrap.pypa.io/pip/{0}/get-pip.py -o {1}'.format(pip_folder, pip_installer).split(' ')
            cmd = 'curl https://bootstrap.pypa.io/pip/get-pip.py -o {0}'.format(get_pip_path).split(' ')
            Commandline.run_shell_command(cmd, 'get-pip')

        else:
            # this should be using secure https, but we should be fine for now
            # as we are only reading data, but might be a possible mid attack
            #response = urlopen('https://bootstrap.pypa.io/pip/{0}/get-pip.py'.format(pip_folder))
            response = urlopen('https://bootstrap.pypa.io/pip/get-pip.py')
            data = response.read()
            
            with open(get_pip_path, 'wb') as f:
                f.write(data)
                
        # Install pip
        # On Linux installing pip with Maya Python creates unwanted dependencies to Mayas Python version, so pip might not work 
        # outside of Maya Python anymore. So lets install pip with the os python version. 
        filepath, filename = os.path.split(get_pip_path)
        #is this an insert, so this pip is found before any other ones?
        sys.path.insert(0, filepath)
        
        
        if self.platform == Platforms.OSX or self.platform == Platforms.LINUX:
            python_str = 'python{0}.{1}'.format(self.max, self.min)
        else:
            python_str = self.python_path
            
        cmd = '{0}&{1}&--user&pip'.format(python_str, get_pip_path).split('&')
        Commandline.run_shell_command(cmd, 'install pip')
        
        print('Global PIP is ready for use!')
        
        
    def is_version_specific(self):
        """Is this install for a specific version of Maya?
        
        Some modules might have specific code for different versions of Maya.
        For example if Maya is running Python 3 versus. 2. get_relative_module_path()
        returns a different result when this True vs.False unless overridden by
        the user.
        
        Returns:
        --------
        bool
            False
        """        
        
        return False
     
    def get_install_root(self):
        """Where should the module's folder and defintion install?
        
        Maya has specific locations it looks for module defintitions os.getenv('MAYA_APP_DIR')
        For windows this is "documents/maya/modules" or "documents/maya/mayaVersion/modules"
        However 'userSetup' files can define alternative locations, which is
        good for shared modules in a production environment.
        
        Returns:
        --------
        str
            os.path.join(self.app_dir, 'modules')
        """        
        return os.path.join(self.app_dir, 'modules')
    
    def get_relative_module_path(self):
        """What's the module folder path from the install root?
        
        From the install location we can create a series of folder to reach
        the base of our module.  This is where Maya will look for the
        'plug-ins', 'scripts', 'icons', and 'presets' dir.  At a minimum
        you should return the name of your module. The default implementation
        create as a path of 'module-name'/platforms/maya-version/platform-name/x64
        when is_version_specific() returns True
        
        Returns:
        str
            self.module_name when is_version_specific() is False
        
        """
        root = self.module_name
        if self._version_specific:
            root = os.path.join(self.module_name, 'platforms', str(self.maya_version),
                                Platforms.get_name(self.platform),'x64')  
        return root
    
    def get_module_path(self):
        return os.path.join(self.install_root, self.relative_module_path)
    
    def get_icon_path(self):
        return os.path.join(self.module_path, 'icons')
    
    def get_presets_path(self):
        return os.path.join(self.module_path, 'presets')
    
    def get_scripts_path(self):
        return os.path.join(self.module_path, 'scripts')
    
    def get_plugins_path(self):
        return os.path.join(self.module_path, 'plug-ins')
    
    def get_site_package_path(self):
        return os.path.join(self.scripts_path, 'site-packages')
    
    def get_package_install_path(self):
        return os.path.join(self.scripts_path, self.module_name)
    
  
    def read_module_definitions(self, path):
        self._module_definitions = []
        if (os.path.exists(path)):
            file = open(path, 'r')
            text = file.read()
            file.close()
          
            for result in re.finditer(ModuleDefinition.MODULE_EXPRESSION, text):
                resultDict = result.groupdict()
                if resultDict['defines']:
                    resultDict['defines'] = resultDict['defines'].split("\n")
                    
                definition = ModuleDefinition(**resultDict)
                self._module_definitions.append(definition)
      
                        
    def write_module_definitions(self, path):
        file = open(path, 'w')
        for entry in self._module_definitions:
            file.write(str(entry))
        
        file.close()

                           
    def __get_definitions(self, search_list, key, value):
        results = []
        for item in search_list:
            if item.__dict__[key] == value:
                results.append(item)
                
        return results
        
          
    def _get_definitions(self, *args, **kwargs):
        result_list = self._module_definitions
        for i in kwargs:
            result_list = self.__get_definitions(result_list, i, kwargs[i])
        return result_list
    
    
    def remove_definitions(self, *args, **kwargs):
        """
        removes all definitions that match the input argument values
        example : module_manager_instance.remove_definitions(module_name='generic', platform='win', maya_version='2023')
        
        Returns:
        --------
        list
            the results that were removed from the manager.
        
        """ 
        results = self._get_definitions(**kwargs)
        for result in results:
            self._module_definitions.pop(self._module_definitions.index(result))
            
        return results
    
    
    def remove_definition(self, entry):
        self.remove_definitions(module_name=entry.module_name,
                                platform=entry.platform, maya_version=entry.maya_version)
    
    def add_definition(self, definition):
        """

        """
        #TODO: Add some checks to make sure the definition doesn't conflict with an existing definition
        self._module_definitions.append(definition)
        
   
    def run(self):
        """this starts the QThread"""
        try:
            self.install_succeeded = self.install()
        except Exception as e:
            self.install_succeeded = False
            print('Install Failed!!\n{0}'.format(e))
            
                 
    def get_definition_entry(self):
        """Converts this class into a module_defintion
        
        Returns:
        --------
        Module_definition
            The module defintion that represents the data of the Module_manager
        
        """
        maya_version = str(self.maya_version)
        relative_path = '.\{0}'.format(self.relative_module_path)        
        platform_name =  self.get_platform_string(Commandline.get_platform())
        
        if not self._version_specific:
            maya_version = ''
            platform_name = ''
            
        defines = []
        if self.site_packages_path:
            site_path =  'PYTHONPATH+:={0}'.format(self.site_packages_path.split(self.module_path)[1])
            defines = [site_path]
        
        module_definition = ModuleDefinition(self.module_name, self.module_version,
                                                             maya_version=maya_version, platform=platform_name, 
                                                             module_path=relative_path,
                                                             defines=defines)
        return module_definition
     
             
    def update_module_definition(self, filename):
        """remove old defintions and adds the current defintion to the .mod
        
        Returns:
        --------
        bool
            True if the update was successful else False        
        """
        new_entry = self.get_definition_entry()
        self.remove_definition(new_entry) #removes any old entries that might match before adding the new one
        self.add_definition(new_entry)  
        try:
            self.write_module_definitions(filename)
        except IOError:
            return False
        
        return True
        

    def pre_install(self):
        """Called before install() to do any sanity checks and prep
        
        This function attempts to create the required install folders
        and update/add the .mod file. Sub-class should call this function
        when overriding

        Returns:
        --------
        bool
            true if the install can continue
        """
        try:
            self.__ensure_pip_exists()

        except Exception as e:
            print('failed to setup global pip {0}'.format(e))
            return False
        
        try:          
            self.make_folder(self.module_path)       
            self.make_folder(self.icons_path)
            self.make_folder(self.presets_path)
            self.make_folder(self.scripts_path)
            self.make_folder(self.plugins_path)
            
            if self.site_packages_path:
                self.make_folder(self.site_packages_path)
        except OSError:
            return False

        filename = os.path.join(self.install_root, (self.module_name + '.mod'))
        self.read_module_definitions(filename)
              
        return self.update_module_definition(filename)
    

    def install(self):
        """The main install function users should override"""        
        installed = False
        if not self.package_installed(self.package_name):

            try:
                self.install_remote_package()
                installed = True
            except:
                pass
            
        return installed
    
    
    def post_install(self):
        """Used after install() to do any clean-up

        """  
        print('post install')
        if self.install_succeeded:
            if self.scripts_path not in sys.path:
                sys.path.append(self.scripts_path)
                print('Add scripts path [{}] to system paths'.format(self.scripts_path))
            else:
                print('scripts path in system paths')
                
                
    def install_pymel(self):
        """Installs pymel to a common Maya location"""
        if not self.package_installed('pymel'):
            Commandline.pip_install('pymel')
        
    
    
##-----begin UI----##
    
class IconButton(QPushButton):
    def __init__(self, text, highlight=False, icon=None, success=False, *args, **kwargs):
        super(IconButton, self).__init__(QIcon(icon), text, *args, **kwargs)

        self.icon = icon
        self.highlight = highlight
        self.success = success
        self.setMinimumHeight(34)
        self.setCursor(QCursor(Qt.PointingHandCursor))
        if self.highlight:
            font = self.font()
            font.setPointSize(14)
            font.setBold(True)
            self.setFont(font)

        if self.success:
            font = self.font()
            font.setPointSize(14)
            font.setBold(True)
            self.setFont(font)

        if self.icon:
            self.setIconSize(QSize(22, 22))
            self.setIcon(QIcon(self.AlphaImage()))

    def AlphaImage(self):
        if self.highlight and not self.success:
            AlphaImage = QPixmap(self.icon)
            painter = QPainter(AlphaImage)

            painter.setCompositionMode(QPainter.CompositionMode_SourceIn)
            painter.fillRect(AlphaImage.rect(), '182828')

            return AlphaImage

        else:
            return QPixmap(self.icon)
        
     

class Resources(object):
    preloaderAnimBase64 = ''' '''
    BYTE_GIF = QByteArray.fromBase64( preloaderAnimBase64.encode() )
    
    company_base64 = None
    install_base64 = '''iVBORw0KGgoAAAANSUhEUgAAAFgAAABYCAYAAABxlTA0AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEwAACxMBAJqcGAAAA2NJREFUeJztnN9RGzEQh3+ryTvuIHQAqSCOzn4OJUAFoYO4BKeCpAR4PkvjdGA6oATTgDYvx4xjwv3x7Z7kyX5v3N5I6w8h6eQ9AMMwDMMwDMMwDMMwDMMwDON/gHIn0JcY4+fDn733v3PlMoTiBdd1feGcewYwO7zOzNvFYvElT1b9cbkT6MI5t8aRXAAgonkI4XuGlAZRvGAANy2x+8myOJFzEPxm9PaMFcE5CD5rTLAyJlgZE6yMCVbGBCtjgpX5oNHoZrP5hr8fEPbOuZX3/kmjv6HEGK9SSuvDa865tff+Ubov8bOIGONXZn74R+g5pXS9XC5fhrQXQuC2eFVVgz5Dc7axA3B5FNoT0Vx6EIhPESml1TuhSyLa1nV9Id1nX+q6viCiLd7KBYDZ8aiWQFwwEV23xXJJfpXbkd9cut/JF7kckvvI1UJcMDNvu+6ZUvIQuX1yH4q4YOfcPYB9131TSB44cvdN7qKIC/bePzVzWVbJQ+Vq7CAApTk4t+RS5AKKi1wuySXJBZR3EVNLLk0uMME2bSrJJcoFJtoHa0suVS4wcV1EjPGq2Wt2flnJzDtmnjvnWn8pKaVZqXKBDIUnQyV3ietzT8PkcoFMlT1DJAuRRS6QsXRqQsnZ5AKZa9MmkJxVLlBA8Z+i5OxygQIEAyqSi5ALFCIYEJVcjFygIMGAiOSi5AKFCQZGSS5OLiAoWLLE/wTJo+VqvaIwWrBWif8AyaPkar+iMPqwR6vEv+cB0eiRq/2KgsRpmlqJfyP59p2w1Jyr+oqChGDVEn/v/SMRXTPz7vUaM29TSpdCC5pq/iq1adI0Ij/lzuMU1A/cY4xX2n2cyvHOQYPRgruKNVJK87F9aJFSap1jJQpRRk8RRPTcEV9tNhsw86+hlZVahBA+MvOaiNoWOBDRri3eh9GCmXnbstIDwIyI1kS0DiGM7U4Mou5HgKYScxQSU8QDenyZeYbsJQqyRwtu/uzF62pzw8wriXZEdhEppfXhPvXcYebdYrH4IdGWiODlcvnCzHON8s+pYeYtM8+l2hM/rgwh/ARwK93uRPyqqupOskHxB42qqu6Y+RZntvAx8720XEDxwL0pZ7oBcNOcihX3rweaKe2hpD26YRiGYRiGYRiGYRiGYRiGYRiGYRjD+AN6etoeN+hk/AAAAABJRU5ErkJggg=='''
    close_base64 = '''iVBORw0KGgoAAAANSUhEUgAAAFgAAABYCAYAAABxlTA0AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEwAACxMBAJqcGAAAA7lJREFUeJztm8tx2zAQhv9lA3YHdiqwO4iGpM5xB6Y7UAdRKohSQeQO5DMfo3Qgd6B0IDfAzSHgjOwhJQDCgvTMfleCXPIbEI8FACiKoiiKoiiKoiiKoiiKoiiKoiiKoijKECT14LqubwAUzDwDACLatW27nM/nb1IxbWia5o6ZF8x8a95rT0SrNE1fJeKJCK7r+ieARc+ldZZlTxIxbSjL8oqItkR0//EaEd1LSA4u2NSQ3Yki67ZtF7Frcl3XN8y86ZNr2GdZ9iV03CT0A5n54UyRgoi2ZVlehY49RNM0dwB2J+QCwK1EbAnBh3NliOg+lmTzR20BXEvH6iO44CRJtjblYkh2kXumWfMmuOA0TV+ZeWNTlojukyTZm184KFVVPRpptjV3HfodAAHBAMDMhUONuGbmbUjJVVU9EtHa4ZZ1nue/QsU/RkTwfD5/Y+aZ+T1tCCbZQ+5KcugoNtHoqOv6N4DCsviBmRd5nj97xhoaf/fCzIVvLFvEBQPOkr0+PEYMH6IIBmQFTFUuEFEw4NU+LrMs+zF00Ux910R0bnLTcSCimVTeoY+oggG/Hr6vEzqVVxggulxgBMHA5ZI/i1xgJMEA0DTNVzMhsZ4ItG27SJLk+kzS5h3MvCOihyzL/vq/rT+jCQbc8wRG1q1LeWaejZmDHlUwIJeMmYJcQGgm50Kapq9ENAuZbGHmzRTkAhOowR0eHdcQo66afGQygoEgkiclF5hAE3GMR5LoGNGkjS+TEgz8l0xEW8fbDh73RGFSTQTgnlc4JmaOwZZJCb5EbsfUJE9CcFmWV0mSrHCh3A6TUxZZoXBldMEBh2cfmcSIYtROzidp4/D4wjQ5ozKaYFe5Jg8xM7lfW9GjSx6lifBJ8hxPfS+9PybRBYeS81kkR20iPKRsh6R0SSIAe5tnxdyu9S5urEBmFWMFhwS7zSjAo6PcE9FDrNWNKIJDrcMNMeUlJHHB0nI7pipZVHBd198BLG3Lh5jmuu4kIqIiTdOXS2KeQvKMxmibQaa0EUVEsGuzIPGBrpJNxxe8JosM04hoaVn0QEQzidqTZdkTM1tvBGzbdhn6HQABwVVVPcLuvEPXyfwJ/Q4deZ7/YubCpqxAsgmAgGCzb+Ec0YZJeZ4/20qWQELwyeMDXdIm5jamPM+fLZJEe4nYImc0AKz6rnX5gDH2iKVp+mKm1n2S9w47NJ0QG6Y1TfOtbdvjTmbNzJuxN4P0HPHdj3EwUlEURVEURVEURVEURVEURVEURVEURVGAf9l4XANGvwF5AAAAAElFTkSuQmCC'''

    def __init__(self):
        super(Resources, self).__init__()        
        self.installer = None
        
    def __del__(self):
        if self.installer:
            self.installer.deleteLater()
             
    @staticmethod
    def base64_to_QPixmap(base64Image):
        pixmap = QPixmap()
        byte_array =  QByteArray.fromBase64( base64Image.encode() )
        pixmap.loadFromData(byte_array)
        
        return QPixmap(pixmap)
    
    @staticmethod
    def qPixmap_to_base64(pixmap, extension):
        #https://doc.qt.io/qtforpython/index.html
        #https://forum.qt.io/topic/85064/qbytearray-to-string/2
        image = pixmap.toImage()
        byteArray = QByteArray()
        buffer = QBuffer(byteArray)
        image.save(buffer, "png")
        base64 = byteArray.toBase64().data()
        result = str(base64, encoding='utf-8')
            
        return result
            
    @staticmethod
    def file_to_base64(file_path):
        extension = os.path.basename(file_path).split('.')[-1]
        pixmap = QPixmap()
        if pixmap.load(file_path):
            return Resources.qPixmap_to_base64(pixmap, extension)
        else:
            return ''
        
    @staticmethod
    def print_file_string(image_path):
        image_string =  Resources.file_to_base64(image_path)
        if image_string:
            print ("Use the next line for the Resources.company_base64")
            print(image_string)
  
      
    def set_installer(self, installer):
        self.installer = installer
    
    
    @property
    def close_icon(self):        
        return self.base64_to_QPixmap(Resources.close_base64)
    
    @property
    def install_icon(self):        
        return self.base64_to_QPixmap(Resources.install_base64)
              
    @property
    def company_icon(self):
        result = None
        if Resources.company_base64:
            result = self.base64_to_QPixmap(Resources.company_base64)
        
        return result
    
    @company_icon.setter
    def company_icon(self, value):
        Resources.company_base64 = value
      
    
class InstallerUi(QWidget):
    def __init__(self, name, module_manager, background_color = '',
                 company_logo_size = [64, 64],
                 launch_message='', 
                 installing_message = 'Installing, please wait ...',
                 failed_message='Install Failed!',
                 success_message="Install Completed Successfully!",
                 post_error_messsage='Install Successful. Clean-up errored.  See output.', 
                 *args, **kwargs
                 ):
        
        parent = module_manager.get_ui_parent()
        super(InstallerUi, self).__init__(parent=parent, *args, **kwargs)
        
        self.name = name
        self.module_manager = module_manager
        
        self.launch_message = launch_message
        self.installing_message = installing_message
        self.failed_message = failed_message
        self.success_message = success_message
        self.post_error_messsage =  post_error_messsage
        
        self.create_layout(background_color, company_logo_size)
        self.set_default_size(name)
        self.install_button.clicked.connect(self.on_install)
        self.close_button.clicked.connect(self.on_close)
        #

    def set_default_size(self, name):
        self.animated_gif.hide()         
        self.setObjectName(name)
        self.setWindowTitle(name)
        self.setWindowModality(Qt.ApplicationModal)
        self.setWindowFlags(Qt.FramelessWindowHint)
        self.setWindowFlags(Qt.Tool)
        self.setFixedSize(self.layout().minimumSize())
        self.close_button.hide()
        
        size = self.layout().minimumSize()
        width = size.width()
        height = size.height()
        desktop = QApplication.desktop()
        screenNumber = desktop.screenNumber(QCursor.pos())
        screenRect = desktop.screenGeometry(screenNumber)
        widthCenter = (screenRect.width() / 2) - (width / 2)
        heightCenter = (screenRect.height() / 2) - (height / 2)        
        self.setGeometry(QRect(widthCenter, heightCenter, width, height))
        
        
    def create_layout(self, background_color, company_logo_size):
        #background color
        if background_color:
            palette = self.palette()
            palette.setColor(self.backgroundRole(), background_color)
            self.setPalette(palette)             
        
        ##-----create all our ui elements THEN arrange them----##
        logo = None

        if RESOURCES.company_icon is not None:
            logo = QLabel()
            smallLogo = RESOURCES.company_icon.scaled(company_logo_size[0], company_logo_size[1], Qt.KeepAspectRatio, Qt.SmoothTransformation)
    
            logo.setPixmap(smallLogo)
            logo.setAlignment(Qt.AlignCenter | Qt.AlignCenter)
            logo.setMargin(15)

        self.install_button = IconButton('Install', highlight=True) #, icon=RESOURCES.install_icon)
        self.install_button.setMinimumHeight(42)
        self.close_button = IconButton(' Close', icon=RESOURCES.close_icon)
        self.close_button.setMinimumHeight(42)

        self.message_label = QLabel()
        self.message_label.setText(self.launch_message)
        self.message_label.show()
        
        self.movie = QMovie()
        self.device = QBuffer(Resources.BYTE_GIF)
        self.movie.setDevice(self.device)
        self.animated_gif = QLabel()

        self.animated_gif.setMovie(self.movie)
        self.animated_gif.setMaximumHeight(24)
        self.animated_gif.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
        self.animated_gif.setScaledContents(True)
        self.animated_gif.setMaximumWidth(24)
        outer = QVBoxLayout()
        self.setLayout(outer)
        if logo:
            outer.addWidget(logo, 0)
            
        message_layout = QVBoxLayout()
        message_layout.addWidget(self.message_label, 1)
        message_layout.setAlignment(Qt.AlignCenter)
        outer.addLayout(message_layout)
        
        button_layout = QHBoxLayout()
        button_layout.addStretch()
        button_layout.addWidget(self.install_button, 0)
        button_layout.addWidget(self.close_button, 0)
        button_layout.addWidget(self.animated_gif, 0)
        button_layout.addStretch()
        button_layout.setAlignment(Qt.AlignCenter)

        outer.addLayout(button_layout)
        self.layout()
                
    def on_install(self):
        self.install_button.hide()
        #self.movie.start() #I'm thinking this causes maya to crash when debugging in WING
        self.animated_gif.show()
        self.message_label.setText(self.installing_message)
        self.message_label.show()
        
        if self.module_manager.pre_install():
            self.connect(self.module_manager, SIGNAL('finished()'), self.done)
            self.module_manager.start()
        else:
            self.close_button.show()
            self.animated_gif.hide()
            self.message_label.setText(self.failed_message)
        
    
    def done(self):
        self.close_button.show()
        self.animated_gif.hide()
        if self.module_manager.install_succeeded:
            self.message_label.setText(self.success_message)
        else:
            self.message_label.setText(self.failed_message)
        
        no_errors = self.module_manager.post_install()
        if not no_errors:
            self.message_label.setText(self.post_error_messsage)
    
        
    def on_close(self):
        self.close()
        
    def clean(self):
        self.movie.stop()
        if self.device.isOpen():
            self.device.close()

    def closeEvent(self, event):
        self.clean()
        
        
        
class MyInstaller(ModuleManager):
    def __init__(self, *args, **kwargs):
        super(MyInstaller, self).__init__(*args, **kwargs)

        
    def get_remote_package(self):
        """Users must override this with pypi name or github address"""
        return super(MyInstaller, self).get_remote_package()
              
        
    
def main():
    if MAYA_RUNNING:
        MODULE_NAME = 'module name'
        MODULE_VERSION = 1.0
        PACKAGE_NAME = MODULE_NAME
        
        manager = MyInstaller(MODULE_NAME, MODULE_VERSION, package_name = PACKAGE_NAME)
        
        
        Resources.company_base64 = '''''' #must be surrounded by three '.  Uses Resources.print_file_string to generate a string from an iFXf00cV114ukwm9xUSVHtiM
        logo_size = [64, 64]
        background_color = '' #hex string

        global RESOURCES
        RESOURCES = Resources()
        window_name =  manager.module_name.replace("_", " ")
        installer = InstallerUi(window_name, manager, background_color=background_color, company_logo_size=logo_size)
        RESOURCES.set_installer(installer)

        installer.show()

           
def onMayaDroppedPythonFile(*args):
    main()
    

def run():
    """
    Run is a function used by WingIDE to execute code after telling Maya to import the module
    """    
    main()
    

if __name__ == "__main__":
    main()