from base64 import b64decode as license64

__version__ = '2021.3.10'
__release_date__ = '2020-12-12 21:10:02.351000'
MOD_NAME = 'BroTools'
MOD_Prefix = 'Bro'
import sys
MOD_PATH = ''
PackagePaths = [['packages'], ['BroRig', 'plugins']]
import codecs
import os
import sys
import traceback
import hashlib
import inspect
from pathlib import Path
from functools import partial, wraps
from types import ModuleType
import maya.OpenMayaMPx as OpenMayaMPx
import maya.cmds as cmds
import maya.mel as mel
plugin_path = cmds.pluginInfo((MOD_NAME + '.py'), query=True, path=True)
plugin_dir = os.path.dirname(plugin_path)
MOD_dir_relative = os.path.join(plugin_dir, '..', '..')
MOD_PATH = os.path.abspath(MOD_dir_relative)

def verbose_log(modpath):
    '\n    Self contained verbose log check.\n    v 1.0.0\n    '
    try:
        brotools_write_path = os.environ.get('BROTOOLS_CONFIG_PATH')
        if (not brotools_write_path):
            brotools_write_path = modpath
        config_path = os.path.join(brotools_write_path, 'config', 'brotools.cfg')
        if os.path.exists(config_path):
            with open(config_path, 'r') as f:
                lines = f.readlines()
        else:
            lines = []
            print('CANT FIND brotools.cfg FILE AT', config_path)
        verbose = False
        for line in lines:
            if (('debug_level: 0' in line) or ('debug_level:0' in line)):
                verbose = True
                print('BROTOOLS.py VERBOSE LOG ENABLED')
        return verbose
    except Exception as e:
        raise Exception('{} verbose_log failure: {}'.format(__file__, e))
VERBOSE_LOG = verbose_log(MOD_PATH)
brotools_path = os.path.join(MOD_PATH, MOD_NAME)
brotools_write_path = os.environ.get('BROTOOLS_CONFIG_PATH')
if (not brotools_write_path):
    brotools_write_path = os.path.join(MOD_PATH, MOD_NAME)
log_path = os.path.join(brotools_write_path, 'bro_startup.log')
if os.path.exists(log_path):
    try:
        os.remove(log_path)
        print('Old log file removed', log_path)
    except:
        pass
cmdFileOutput_descriptor = cmds.cmdFileOutput(o=log_path)

def remove_pyc_files():
    log('Cleaning up .pyc files...')
    errors = False
    for (root, dirs, files) in os.walk(brotools_path, topdown=False):
        for filename in files:
            (name, ext) = os.path.splitext(filename)
            if (ext.lower() == '.pyc'):
                filepath = os.path.join(root, filename)
                try:
                    os.remove(filepath)
                except Exception as e:
                    errors = True
                    log('   Failed to cleanup .pyc file [{}], {}.'.format(filepath, e), traceback=True)
    if (not errors):
        log('   .pyc cleanup finished.')
    else:
        log('   .pyc cleanup finished with errors.')

def brotools_hash():
    hash_files = []
    for (root, dirs, files) in os.walk(brotools_path, topdown=False):
        for filename in files:
            (name, ext) = os.path.splitext(filename)
            if (ext.lower() == '.py'):
                filepath = os.path.join(root, filename)
                relpath = os.path.relpath(filepath, brotools_path)
                hash_files.append(filepath)
    hash_obj = hashlib.sha1(open(hash_files[0], 'rb').read())
    for fname in hash_files[1:]:
        hash_obj.update(open(fname, 'rb').read())
    checksum = hash_obj.hexdigest()
    return checksum

def individual_files_hash():
    hash_files = []
    hash_data = []
    for (root, dirs, files) in os.walk(brotools_path, topdown=False):
        for filename in files:
            (name, ext) = os.path.splitext(filename)
            if (ext.lower() == '.py'):
                filepath = os.path.join(root, filename)
                relpath = os.path.relpath(filepath, brotools_path)
                hash_files.append(filepath)
                hash_data.append([relpath, file_hash(filepath)])
    return hash_data

def file_hash(filepath):
    hash_obj = hashlib.sha1(open(filepath, 'rb').read())
    checksum = hash_obj.hexdigest()
    return checksum

def print_directory_tree(directory_path, indent='', is_last=True):
    '\n    Print a tree structure of the given directory with file sizes.\n    \n    Args:\n        directory_path (str): Path to the directory\n        indent (str): Current indentation string\n        is_last (bool): If this item is the last in its parent directory\n    '
    path = Path(directory_path).resolve()
    name = path.name
    if is_last:
        connector = '└── '
        next_indent = (indent + '    ')
    else:
        connector = '├── '
        next_indent = (indent + '│   ')
    if path.is_dir():
        print(f'{indent}{connector}{name}/')
        items = list(path.iterdir())
        items.sort(key=(lambda x: (x.is_file(), x.name.lower())))
        for (i, item) in enumerate(items):
            print_directory_tree(item, next_indent, (i == (len(items) - 1)))
    else:
        size = path.stat().st_size
        size_str = format_size(size)
        print(f'{indent}{connector}{name} ({size_str}) [{file_hash(path)}]')

def format_size(size_bytes):
    '\n    Format a file size in bytes to a human-readable string.\n    \n    Args:\n        size_bytes (int): File size in bytes\n        \n    Returns:\n        str: Human-readable file size\n    '
    units = ['B', 'KB', 'MB', 'GB', 'TB']
    unit_index = 0
    while ((size_bytes >= 1024) and (unit_index < (len(units) - 1))):
        size_bytes /= 1024
        unit_index += 1
    if (unit_index == 0):
        return f'{size_bytes} {units[unit_index]}'
    else:
        return f'{size_bytes:.2f} {units[unit_index]}'
import platform
import locale
import sys
from datetime import datetime
print('\n// -----------------')
print('{} Startup Log'.format(MOD_NAME))
print('Loader Plugin Version: {}.{}\n'.format(__version__, __release_date__))
print('-  {:<15} {}, {}'.format('Log path:', str(log_path), str(cmdFileOutput_descriptor)))
print('-  {:<15} {}'.format('Date started:', str(datetime.now())))
print('\nMAYA INFO')
print('-  {:<15} {}'.format('OS:', cmds.about(q=True, os=True)))
print('-  {:<15} {}'.format('UI Lang:', cmds.about(q=True, uil=True)))
print('-  {:<15} {}'.format('Maya:', cmds.about(q=True, v=True)))
print('-  {:<15} {}'.format('Maya full:', cmds.about(q=True, iv=True)))
print('-  {:<15} {}'.format('Plugins:', cmds.pluginInfo(q=True, lsp=True)))
print('-  {:<15} {}'.format('Python:', sys.version))
print('\nSYSTEM INFO')
print('-  {:<15} {}'.format('Platform:', platform.platform()))
print('-  {:<15} {}'.format('Locale Def:', locale.getdefaultlocale()))
print('-  {:<15} {}'.format('Locale:', locale.getlocale()))
print('\nADDITIONAL INFO')
print('-  {:<15} {}'.format('MOD_PATH:', MOD_PATH))
print('-  {:<15} {}'.format('brotools_path:', brotools_path))
print('-  {:<15} {}'.format('brotools_path files:', os.listdir(brotools_path)))
print('-  {:<15} {}'.format('brotools hash:', brotools_hash()))
print('-  {:<15} {}'.format('brotools files hash:', individual_files_hash()))
if cmds.about(q=True, win=True):
    import ctypes
    user32 = ctypes.windll.user32
    screensize = (user32.GetSystemMetrics(0), user32.GetSystemMetrics(1), user32.GetSystemMetrics(78))
    print('-  {:<15} {}'.format('Screen Info:', str(screensize)))
    print('-  {:<15} {}'.format('Win:', str(sys.getwindowsversion())))
if VERBOSE_LOG:
    print('==== FILE STRUCTURE ====')
    print_directory_tree(brotools_path)
    print('==== ====')
print("\nIf you wish to keep any of this information private you're free to remove\nsome lines from here, however it can make debugging process harder. One part of\ndebugging process is to recreate your error. If I can't do that in my main system\nmy next step is to start a Virtual Machine with your OS and environment as close to\nyours as possible. That's what all the above data is used for. \n")
print('----------------- //\n')

def clear_log():
    with codecs.open(os.path.join(brotools_write_path, 'bro_startup.log'), 'w', encoding='utf-8') as f:
        f.write('')

def splitall(path):
    allparts = []
    while 1:
        parts = os.path.split(path)
        if (parts[0] == path):
            allparts.insert(0, parts[0])
            break
        elif (parts[1] == path):
            allparts.insert(0, parts[1])
            break
        else:
            path = parts[0]
            allparts.insert(0, parts[1])
    return_parts = []
    for part in allparts:
        if ((part != os.path.sep) and (part not in ['\\\\', '\\', '/', '//'])):
            return_parts.append(part)
    return return_parts

def log(*args, **kwargs):
    debug = kwargs.get('debug', False)
    if ((debug == True) and (VERBOSE_LOG == False)):
        return
    tb = kwargs.get('traceback', False)
    critical = kwargs.get('critical', False)
    text = '// {}.py -'.format(MOD_NAME)
    for item in args:
        text += ' '
        text += str(item)
    text += ' //'
    print(text)
    if tb:
        cmds.warning(text)
        print('\n===== TRACEBACK START (SEND THIS TO DEVELOPER, you may also include log above) =====')
        traceback.print_exc()
        print('===== TRACEBACK END =====\n')
    if critical:
        cmds.error(text)

def catch_errors_critical(f):

    @wraps(f)
    def wrapped(*args, **kwargs):
        try:
            return f(*args, **kwargs)
        except:
            log('Error in {} function!'.format(f.__name__), traceback=True, critical=True)
    return wrapped

def catch_errors(f):

    @wraps(f)
    def wrapped(*args, **kwargs):
        try:
            return f(*args, **kwargs)
        except:
            log('Error in {} function!'.format(f.__name__), traceback=True)
    return wrapped

def rreload(module, depth=0, max_depth=3, top=True):
    'Recursively reload modules.'
    if top:
        log('rreload module', module, 'started...')
    if (depth > max_depth):
        return
    for attribute_name in dir(module):
        if ((attribute_name != MOD_NAME) and (attribute_name != 'plug-ins')):
            try:
                attribute = getattr(module, attribute_name)
                if (type(attribute) is ModuleType):
                    print(('=' * depth), attribute_name)
                    rreload(attribute, depth=(depth + 1), top=False)
                    mel.eval('reload({})'.format(attribute_name))
            except:
                log('Unable to rreload', attribute_name)
    if top:
        log('rreload finished.')

@catch_errors_critical
def setup_package_path(path):
    '\n    Setup the packages that have been decoupled from the MOD.\n\n    :param path: Path to add\n    :return: Module parent directory.\n    '
    for sysPath in sys.path:
        if (path == sysPath):
            log('Removing', path, 'from path.')
            sys.path.remove(path)
    if (path not in sys.path):
        log('Appending', path, 'to path.')
        sys.path.append(path)

def plugin_init_dummy():
    log('plugin_init_dummy')

def mel_import():
    '\n    This is needed so that BroTools module is registered inside Maya for use with things like scriptJobs.\n    '
    try:
        py_commands = ['import sys', "sys.path.append('{}')".format(MOD_PATH), 'import {MOD_NAME}'.format(MOD_NAME=MOD_NAME)]
        for command in py_commands:
            mel.eval('python (encodeString("{}"));'.format(command))
    except:
        cmds.confirmDialog(title='BroTools: Startup error', message='BroTools is unable to launch. Please make sure you followed all installation steps.\n\n        To make sure everything works, please, follow these steps exactly:\n\n        1. Close Maya\n        2. Delete BroTools folder completely.\n        3. Search your PC for \'BroTools\', and if you find any folders or files - remove those.\n        4. Find Maya.env file (Usually under C:\\Users\\USERNAME\\Documents\\maya\\VERSION) anything related to BroTools from it\n        4. Re-Download BroTools archive to make sure you got the latest one\n        5. Unpack it in any folder on your PC. Make sure that path contains only latin letters and underscores. Using other symbols may interfere with a lot of programs\' work. You can use your \\Documents\\maya folder if you wish so it will look like "\\Documents\\maya\\BroTools"\n        6. Open Maya\n        7. Drag and drop "install.mel" into Maya viewport\n        8. Restart Maya\n\n        BroTools menu should appear at the top menu bar. You can also manually load\\unload BroTools from Maya\'s plugin manager window.\n        You may be able to find some information in Maya\'s Script Editor, it should display some error details there.\n\n        If you still have troubles - contact me at support@brotools.tech and provide log files (brotools.log, bro_startup.log). You can find them under BroTools folder.\n\n        Cheers!', icon='critical')
        log('{} mel eval import failed!'.format(MOD_NAME), traceback=True, critical=True)

def on_register_ui(*args, **kwargs):
    from BroTools.core.system import utilities as su
    log((MOD_NAME + ' init finished...'))
    log('Close startup log file...')
    close_result = cmds.cmdFileOutput(c=cmdFileOutput_descriptor)
    if (close_result != 0):
        log('Unable to close startup log file: ', close_result)
        log('You can read about error codes here: http://download.autodesk.com/us/maya/2011help/CommandsPython/cmdFileOutput.html')
    try:
        su.on_plugin_initialized()
    except Exception as e:
        cmds.warning('on_plugin_initialized failed: {}'.format(e))
        traceback.print_exc()

def on_unregister_ui(*args, **kwargs):
    from BroTools.core.system import utilities as su
    su.on_plugin_uninitialized()

@catch_errors_critical
def initializePlugin(mobject):
    '\n    Used by Maya to initialize Maya plugin\n    :param mobject: Passed by Maya.\n    :return: Returns status of registerUI function.\n    '
    log('{} Plugin initializing...'.format(MOD_NAME))
    remove_pyc_files()
    mplugin = OpenMayaMPx.MFnPlugin(mobject, 'Michael Davydov\nhttp://michaeldavydov.com', '1.0')
    setup_package_path(MOD_PATH)
    for pt in PackagePaths:
        setup_package_path(os.path.join(MOD_PATH, 'BroTools', *pt))
    status = mplugin.registerUI(on_register_ui, on_unregister_ui)
    log('initializePlugin end with status', status)
    return status

def uninitializePlugin(mobject):
    '\n    Used by Maya to uninitialize plugin.\n    :param mobject: Passed by Maya.\n    '
    log((MOD_NAME + ' Plugin unloading...'))
    mplugin = OpenMayaMPx.MFnPlugin(mobject)
    log('Cleaning up windows...')
    import BroTools.core.broqt.bro_windows as bw
    for w in bw.instance_cache:
        try:
            w.setParent(None)
            w.deleteLater()
        except:
            pass
    bw.instance_cache = []
    log('Unloading modules...')
    userPath = MOD_PATH.lower()
    modules = []
    for (root, dirs, files) in os.walk(MOD_PATH, topdown=False):
        for name in files:
            if ((name in ['.py', '.pyd', '.so']) and ('__init__' not in name) and ('.pyc' not in name)):
                modulePath = os.path.join(root, name)
                modulePath = modulePath.replace((MOD_PATH + os.path.sep), '')
                modulePath = modulePath.replace(os.path.sep, '.')
                modulePath = ((MOD_NAME + '.') + modulePath.replace('.py', ''))
                modules.append(modulePath)
    for (key, module) in sys.modules.items():
        try:
            moduleFilePath = inspect.getfile(module).lower()
            if (moduleFilePath == plugin_path.lower()):
                continue
            if moduleFilePath.startswith(userPath):
                modules.append(key)
        except:
            pass
    print('BroTools modules:\n    - ', '\n     - '.join(modules))
    success = []
    error = []
    for module in reversed(modules):
        try:
            del sys.modules[module]
            if (module not in success):
                success.append(module)
        except Exception as e:
            if (module not in error):
                error.append(((str(module) + ': ') + str(e)))
    print('Unloaded modules:\n    - ', '\n     - '.join(success))
    print('Failed unloading:\n    - ', '\n     - '.join(error))
