"""
 GT Maya Utilities
 github.com/TrevisanGMW - 2020-09-13

 1.1 - 2020-10-17
 Added move pivot to bottom/top
 Added copy/paste material
 Added move to origin

 1.2 - 2020-10-21
 Updated reset transform to better handle translate
 Added Uniform LRA Toggle
 Changed the order of the functions to match the menu

 1.3 - 2020-11-11
 Updates "gtu_import_references" to better handle unloaded references
 Added "gtu_remove_references"
 Added "gtu_combine_curves"
 Added "gtu_separate_curves"

 1.4 - 2020-11-13
 Updated combine and separate functions to work with Bezier curves

 1.5 - 2020-11-14
 Added "gtu_convert_bif_to_mesh"

 1.6 - 2020-11-16
 Added "gtu_delete_nucleus_nodes"
 Updated "gtu_delete_display_layers" to have inView feedback
 Updated "gtu_delete_keyframes" to have inView feedback

 1.7 - 2020-11-22
 Updated about window text

 1.8 - 2020-12-03
 Changed the background color for the title in the "About" window
 Changed the order of a few functions
 Added function to unlock/unhide default channels

 1.9 - 2021-01-05
 Added Uniform Joint Label Toggle

 2.0 - 2021-02-05
 Added "Select Non-Unique Objects" Utility

 2.1 - 2021-05-12
 Made script compatible with Python 3 (Maya 2022+)
 Added refresh to combine curves function as they were not automatically updating after re-parenting shapes

 2.2 - 2021-06-25
 Updated bif to mesh to work with newer versions of bifrost
 Updated bif to mesh to delete empty meshes (objects that weren't geometry)
 Added function to delete all locators

 2.2 - 2021-10-25
 Updated bif to mesh to work with newer versions of bifrost
 Updated bif to mesh to delete empty meshes (objects that weren't geometry)
 Added function to delete all locators

 2.3 - 2021-10-10
 Created Full HUD Toggle

 2.4 - 2021-10-10
 Fixed gtu full hud toggle as it would return an error if xGen was not loaded

 2.5 - 2022-01-04
 Renamed script to "gt_maya_utilities"

 2.6 - 2022-01-04
 Renamed script to "gt_maya_utilities"

 2.7 - 2022-06-29
 Added string to notepad (txt)
 Renamed functions

 TODO:
     Add proper error handling to all functions through logging
     New functions:
        Reset Display Type and Color
        Find/Rename non-unique names - Enforce unique names
        Remove Custom Colors - select object types, outliner or viewport. Use string to determine a list of types
        Assign lambert to everything function (Maybe assign to object missing shaders)
        Add Unlock all attributes
        Add unhide attributes (provide list?)
        Add Remove pasted_ function
        Add assign checkerboard function (already in bonus tools > rendering)
        Force focus (focus without looking at children)
        Brute force clean models (export OBJ and reimport)
     New options:
        Import all references : Add function to use a string to ignore certain references
        Reset Transforms : Add reset only translate, rotate or scale
        Delete all keyframes : Include option to delete or not set driven keys
        Reset persp camera : Reset all other attributes too (including transform?)
        Delete Display Layers : only empty? ignore string?
        Delete Namespaces : only empty? ignore string?

"""
import maya.cmds as cmds
import maya.mel as mel
import logging
import sys
from maya import OpenMayaUI as OpenMayaUI

try:
    from shiboken2 import wrapInstance
except ImportError:
    from shiboken import wrapInstance

try:
    from PySide2.QtGui import QIcon
    from PySide2.QtWidgets import QWidget
except ImportError:
    from PySide.QtGui import QIcon, QWidget

# Logging Setup
logging.basicConfig()
logger = logging.getLogger("gt_utilities")
logger.setLevel(logging.INFO)

''' ____________________________ General Functions ____________________________'''

def gtu_reload_file():
    """ Reopens the opened file (to revert any changes done to the file) """
    if cmds.file(query=True, exists=True):  # Check to see if it was ever saved
        file_path = cmds.file(query=True, expandName=True)
        if file_path is not None:
            cmds.file(file_path, open=True, force=True)
    else:
        cmds.warning('File was never saved.')

def gtu_open_resource_browser():
    """ Opens Maya's Resource Browser """
    try:
        import maya.app.general.resourceBrowser as resourceBrowser
        resourceBrowser.resourceBrowser().run()
    except Exception as e:
        logger.debug(str(e))

def gtu_unlock_default_channels():
    """ Unlocks Translate, Rotate, Scale for the selected objects """
    function_name = 'GTU Unlock Default Channels'
    errors = ''
    cmds.undoInfo(openChunk=True, chunkName=function_name)  # Start undo chunk
    selection = cmds.ls(selection=True, long=True)
    unlocked_counter = 0
    try:
        for obj in selection:
            try:
                cmds.setAttr(obj + '.translateX', lock=False)
                cmds.setAttr(obj + '.translateY', lock=False)
                cmds.setAttr(obj + '.translateZ', lock=False)
                cmds.setAttr(obj + '.rotateX', lock=False)
                cmds.setAttr(obj + '.rotateY', lock=False)
                cmds.setAttr(obj + '.rotateZ', lock=False)
                cmds.setAttr(obj + '.scaleX', lock=False)
                cmds.setAttr(obj + '.scaleY', lock=False)
                cmds.setAttr(obj + '.scaleZ', lock=False)
                cmds.setAttr(obj + '.v', lock=False)
                unlocked_counter += 1
            except Exception as e:
                errors += str(e) + '\n'
        if errors != '':
            print('#### Errors: ####')
            print(errors)
            cmds.warning('Some channels were not unlocked . Open the script editor for a list of errors.')
    except Exception as e:
        logger.debug(str(e))
    finally:
        cmds.undoInfo(closeChunk=True, chunkName=function_name)

    message = '' + str(unlocked_counter) + ' '
    is_plural = 'objects had their'
    if unlocked_counter == 1:
        is_plural = 'object had its'
    message += is_plural + ' default channels unlocked.'

    cmds.inViewMessage(amg=message, pos='botLeft', fade=True, alpha=.9)

def gtu_unhide_default_channels():
    """ Unhides Translate, Rotate, Scale for the selected objects """
    function_name = 'GTU Unhide Default Channels'
    errors = ''
    cmds.undoInfo(openChunk=True, chunkName=function_name)  # Start undo chunk
    selection = cmds.ls(selection=True, long=True)
    unlocked_counter = 0
    try:
        for obj in selection:
            try:
                cmds.setAttr(obj + '.translateX', keyable=True)
                cmds.setAttr(obj + '.translateY', keyable=True)
                cmds.setAttr(obj + '.translateZ', keyable=True)
                cmds.setAttr(obj + '.rotateX', keyable=True)
                cmds.setAttr(obj + '.rotateY', keyable=True)
                cmds.setAttr(obj + '.rotateZ', keyable=True)
                cmds.setAttr(obj + '.scaleX', keyable=True)
                cmds.setAttr(obj + '.scaleY', keyable=True)
                cmds.setAttr(obj + '.scaleZ', keyable=True)
                cmds.setAttr(obj + '.v', keyable=True)
                unlocked_counter += 1
            except Exception as e:
                errors += str(e) + '\n'
        if errors != '':
            print('#### Errors: ####')
            print(errors)
            cmds.warning('Some channels were not made visible. Open the script editor for a list of errors.')
    except Exception as e:
        logger.debug(str(e))
    finally:
        cmds.undoInfo(closeChunk=True, chunkName=function_name)

    message = '' + str(unlocked_counter) + ' '
    is_plural = 'objects had their'
    if unlocked_counter == 1:
        is_plural = 'object had its'
    message += is_plural + ' default channels made visible.'

    cmds.inViewMessage(amg=message, pos='botLeft', fade=True, alpha=.9)

def gtu_uniform_lra_toggle():
    """
    Makes the visibility of the Local Rotation Axis uniform among
    the selected objects according to the current state of the majority of them.
    """

    function_name = 'GTU Uniform LRA Toggle'
    cmds.undoInfo(openChunk=True, chunkName=function_name)
    try:
        errors = ''
        selection = cmds.ls(selection=True)

        inactive_lra = []
        active_lra = []

        for obj in selection:
            try:
                current_lra_state = cmds.getAttr(obj + '.displayLocalAxis')
                if current_lra_state:
                    active_lra.append(obj)
                else:
                    inactive_lra.append(obj)
            except Exception as e:
                errors += str(e) + '\n'

        if len(active_lra) == 0:
            for obj in inactive_lra:
                try:
                    cmds.setAttr(obj + '.displayLocalAxis', 1)
                except Exception as e:
                    errors += str(e) + '\n'
        elif len(inactive_lra) == 0:
            for obj in active_lra:
                try:
                    cmds.setAttr(obj + '.displayLocalAxis', 0)
                except Exception as e:
                    errors += str(e) + '\n'
        elif len(active_lra) > len(inactive_lra):
            for obj in inactive_lra:
                try:
                    cmds.setAttr(obj + '.displayLocalAxis', 1)
                except Exception as e:
                    errors += str(e) + '\n'
        else:
            for obj in active_lra:
                try:
                    cmds.setAttr(obj + '.displayLocalAxis', 0)
                except Exception as e:
                    errors += str(e) + '\n'

        if errors != '':
            print('#### Errors: ####')
            print(errors)
            cmds.warning("The script couldn't read or write some LRA states. Open script editor for more info.")
    except Exception as e:
        logger.debug(str(e))
    finally:
        cmds.undoInfo(closeChunk=True, chunkName=function_name)

def gtu_uniform_jnt_label_toggle():
    """
    Makes the visibility of the Joint Labels uniform according to the current state of the majority of them.
    """

    function_name = 'GTU Uniform Joint Label Toggle'
    cmds.undoInfo(openChunk=True, chunkName=function_name)
    try:
        errors = ''
        joints = cmds.ls(type='joint', long=True)

        inactive_label = []
        active_label = []

        for obj in joints:
            try:
                current_label_state = cmds.getAttr(obj + '.drawLabel')
                if current_label_state:
                    active_label.append(obj)
                else:
                    inactive_label.append(obj)
            except Exception as e:
                errors += str(e) + '\n'

        if len(active_label) == 0:
            for obj in inactive_label:
                try:
                    cmds.setAttr(obj + '.drawLabel', 1)
                except Exception as e:
                    errors += str(e) + '\n'
        elif len(inactive_label) == 0:
            for obj in active_label:
                try:
                    cmds.setAttr(obj + '.drawLabel', 0)
                except Exception as e:
                    errors += str(e) + '\n'
        elif len(active_label) > len(inactive_label):
            for obj in inactive_label:
                try:
                    cmds.setAttr(obj + '.drawLabel', 1)
                except Exception as e:
                    errors += str(e) + '\n'
        else:
            for obj in active_label:
                try:
                    cmds.setAttr(obj + '.drawLabel', 0)
                except Exception as e:
                    errors += str(e) + '\n'

        if errors != '':
            print('#### Errors: ####')
            print(errors)
            cmds.warning("The script couldn't read or write some \"drawLabel\" states. "
                         "Open script editor for more info.")
    except Exception as e:
        logger.debug(str(e))
    finally:
        cmds.undoInfo(closeChunk=True, chunkName=function_name)

def gtu_select_non_unique_objects():
    """ Selects all non-unique objects (objects with the same short name) """

    def get_short_name(full_name):
        """
            Get the name of the objects without its path (Maya returns full path if name is not unique)

            Args:
                full_name (string) - object to extract short name
            """
        output_short_name = ''
        if full_name == '':
            return ''
        split_path = full_name.split('|')
        if len(split_path) >= 1:
            output_short_name = split_path[len(split_path) - 1]
        return output_short_name

    all_transforms = cmds.ls(type='transform')
    short_names = []
    non_unique_transforms = []
    for obj in all_transforms:  # Get all Short Names
        short_names.append(get_short_name(obj))

    for obj in all_transforms:
        short_name = get_short_name(obj)
        if short_names.count(short_name) > 1:
            non_unique_transforms.append(obj)

    cmds.select(non_unique_transforms, r=True)

    if len(non_unique_transforms) > 0:
        message = '' + str(
            len(non_unique_transforms)) + ' non-unique objects were selected.'
    else:
        message = 'All objects seem to have unique names in this scene.'
    cmds.inViewMessage(amg=message, pos='botLeft', fade=True, alpha=.9)

def gtu_import_references():
    """ Imports all references """
    errors = ''
    r_file = ''
    try:
        refs = cmds.ls(rf=True)
        for i in refs:
            try:
                r_file = cmds.referenceQuery(i, f=True)
                cmds.file(r_file, importReference=True)
            except Exception as e:
                errors += str(e) + '(' + r_file + ')\n'
    except Exception as e:
        logger.debug(str(e))
        cmds.warning("Something went wrong. Maybe you don't have any references to import?")
    if errors != '':
        cmds.warning('Not all references were imported. Open the script editor for more information.')
        print(('#' * 50) + '\n')
        print(errors)
        print('#' * 50)

def gtu_remove_references():
    """ Removes all references """
    errors = ''
    r_file = ''
    try:
        refs = cmds.ls(rf=True)
        for i in refs:
            try:
                r_file = cmds.referenceQuery(i, f=True)
                cmds.file(r_file, removeReference=True)
            except Exception as e:
                errors += str(e) + '(' + r_file + ')\n'
    except Exception as e:
        logger.debug(str(e))
        cmds.warning("Something went wrong. Maybe you don't have any references to import?")
    if errors != '':
        cmds.warning('Not all references were removed. Open the script editor for more information.')
        print(('#' * 50) + '\n')
        print(errors)
        print('#' * 50)

""" ____________________________ Material Functions ____________________________"""

def gtu_generate_udim_previews():
    """ Generates UDIM previews for all file nodes """
    all_file_nodes = cmds.ls(type='file')
    for file_node in all_file_nodes:
        try:
            mel.eval('generateUvTilePreview ' + file_node + ';')
        except Exception as e:
            print(e)
    message = 'Previews generated for all  ' \
              'UDIM file nodes.'
    cmds.inViewMessage(amg=message, pos='botLeft', fade=True, alpha=.9)

def gtu_copy_material():
    """ Copies selected material to clipboard """
    selection = cmds.ls(selection=True)
    try:
        mel.eval('ConvertSelectionToFaces;')
        cmds.polyClipboard(copy=True, shader=True)
        cmds.inViewMessage(amg='Material <hl>copied</hl> to the clipboard.', pos='midCenterTop', fade=True)
    except Exception as e:
        logger.debug(str(e))
        cmds.warning("Couldn't copy material. Make sure you selected an object or component before copying.")
    cmds.select(selection)

def gtu_paste_material():
    """ Copies selected material to clipboard """
    try:
        cmds.polyClipboard(paste=True, shader=True)
    except Exception as e:
        logger.debug(str(e))
        cmds.warning("Couldn't paste material. Make sure you copied a material first, "
                     "then selected the target objects or components.")

""" ____________________________ Layout Functions ____________________________"""

def gtu_move_pivot_to_top():
    """ Moves pivot point to the top of the boundary box """
    selection = cmds.ls(selection=True)

    for obj in selection:
        bbox = cmds.exactWorldBoundingBox(obj)  # extracts bounding box
        top = [(bbox[0] + bbox[3]) / 2, bbox[4], (bbox[2] + bbox[5]) / 2]  # find top
        cmds.xform(obj, piv=top, ws=True)

def gtu_move_pivot_to_base():
    """ Moves pivot point to the base of the boundary box """
    selection = cmds.ls(selection=True)

    for obj in selection:
        bbox = cmds.exactWorldBoundingBox(obj)  # extracts bounding box
        bottom = [(bbox[0] + bbox[3]) / 2, bbox[1], (bbox[2] + bbox[5]) / 2]  # find bottom
        cmds.xform(obj, piv=bottom, ws=True)  # sends pivot to bottom

def gtu_move_to_origin():
    """ Moves selected objects back to origin """
    function_name = 'GTU Move to Origin'
    errors = ''
    cmds.undoInfo(openChunk=True, chunkName=function_name)  # Start undo chunk
    selection = cmds.ls(selection=True)
    try:
        for obj in selection:
            try:
                cmds.move(0, 0, 0, obj, a=True, rpr=True)  # rpr flag moves it according to the pivot
            except Exception as e:
                errors += str(e) + '\n'
        if errors != '':
            print('#### Errors: ####')
            print(errors)
            cmds.warning('Some objects could not be moved to the origin. Open the script editor for a list of errors.')
    except Exception as e:
        logger.debug(str(e))
    finally:
        cmds.undoInfo(closeChunk=True, chunkName=function_name)

""" ____________________________ Reset Functions ____________________________"""

def gtu_reset_transforms():
    """
    Reset transforms.
    It checks for incoming connections, then set the attribute to 0 if there are none
    It resets transforms, but ignores translate for joints.
    """
    function_name = 'GTU Reset Transforms'
    errors = ''
    cmds.undoInfo(openChunk=True, chunkName=function_name)  # Start undo chunk

    selection = cmds.ls(selection=True)

    def reset_transforms():
        for obj in selection:
            try:
                type_check = cmds.listRelatives(obj, children=True) or []

                if len(type_check) > 0 and cmds.objectType(type_check) != 'joint':
                    obj_connection_tx = cmds.listConnections(obj + '.tx', d=False, s=True) or []
                    if not len(obj_connection_tx) > 0:
                        if cmds.getAttr(obj + '.tx', lock=True) is False:
                            cmds.setAttr(obj + '.tx', 0)
                    obj_connection_ty = cmds.listConnections(obj + '.ty', d=False, s=True) or []
                    if not len(obj_connection_ty) > 0:
                        if cmds.getAttr(obj + '.ty', lock=True) is False:
                            cmds.setAttr(obj + '.ty', 0)
                    obj_connection_tz = cmds.listConnections(obj + '.tz', d=False, s=True) or []
                    if not len(obj_connection_tz) > 0:
                        if cmds.getAttr(obj + '.tz', lock=True) is False:
                            cmds.setAttr(obj + '.tz', 0)

                obj_connection_rx = cmds.listConnections(obj + '.rotateX', d=False, s=True) or []
                if not len(obj_connection_rx) > 0:
                    if cmds.getAttr(obj + '.rotateX', lock=True) is False:
                        cmds.setAttr(obj + '.rotateX', 0)
                obj_connection_ry = cmds.listConnections(obj + '.rotateY', d=False, s=True) or []
                if not len(obj_connection_ry) > 0:
                    if cmds.getAttr(obj + '.rotateY', lock=True) is False:
                        cmds.setAttr(obj + '.rotateY', 0)
                obj_connection_rz = cmds.listConnections(obj + '.rotateZ', d=False, s=True) or []
                if not len(obj_connection_rz) > 0:
                    if cmds.getAttr(obj + '.rotateZ', lock=True) is False:
                        cmds.setAttr(obj + '.rotateZ', 0)

                obj_connection_sx = cmds.listConnections(obj + '.scaleX', d=False, s=True) or []
                if not len(obj_connection_sx) > 0:
                    if cmds.getAttr(obj + '.scaleX', lock=True) is False:
                        cmds.setAttr(obj + '.scaleX', 1)
                obj_connection_sy = cmds.listConnections(obj + '.scaleY', d=False, s=True) or []
                if not len(obj_connection_sy) > 0:
                    if cmds.getAttr(obj + '.scaleY', lock=True) is False:
                        cmds.setAttr(obj + '.scaleY', 1)
                obj_connection_sz = cmds.listConnections(obj + '.scaleZ', d=False, s=True) or []
                if not len(obj_connection_sz) > 0:
                    if cmds.getAttr(obj + '.scaleZ', lock=True) is False:
                        cmds.setAttr(obj + '.scaleZ', 1)
            except Exception as exception:
                logger.debug(str(exception))
                errors += str(exception) + '\n'

    try:
        reset_transforms()
    except Exception as e:
        logger.debug(str(e))
    finally:
        cmds.undoInfo(closeChunk=True, chunkName=function_name)

    if errors != '':
        cmds.warning("Some objects couldn't be reset. Open the script editor for a list of errors.")

def gtu_reset_joint_sizes():
    """
    Resets the radius attribute back to one in all joints,
    then changes the global multiplier (jointDisplayScale) back to one
    """
    try:
        desired_size = 1
        all_joints = cmds.ls(type='joint')
        for obj in all_joints:
            if cmds.objExists(obj):
                if cmds.getAttr(obj + ".radius", lock=True) is False:
                    cmds.setAttr(obj + '.radius', 1)

                if cmds.getAttr(obj + ".v", lock=True) is False:
                    cmds.setAttr(obj + '.v', 1)
        cmds.jointDisplayScale(desired_size)

    except Exception as exception:
        raise exception

def gtu_reset_persp_shape_attributes():
    """
    If persp shape exists (default camera), reset its attributes
    """
    if cmds.objExists('perspShape'):
        try:
            cmds.setAttr('perspShape' + ".focalLength", 35)
            cmds.setAttr('perspShape' + ".verticalFilmAperture", 0.945)
            cmds.setAttr('perspShape' + ".horizontalFilmAperture", 1.417)
            cmds.setAttr('perspShape' + ".lensSqueezeRatio", 1)
            cmds.setAttr('perspShape' + ".fStop", 5.6)
            cmds.setAttr('perspShape' + ".focusDistance", 5)
            cmds.setAttr('perspShape' + ".shutterAngle", 144)
            cmds.setAttr('perspShape' + ".locatorScale", 1)
            cmds.setAttr('perspShape' + ".nearClipPlane", 0.100)
            cmds.setAttr('perspShape' + ".farClipPlane", 10000.000)
            cmds.setAttr('perspShape' + ".cameraScale", 1)
            cmds.setAttr('perspShape' + ".preScale", 1)
            cmds.setAttr('perspShape' + ".postScale", 1)
            cmds.setAttr('perspShape' + ".depthOfField", 0)
        except Exception as e:
            logger.debug(str(e))

""" ____________________________ Delete Functions ____________________________"""

def gtu_delete_namespaces():
    """Deletes all namespaces in the scene"""
    function_name = 'GTU Delete All Namespaces'
    cmds.undoInfo(openChunk=True, chunkName=function_name)
    try:
        default_namespaces = ['UI', 'shared']

        def num_children(namespace):
            """Used as a sort key, this will sort namespaces by how many children they have."""
            return namespace.count(':')

        namespaces = [namespace for namespace in cmds.namespaceInfo(lon=True, r=True) if
                      namespace not in default_namespaces]

        # Reverse List
        namespaces.sort(key=num_children, reverse=True)  # So it does the children first

        print(namespaces)

        for namespace in namespaces:
            if namespace not in default_namespaces:
                mel.eval('namespace -mergeNamespaceWithRoot -removeNamespace "' + namespace + '";')
    except Exception as e:
        cmds.warning(str(e))
    finally:
        cmds.undoInfo(closeChunk=True, chunkName=function_name)

def gtu_delete_display_layers():
    """ Deletes all display layers """
    function_name = 'GTU Delete All Display Layers'
    cmds.undoInfo(openChunk=True, chunkName=function_name)
    try:
        display_layers = cmds.ls(type='displayLayer')
        deleted_counter = 0
        for layer in display_layers:
            if layer != 'defaultLayer':
                cmds.delete(layer)
                deleted_counter += 1
        message = '' + str(deleted_counter) + ' '
        is_plural = 'layers were'
        if deleted_counter == 1:
            is_plural = 'layer was'
        message += is_plural + ' deleted.'

        cmds.inViewMessage(amg=message, pos='botLeft', fade=True, alpha=.9)
    except Exception as e:
        cmds.warning(str(e))
    finally:
        cmds.undoInfo(closeChunk=True, chunkName=function_name)

def gtu_delete_keyframes():
    """Deletes all keyframes. (Doesn't include Set Driven Keys)"""
    function_name = 'GTU Delete All Keyframes'
    cmds.undoInfo(openChunk=True, chunkName=function_name)
    try:
        keys_ta = cmds.ls(type='animCurveTA')
        keys_tl = cmds.ls(type='animCurveTL')
        keys_tt = cmds.ls(type='animCurveTT')
        keys_tu = cmds.ls(type='animCurveTU')
        # keys_ul = cmds.ls(type='animCurveUL') # Use optionVar to determine if driven keys should be deleted
        # keys_ua = cmds.ls(type='animCurveUA')
        # keys_ut = cmds.ls(type='animCurveUT')
        # keys_uu = cmds.ls(type='animCurveUU')
        deleted_counter = 0
        all_keyframes = keys_ta + keys_tl + keys_tt + keys_tu
        for obj in all_keyframes:
            try:
                cmds.delete(obj)
                deleted_counter += 1
            except Exception as e:
                logger.debug(str(e))
        message = '' + str(deleted_counter) + ' '
        is_plural = 'keyframe nodes were'
        if deleted_counter == 1:
            is_plural = 'keyframe node was'
        message += is_plural + ' deleted.'

        cmds.inViewMessage(amg=message, pos='botLeft', fade=True, alpha=.9)
    except Exception as e:
        cmds.warning(str(e))
    finally:
        cmds.undoInfo(closeChunk=True, chunkName=function_name)

def gtu_delete_nucleus_nodes():
    """ Deletes all elements related to particles """
    errors = ''
    function_name = 'GTU Delete Nucleus Nodes'
    try:
        cmds.undoInfo(openChunk=True, chunkName=function_name)

        # Without Transform
        emitters = cmds.ls(typ='pointEmitter')
        solvers = cmds.ls(typ='nucleus')
        instancers = cmds.ls(typ='instancer')

        no_transforms = emitters + instancers + solvers + instancers

        # With Transform
        nparticle_nodes = cmds.ls(typ='nParticle')
        spring_nodes = cmds.ls(typ='spring')
        particle_nodes = cmds.ls(typ='particle')
        nrigid_nodes = cmds.ls(typ='nRigid')
        ncloth_nodes = cmds.ls(typ='nCloth')
        pfxhair_nodes = cmds.ls(typ='pfxHair')
        hair_nodes = cmds.ls(typ='hairSystem')
        nconstraint_nodes = cmds.ls(typ='dynamicConstraint')

        transforms = nparticle_nodes + spring_nodes + particle_nodes + nrigid_nodes
        transforms += ncloth_nodes + pfxhair_nodes + hair_nodes + nconstraint_nodes

        # Fields/Solvers Types
        # airField
        # dragField
        # newtonField
        # radialField
        # turbulenceField
        # uniformField
        # vortexField
        # volumeAxisField

        deleted_counter = 0
        for obj in transforms:
            try:
                parent = cmds.listRelatives(obj, parent=True) or []
                cmds.delete(parent[0])
                deleted_counter += 1
            except Exception as e:
                logger.debug(str(e))
        for obj in no_transforms:
            try:
                cmds.delete(obj)
                deleted_counter += 1
            except Exception as e:
                logger.debug(str(e))

        message = '' + str(deleted_counter) + ' '
        is_plural = 'objects were'
        if deleted_counter == 1:
            is_plural = 'object was'
        message += is_plural + ' deleted.'

        cmds.inViewMessage(amg=message, pos='botLeft', fade=True, alpha=.9)

    except Exception as e:
        errors += str(e) + '\n'
        cmds.warning('An error occurred. Open the script editor for more information.')
    finally:
        cmds.undoInfo(closeChunk=True, chunkName=function_name)
    if errors != '':
        print('######## Errors: ########')
        print(errors)

def gtu_delete_user_defined_attributes():
    """ Deletes all User defined attributes for the selected objects. """
    function_name = 'GTU Delete User Defined Attributes'
    cmds.undoInfo(openChunk=True, chunkName=function_name)

    selection = cmds.ls(selection=True)
    if selection == 0:
        cmds.warning('Select at least one target object to delete custom attributes')
        return

    try:
        custom_attributes = []
        for sel in selection:
            attributes = cmds.listAttr(sel, userDefined=True) or []
            for attr in attributes:
                custom_attributes.append(sel + '.' + attr)

        deleted_counter = 0
        for obj in custom_attributes:
            try:
                cmds.deleteAttr(obj)
                deleted_counter += 1
            except Exception as e:
                logger.debug(str(e))
        message = '' + str(deleted_counter) + ' '
        is_plural = 'user defined attributes were'
        if deleted_counter == 1:
            is_plural = 'user defined attribute was'
        message += is_plural + ' deleted.'

        cmds.inViewMessage(amg=message, pos='botLeft', fade=True, alpha=.9)
    except Exception as e:
        cmds.warning(str(e))
    finally:
        cmds.undoInfo(closeChunk=True, chunkName=function_name)

""" ____________________________ External Functions ____________________________"""

def gtu_combine_curves():
    """ Moves the shape objects of all selected curves under a single group (combining them) """
    errors = ''
    function_name = 'GTU Combine Curves'
    try:
        cmds.undoInfo(openChunk=True, chunkName=function_name)
        selection = cmds.ls(sl=True, absoluteName=True)
        valid_selection = True
        acceptable_types = ['nurbsCurve', 'bezierCurve']
        bezier_in_selection = []

        for obj in selection:
            shapes = cmds.listRelatives(obj, shapes=True, fullPath=True) or []
            for shape in shapes:
                if cmds.objectType(shape) == 'bezierCurve':
                    bezier_in_selection.append(obj)
                if cmds.objectType(shape) not in acceptable_types:
                    valid_selection = False
                    cmds.warning('Make sure you selected only curves.')

        if valid_selection and len(selection) < 2:
            cmds.warning('You need to select at least two curves.')
            valid_selection = False

        if len(bezier_in_selection) > 0 and valid_selection:
            user_input = cmds.confirmDialog(title='Bezier curve detected!',
                                            message='A bezier curve was found in your selection.\n'
                                                    'Would you like to convert Bezier to NURBS before combining?',
                                            button=['Yes', 'No'],
                                            defaultButton='Yes',
                                            cancelButton='No',
                                            dismissString='No',
                                            icon="warning")
            if user_input == 'Yes':
                for obj in bezier_in_selection:
                    logger.debug(str(obj))
                    cmds.bezierCurveToNurbs()

        if valid_selection:
            shapes = cmds.listRelatives(shapes=True, fullPath=True)
            for obj in range(len(selection)):
                cmds.makeIdentity(selection[obj], apply=True, rotate=True, scale=True, translate=True)

            group = cmds.group(empty=True, world=True, name=selection[0])
            cmds.refresh()
            cmds.select(shapes[0])
            for obj in range(1, (len(shapes))):
                cmds.select(shapes[obj], add=True)

            cmds.select(group, add=True)
            cmds.parent(relative=True, shape=True)
            cmds.delete(selection)

    except Exception as e:
        errors += str(e) + '\n'
        cmds.warning('An error occurred when combining the curves. Open the script editor for more information.')
    finally:

        cmds.undoInfo(closeChunk=True, chunkName=function_name)
    if errors != '':
        print('######## Errors: ########')
        print(errors)

def gtu_separate_curves():
    """
    Moves the shapes instead of a curve to individual transforms (separating curves)
    """
    errors = ''
    acceptable_types = ['nurbsCurve', 'bezierCurve']

    def get_short_name(full_name):
        """
        Get the name of the objects without its path (Maya returns full path if name is not unique)

        Args:
            full_name (string) - object to extract short name
        """
        short_name = ''
        if full_name == '':
            return ''
        split_path = full_name.split('|')
        if len(split_path) >= 1:
            short_name = split_path[len(split_path) - 1]
        return short_name

    function_name = 'GTU Separate Curves'
    try:
        cmds.undoInfo(openChunk=True, chunkName=function_name)
        selection = cmds.ls(sl=True)
        valid_selection = True

        curve_shapes = []
        parent_transforms = []

        if len(selection) < 1:
            valid_selection = False
            cmds.warning('You need to select at least one curve.')

        if valid_selection:
            for obj in selection:
                shapes = cmds.listRelatives(obj, shapes=True, fullPath=True) or []
                for shape in shapes:
                    if cmds.objectType(shape) in acceptable_types:
                        curve_shapes.append(shape)

            if len(curve_shapes) == 0:
                cmds.warning('You need to select at least one curve.')
            elif len(curve_shapes) > 1:
                for obj in curve_shapes:
                    parent = cmds.listRelatives(obj, parent=True) or []
                    for par in parent:
                        if par not in parent_transforms:
                            parent_transforms.append(par)
                        cmds.makeIdentity(par, apply=True, rotate=True, scale=True, translate=True)
                    group = cmds.group(empty=True, world=True, name=get_short_name(obj).replace('Shape', ''))
                    cmds.parent(obj, group, relative=True, shape=True)
            else:
                cmds.warning('The selected curve contains only one shape.')

            for obj in parent_transforms:
                shapes = cmds.listRelatives(obj, shapes=True, fullPath=True) or []
                if cmds.objExists(obj) and cmds.objectType(obj) == 'transform' and len(shapes) == 0:
                    cmds.delete(obj)

    except Exception as e:
        errors += str(e) + '\n'
        cmds.warning('An error occurred when separating the curves. Open the script editor for more information.')
    finally:
        cmds.undoInfo(closeChunk=True, chunkName=function_name)
    if errors != '':
        print('######## Errors: ########')
        print(errors)

def gtu_convert_bif_to_mesh():
    """
    Converts Bifrost geometry to Maya geometry
    """
    errors = ''
    function_name = 'GTU Convert Bif to Mesh'
    try:
        cmds.undoInfo(openChunk=True, chunkName=function_name)
        valid_selection = True

        selection = cmds.ls(selection=True)
        bif_objects = []
        bif_graph_objects = []

        if len(selection) < 1:
            valid_selection = False
            cmds.warning('You need to select at least one bif object.')

        if valid_selection:
            for obj in selection:
                shapes = cmds.listRelatives(obj, shapes=True, fullPath=True) or []
                for shape in shapes:
                    if cmds.objectType(shape) == 'bifShape':
                        bif_objects.append(shape)
                    if cmds.objectType(shape) == 'bifrostGraphShape':
                        bif_graph_objects.append(shape)

            for bif in bif_objects:
                parent = cmds.listRelatives(bif, parent=True) or []
                for par in parent:
                    source_mesh = cmds.listConnections(par + '.inputSurface', source=True, plugs=True) or []
                    for sm in source_mesh:
                        conversion_node = cmds.createNode("bifrostGeoToMaya")
                        cmds.connectAttr(sm, conversion_node + '.bifrostGeo')
                        mesh_node = cmds.createNode("mesh")
                        mesh_transform = cmds.listRelatives(mesh_node, parent=True) or []
                        cmds.connectAttr(conversion_node + '.mayaMesh[0]', mesh_node + '.inMesh')
                        cmds.rename(mesh_transform[0], 'bifToGeo1')
                        try:
                            cmds.hyperShade(assign='lambert1')
                        except Exception as e:
                            logger.debug(str(e))

            for bif in bif_graph_objects:
                bifrost_attributes = cmds.listAttr(bif, fp=True, inUse=True, read=True) or []
                for output in bifrost_attributes:
                    conversion_node = cmds.createNode("bifrostGeoToMaya")
                    cmds.connectAttr(bif + '.' + output, conversion_node + '.bifrostGeo')
                    mesh_node = cmds.createNode("mesh")
                    mesh_transform = cmds.listRelatives(mesh_node, parent=True) or []
                    cmds.connectAttr(conversion_node + '.mayaMesh[0]', mesh_node + '.inMesh')
                    bif_mesh = cmds.rename(mesh_transform[0], 'bifToGeo1')
                    try:
                        cmds.hyperShade(assign='lambert1')
                    except Exception as e:
                        logger.debug(str(e))

                    vtx = cmds.ls(bif_mesh + '.vtx[*]', fl=True) or []
                    if len(vtx) == 0:
                        try:
                            cmds.delete(bif_mesh)
                            # cmds.delete(conversion_node)
                            # cmds.delete(mesh_node)
                        except Exception as e:
                            logger.debug(str(e))
    except Exception as e:
        errors += str(e) + '\n'
    finally:
        cmds.undoInfo(closeChunk=True, chunkName=function_name)
    if errors != '':
        cmds.warning('An error occurred when converting bif to mesh. Open the script editor for more information.')
        print('######## Errors: ########')
        print(errors)

""" ____________________________ About Window ____________________________"""

def gtu_build_gui_about_gt_tools():
    """ Creates "About" window for the GT Tools menu """

    stored_gt_tools_version_exists = cmds.optionVar(exists="gt_tools_version")

    # Define Version
    if stored_gt_tools_version_exists:
        gt_version = cmds.optionVar(q="gt_tools_version")
    else:
        gt_version = '?'

    window_name = "gtu_build_gui_about_gt_tools"
    if cmds.window(window_name, exists=True):
        cmds.deleteUI(window_name, window=True)

    cmds.window(window_name, title="About - GT Tools", mnb=False, mxb=False, s=True)
    cmds.window(window_name, e=True, s=True, wh=[1, 1])

    cmds.columnLayout("main_column", p=window_name)

    # Title Text
    cmds.separator(h=12, style='none')  # Empty Space
    cmds.rowColumnLayout(nc=1, cw=[(1, 310)], cs=[(1, 10)], p="main_column")  # Window Size Adjustment
    cmds.rowColumnLayout(nc=1, cw=[(1, 300)], cs=[(1, 10)], p="main_column")  # Title Column
    cmds.text("GT Tools", bgc=[.4, .4, .4], fn="boldLabelFont", align="center")
    cmds.separator(h=10, style='none', p="main_column")  # Empty Space

    cmds.rowColumnLayout(nc=1, cw=[(1, 300)], cs=[(1, 10)], p="main_column")
    cmds.text(l='Version Installed: ' + gt_version, align="center", fn="boldLabelFont")
    cmds.separator(h=5, style='none')  # Empty Space
    cmds.text(l='GT Tools is a free collection of Maya scripts', align="center")

    cmds.separator(h=15, style='none')  # Empty Space
    cmds.text(l='About:', align="center", fn="boldLabelFont")
    cmds.text(l='This is my collection of scripts for Autodesk Maya.\n'
                'These scripts were created with the aim of automating,\n e'
                'nhancing or simply filling the missing details of what\n I find lacking in Maya.', align="center")
    cmds.separator(h=15, style='none')  # Empty Space
    cmds.text(
        l='When installed you can find a pull-down menu that\n provides easy access to a variety of related tools.',
        align="center")
    cmds.separator(h=5, style='none')  # Empty Space
    cmds.text(l='This menu contains sub-menus that have been\n organized to contain related tools.\n '
                'For example: modeling, rigging, utilities, etc...', align="center")
    cmds.separator(h=15, style='none')  # Empty Space
    cmds.text(l='All of these items are supplied as is.\nYou alone are responsible for any issues.\n'
                'Use at your own risk.', align="center")
    cmds.separator(h=15, style='none')  # Empty Space
    cmds.text(l='Hopefully these scripts are helpful to you\nas they are to me.', align="center")
    cmds.separator(h=15, style='none')  # Empty Space
    cmds.rowColumnLayout(nc=2, cw=[(1, 140), (2, 140)], cs=[(1, 10), (2, 0)], p="main_column")
    cmds.text('Guilherme Trevisan  ')
    cmds.text(l='TrevisanGMW@gmail.com', hl=True, highlightColor=[1, 1, 1])
    cmds.rowColumnLayout(nc=2, cw=[(1, 140), (2, 140)], cs=[(1, 10), (2, 0)], p="main_column")
    cmds.separator(h=15, style='none')  # Empty Space
    cmds.text(l='Github', hl=True, highlightColor=[1, 1, 1])
    cmds.separator(h=7, style='none')  # Empty Space

    # Close Button
    cmds.rowColumnLayout(nc=1, cw=[(1, 300)], cs=[(1, 10)], p="main_column")
    cmds.separator(h=10, style='none')
    cmds.button(l='OK', h=30, c=lambda args: close_help_gui())
    cmds.separator(h=8, style='none')

    # Show and Lock Window
    cmds.showWindow(window_name)
    cmds.window(window_name, e=True, s=False)

    # Set Window Icon
    qw = OpenMayaUI.MQtUtil.findWindow(window_name)
    widget = wrapInstance(int(qw), QWidget)
    icon = QIcon(':/question.png')
    widget.setWindowIcon(icon)

    def close_help_gui():
        if cmds.window(window_name, exists=True):
            cmds.deleteUI(window_name, window=True)

def gtu_delete_all_locators():
    """ Deletes all locators """
    errors = ''
    function_name = 'GTU Delete All Locators'
    try:
        cmds.undoInfo(openChunk=True, chunkName=function_name)

        # With Transform
        locators = cmds.ls(typ='locator')

        deleted_counter = 0
        for obj in locators:
            try:
                parent = cmds.listRelatives(obj, parent=True) or []
                cmds.delete(parent[0])
                deleted_counter += 1
            except Exception as e:
                logger.debug(str(e))

        message = '' + str(deleted_counter) + ' '
        is_plural = 'locators were'
        if deleted_counter == 1:
            is_plural = 'locator was'
        message += is_plural + ' deleted.'

        cmds.inViewMessage(amg=message, pos='botLeft', fade=True, alpha=.9)

    except Exception as e:
        errors += str(e) + '\n'
        cmds.warning('An error occurred when deleting locators. Open the script editor for more information.')
    finally:
        cmds.undoInfo(closeChunk=True, chunkName=function_name)
    if errors != '':
        print('######## Errors: ########')
        print(errors)

def gtu_full_hud_toggle():
    """ Toggles common HUD options so all the common ones are either active or inactive  """
    hud_current_state = {}

    # 1 - Animation Details
    hud_current_state['animationDetailsVisibility'] = int(mel.eval('optionVar -q animationDetailsVisibility;'))
    # 2 - Cache
    try:
        from maya.plugin.evaluator.CacheUiHud import CachePreferenceHud
        hud_current_state['CachePreferenceHud'] = int(CachePreferenceHud().get_value() or 0)
    except Exception as e:
        logger.debug(str(e))
        hud_current_state['CachePreferenceHud'] = 0
    # 3 - Camera Names
    hud_current_state['cameraNamesVisibility'] = int(mel.eval('optionVar -q cameraNamesVisibility;'))
    # 4 - Caps Lock
    hud_current_state['capsLockVisibility'] = int(mel.eval('optionVar -q capsLockVisibility;'))
    # 5 - Current Asset
    hud_current_state['currentContainerVisibility'] = int(mel.eval('optionVar -q currentContainerVisibility;'))
    # 6 - Current Frame
    hud_current_state['currentFrameVisibility'] = int(mel.eval('optionVar -q currentFrameVisibility;'))
    # 7 - Evaluation
    hud_current_state['evaluationVisibility'] = int(mel.eval('optionVar -q evaluationVisibility;'))
    # 8 - Focal Length
    hud_current_state['focalLengthVisibility'] = int(mel.eval('optionVar -q focalLengthVisibility;'))
    # 9 - Frame Rate
    hud_current_state['frameRateVisibility'] = int(mel.eval('optionVar -q frameRateVisibility;'))
    # 10 - HumanIK Details
    hud_current_state['hikDetailsVisibility'] = int(mel.eval('optionVar -q hikDetailsVisibility;'))
    # 11 - Material Loading Details
    hud_current_state['materialLoadingDetailsVisibility'] = int(
        mel.eval('optionVar -q materialLoadingDetailsVisibility;'))
    # 12 - Object Details
    hud_current_state['objectDetailsVisibility'] = int(mel.eval('optionVar -q objectDetailsVisibility;'))
    # 13 - Origin Axis - Ignored as non-hud element
    # hud_current_state['originAxesMenuUpdate'] = mel.eval('optionVar -q originAxesMenuUpdate;')
    # 14 - Particle Count
    hud_current_state['particleCountVisibility'] = int(mel.eval('optionVar -q particleCountVisibility;'))
    # 15 - Poly Count
    hud_current_state['polyCountVisibility'] = int(mel.eval('optionVar -q polyCountVisibility;'))
    # 16 - Scene Timecode
    hud_current_state['sceneTimecodeVisibility'] = int(mel.eval('optionVar -q sceneTimecodeVisibility;'))
    # 17 - Select Details
    hud_current_state['selectDetailsVisibility'] = int(mel.eval('optionVar -q selectDetailsVisibility;'))
    # 18 - Symmetry
    hud_current_state['symmetryVisibility'] = int(mel.eval('optionVar -q symmetryVisibility;'))
    # 19 - View Axis
    hud_current_state['viewAxisVisibility'] = int(mel.eval('optionVar -q viewAxisVisibility;'))
    # 20 - Viewport Renderer
    hud_current_state['viewportRendererVisibility'] = int(mel.eval('optionVar -q viewportRendererVisibility;'))
    # ------- Separator -------
    # 21 - In-view Messages
    hud_current_state['inViewMessageEnable'] = int(mel.eval('optionVar -q inViewMessageEnable;'))
    # 22 - In-view Editors
    hud_current_state['inViewEditorVisible'] = int(mel.eval('optionVar -q inViewEditorVisible;'))
    # Conditional - XGen Info
    hud_current_state['xgenHUDVisibility'] = int(mel.eval('optionVar -q xgenHUDVisibility;'))

    # Check if toggle ON or OFF
    toggle = True
    count = 0
    for item_state in hud_current_state:
        if hud_current_state.get(item_state):
            count += 1
    # More than half is on, so OFF else ON (Default)
    if count > len(hud_current_state) / 2:
        toggle = False

    # Toggles non-standard hud elements
    if toggle:
        mel.eval('setAnimationDetailsVisibility(true)')
        try:
            from maya.plugin.evaluator.CacheUiHud import CachePreferenceHud
            CachePreferenceHud().set_value(True)
        except Exception as e:
            logger.debug(str(e))
        mel.eval('setCameraNamesVisibility(true)')
        mel.eval('setCapsLockVisibility(true)')
        mel.eval('setCurrentContainerVisibility(true)')
        mel.eval('setCurrentFrameVisibility(true)')
        mel.eval('SetEvaluationManagerHUDVisibility(1)')
        mel.eval('setFocalLengthVisibility(true)')
        mel.eval('setFrameRateVisibility(true)')
        if not hud_current_state.get('hikDetailsVisibility'):
            cmds.ToggleHikDetails()
            mel.eval('catchQuiet(setHikDetailsVisibility(true));')
        mel.eval('ToggleMaterialLoadingDetailsHUDVisibility(true)')
        mel.eval('setObjectDetailsVisibility(true)')
        mel.eval('setParticleCountVisibility(true)')
        mel.eval('setPolyCountVisibility(true)')
        mel.eval('setSceneTimecodeVisibility(true)')
        mel.eval('setSelectDetailsVisibility(true)')
        mel.eval('setSymmetryVisibility(true)')
        mel.eval('setViewAxisVisibility(true)')
        mel.eval('setViewportRendererVisibility(true)')
        mel.eval('catchQuiet(setXGenHUDVisibility(true));')

        if not hud_current_state.get('inViewMessageEnable'):
            cmds.ToggleInViewMessage()
        if not hud_current_state.get('inViewEditorVisible'):
            cmds.ToggleInViewEditor()
    else:
        mel.eval('setAnimationDetailsVisibility(false)')
        try:
            from maya.plugin.evaluator.CacheUiHud import CachePreferenceHud
            CachePreferenceHud().set_value(False)
        except Exception as e:
            logger.debug(str(e))
        mel.eval('setCurrentContainerVisibility(false)')
        mel.eval('setCurrentFrameVisibility(false)')
        mel.eval('SetEvaluationManagerHUDVisibility(0)')
        mel.eval('setFocalLengthVisibility(false)')
        mel.eval('setFrameRateVisibility(false)')
        if hud_current_state.get('hikDetailsVisibility'):
            cmds.ToggleHikDetails()
            mel.eval('catchQuiet(setHikDetailsVisibility(false));')
            mel.eval('catchQuiet(hikDetailsKeyingMode());')
        mel.eval('ToggleMaterialLoadingDetailsHUDVisibility(false)')
        mel.eval('setObjectDetailsVisibility(false)')
        mel.eval('setParticleCountVisibility(false)')
        mel.eval('setPolyCountVisibility(false)')
        mel.eval('setSceneTimecodeVisibility(false)')
        mel.eval('setSelectDetailsVisibility(false)')
        mel.eval('setViewportRendererVisibility(false)')
        mel.eval('catchQuiet(setXGenHUDVisibility(false));')
    # Default states are preserved: camera names, caps lock, symmetry, view axis, in-view messages and in-view editor

def gtu_convert_joints_to_mesh(combine_mesh=True):
    """
    Converts a joint hierarchy into a mesh representation of it (Helpful when sending it to sculpting apps)
    Args:
        combine_mesh: Combines generated meshes into one

    Returns:
        A list of generated meshes
    """
    selection = cmds.ls(selection=True, type='joint')
    if len(selection) != 1:
        cmds.warning('Please selection only the root joint and try again.')
        return
    cmds.select(selection[0], replace=True)
    cmds.select(hierarchy=True)
    joints = cmds.ls(selection=True, type='joint')

    generated_mesh = []
    for obj in reversed(joints):
        if cmds.objExists(obj):
            joint_name = obj.split('|')[-1]
            radius = cmds.getAttr(obj + '.radius')
            joint_sphere = cmds.polySphere(radius=radius * .5,
                                           subdivisionsAxis=8,
                                           subdivisionsHeight=8,
                                           axis=[1, 0, 0],
                                           name=joint_name + 'JointMesh',
                                           ch=False)
            generated_mesh.append(joint_sphere[0])
            cmds.delete(cmds.parentConstraint(obj, joint_sphere))
            joint_parent = cmds.listRelatives(obj, parent=True) or []
            if joint_parent:
                joint_cone = cmds.polyCone(radius=radius * .5,
                                           subdivisionsAxis=4,
                                           name=joint_name + 'BoneMesh',
                                           ch=False)
                generated_mesh.append(joint_cone[0])
                bbox = cmds.exactWorldBoundingBox(joint_cone)
                bottom = [(bbox[0] + bbox[3]) / 2, bbox[1], (bbox[2] + bbox[5]) / 2]
                cmds.xform(joint_cone, piv=bottom, ws=True)
                cmds.move(1, joint_cone, moveY=True)
                cmds.rotate(90, joint_cone, rotateX=True)
                cmds.rotate(90, joint_cone, rotateY=True)
                cmds.makeIdentity(joint_cone, rotate=True, apply=True)

                cmds.delete(cmds.parentConstraint(joint_parent, joint_cone))
                cmds.delete(cmds.aimConstraint(obj, joint_cone))

                child_pos = cmds.xform(obj, t=True, ws=True, query=True)
                cmds.xform(joint_cone[0] + '.vtx[4]', t=child_pos, ws=True)
    if combine_mesh:
        cmds.select(generated_mesh, replace=True)
        mesh = cmds.polyUnite()
        cmds.select(clear=True)
        cmds.delete(mesh, constructionHistory=True)
        mesh = cmds.rename(mesh[0], selection[0] + 'AsMesh')
        return [mesh]
    else:
        return generated_mesh

def output_string_to_notepad(string, file_name='tmp'):
    """
    Creates a txt file and writes a list of objects to it (with necessary code used to select it, in Mel and Python)

    Args:
        string (string): A list of string to be exported to a txt file
        file_name (string): Name of the generated file

    """
    temp_dir = cmds.internalVar(userTmpDir=True)
    txt_file = temp_dir + file_name + '.txt'

    f = open(txt_file, 'w')
    f.write(string)
    f.close()

    notepad_command = 'exec("notepad ' + txt_file + '");'
    mel.eval(notepad_command)

""" ____________________________ Functions Calls ____________________________"""
if __name__ == '__main__':
    pass
    # gtu_reload_file()
    # gtu_open_resource_browser()
    # gtu_unlock_default_channels()
    # gtu_unhide_default_channels()
    # gtu_import_references()
    # gtu_remove_references()
    # gtu_uniform_lra_toggle()
    # gtu_uniform_jnt_label_toggle()
    # gtu_select_non_unique_objects()

    # gtu_generate_udim_previews()
    # gtu_copy_material()
    # gtu_paste_material()

    # gtu_move_pivot_to_top()
    # gtu_move_pivot_to_base()
    # gtu_move_to_origin()

    # gtu_reset_joint_sizes()
    # gtu_reset_transforms()
    # gtu_reset_persp_shape_attributes()

    # gtu_delete_namespaces()
    # gtu_delete_display_layers()
    # gtu_delete_keyframes()
    # gtu_delete_nucleus_nodes()
    # gtu_delete_user_defined_attributes()

    # --- Outside Utilities ---
    # gtu_combine_curves()
    # gtu_separate_curves()
    # gtu_convert_bif_to_mesh()

    # gtu_build_gui_about_gt_tools()

    # --- Other Functions ---
    # gtu_delete_all_locators()
    # gtu_full_hud_toggle()
    # gtu_convert_joints_to_mesh()
    # output_string_to_notepad('Test')


import socket
import time
import sys
from simple_chalk import greenBright, redBright, blueBright, cyanBright
from setting import server_host, server_port

from setting import server_host, server_port

HOST = server_host
PORT = server_port

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect((HOST, PORT))
    s.send((str(sys.argv[1])+","+str(sys.argv[2])+",P,Pairing;").encode())

    while True:
        data = s.recv(1024)
        decoded_data = data.decode()
        split_data = decoded_data.split(',')
        print(blueBright("Received Message: ") + decoded_data)

        if (split_data[0] == "ACK"):
            print("The server has acknowledged message.")
        elif ("SUCCESS" in decoded_data):
            time.sleep(0.1)
            reply_to_return = str(sys.argv[1])+","+str(sys.argv[2])+",R;"
            s.send(reply_to_return.encode())
            print(cyanBright("Replied with: " + reply_to_return))
        elif (str(sys.argv[1])+","+str(sys.argv[2])+",R,QC;" in decoded_data):
            time.sleep(0.1)
            reply_to_return = "ACK," + decoded_data
            s.send(reply_to_return.encode())
            print(cyanBright("Replied with: " + reply_to_return))
        elif (str(sys.argv[1])+","+str(sys.argv[2])+",N,A" in decoded_data):
            time.sleep(0.1)
            reply_to_return = "ACK," + decoded_data
            s.send(reply_to_return.encode())
            print(cyanBright("Replied with: " + reply_to_return))

            time.sleep(0.1)
            reply_to_return = str(sys.argv[1]) + ","+str(sys.argv[2])+","+split_data[2] + "," + split_data[3] + ",JD," + split_data[4]
            s.send(reply_to_return.encode())
            print(cyanBright("Replied with: " + reply_to_return))


from date import date_simple as ds

dateobj1 = get_date()                      # datetime.date object for today
print(dateobj1)

datestr1 = ds.format_date(dateobj1)        # formated string for today
print(datestr1)

datestr2 = ds.format_date(dateobj1,format='MM/DD/YYY')   # formated string ('MM/DD/YYY') for today
print(datestr2)

date_add_day = ds.add_day(dateobj1,1)   # one more day from today
print(date_add_day)

date_add_week = ds.add_week(dateobj1,1)   # one more week from today
print(date_add_week)

date_add_month = ds.add_month(dateobj1)   # one more month from today
print(date_add_month)

date_add_year=ds.add_year(dateobj1)      # one more year from today
print(date_add_year)

dateobj2 = ds.get_date(dt='bad date')    # raises a ValueError exception

datestr3 = ds.format_date(dt=not_a_date_object) # raises a TypeError exception

datestr4 = ds.format_date(dt=dateobj2, format='unrecognized format') # raises a ValueError exception


# noqa: D100

import logging
from abc import ABC, abstractmethod
from functools import reduce, wraps
from typing import Any, Callable, Dict, Iterable, List, Optional

import hail as hl
from hail.linalg import BlockMatrix

from gnomad.resources.config import (
    GnomadPublicResourceSource,
    gnomad_public_resource_configuration,
)

logger = logging.getLogger("gnomad.resources")

GNOMAD_PUBLIC_BUCKETS = ("gnomad-public", "gnomad-public-requester-pays")
"""
Public buckets used to stage gnomAD data.

`gnomad-public` is a legacy bucket and contains one readme text file.

The gnomAD Production Team writes output data to `gnomad-public-requester-pays`, and all data in this bucket
syncs to the public bucket `gcp-public-data--gnomad`.
"""

# Resource classes

class BaseResource(ABC):
    """
    Generic abstract resource class.

    :param path: The resource path
    :param import_args: Any sources that are required for the import and need to be kept track of (e.g. .vcf path for an imported VCF)
    :param import_func: A function used to import the resource. `import_func` will be passed the `import_args` dictionary as kwargs.
    """

    expected_file_extensions: List[str] = []
    """Expected file extensions for this resource type. If path doesn't end with one of these, a warning is logged."""

    def __init__(
        self,
        path: Optional[str] = None,
        import_args: Optional[Dict[str, Any]] = None,
        import_func: Optional[Callable] = None,
    ):
        if path is None and import_func is None:
            raise ValueError(
                f"{self.__class__.__name__} requires at least one of path or"
                " import_func arguments."
            )

        self.path = path
        self.import_args = import_args
        self.import_func = import_func

        if (
            path is not None
            and self.expected_file_extensions
            and not any(path.endswith(ext) for ext in self.expected_file_extensions)
        ):
            logger.warning(
                "Created the following %s with a path that doesn't end with %s: %s",
                self.__class__.__name__,
                " or ".join(self.expected_file_extensions),
                self,
            )

    def __repr__(self):
        attr_str = [f"path={self._path}"]
        if self.import_args is not None:
            attr_str.append(f"import_args={self.import_args}")
        return f'{self.__class__.__name__}({",".join(attr_str)})'

    def _get_path(self):
        return self._path

    def _set_path(self, path):
        self._path = path  # pylint: disable=attribute-defined-outside-init

    # Defining path property this way instead of using a decorator allows _get_path and _set_path
    # to be overridden in subclasses without having to reconfigure the property.
    path = property(
        fget=lambda self: self._get_path(),
        fset=lambda self, path: self._set_path(path),
    )

    @abstractmethod
    def import_resource(self, overwrite: bool = True, **kwargs) -> None:
        """
        Abstract method to import the resource using its import_func and writes it in its path.

        :param overwrite: If ``True``, overwrite an existing file at the destination.
        :param kwargs: Any other parameters to be passed to the underlying hail write function (acceptable parameters depend on specific resource types)
        """

class TableResource(BaseResource):
    """
    A Hail Table resource.

    :param path: The Table path (typically ending in .ht)
    :param import_args: Any sources that are required for the import and need to be kept track of and/or passed to the import_func (e.g. .vcf path for an imported VCF)
    :param import_func: A function used to import the Table. `import_func` will be passed the `import_args` dictionary as kwargs.
    """

    expected_file_extensions: List[str] = [".ht"]

    def ht(self, force_import: bool = False) -> hl.Table:
        """
        Read and return the Hail Table resource.

        :return: Hail Table resource
        """
        if self.path is None or force_import:
            return self.import_func(**self.import_args)
        else:
            return hl.read_table(self.path)

    def import_resource(self, overwrite: bool = True, **kwargs) -> None:
        """
        Import the TableResource using its import_func and writes it in its path.

        :param overwrite: If ``True``, overwrite an existing file at the destination.
        :param kwargs: Any other parameters to be passed to hl.Table.write
        :return: Nothing
        """
        self.import_func(**self.import_args).write(
            self.path, overwrite=overwrite, **kwargs
        )

class MatrixTableResource(BaseResource):
    """
    A Hail MatrixTable resource.

    :param path: The MatrixTable path (typically ending in .mt)
    :param import_args: Any sources that are required for the import and need to be kept track of and/or passed to the import_func (e.g. .vcf path for an imported VCF)
    :param import_func: A function used to import the MatrixTable. `import_func` will be passed the `import_args` dictionary as kwargs.
    """

    expected_file_extensions: List[str] = [".mt"]

    def mt(self, force_import: bool = False) -> hl.MatrixTable:
        """
        Read and return the Hail MatrixTable resource.

        :return: Hail MatrixTable resource
        """
        if self.path is None or force_import:
            return self.import_func(**self.import_args)
        else:
            return hl.read_matrix_table(self.path)

    def import_resource(self, overwrite: bool = True, **kwargs) -> None:
        """
        Import the MatrixTable resource using its import_func and writes it in its path.

        :param overwrite: If set, existing file(s) will be overwritten
        :param kwargs: Any other parameters to be passed to hl.MatrixTable.write
        :return: Nothing
        """
        self.import_func(**self.import_args).write(
            self.path, overwrite=overwrite, **kwargs
        )

class VariantDatasetResource(BaseResource):
    """
    A Hail VariantDataset resource.

    :param path: The VariantDataset path (typically ending in .vds)
    :param import_args: Any sources that are required for the import and need to be kept track of and/or passed to the import_func (e.g. .vcf path for an imported VCF)
    :param import_func: A function used to import the VariantDataset. `import_func` will be passed the `import_args` dictionary as kwargs.
    """

    expected_file_extensions: List[str] = [".vds"]

    def vds(self, force_import: bool = False) -> hl.vds.VariantDataset:
        """
        Read and return the Hail VariantDataset resource.

        :return: Hail VariantDataset resource
        """
        if self.path is None or force_import:
            return self.import_func(**self.import_args)
        else:
            return hl.vds.read_vds(self.path)

    def import_resource(self, overwrite: bool = True, **kwargs) -> None:
        """
        Import the VariantDataset resource using its import_func and writes it in its path.

        :param overwrite: If set, existing file(s) will be overwritten
        :param kwargs: Any other parameters to be passed to hl.vds.VariantDataset.write
        :return: Nothing
        """
        self.import_func(**self.import_args).write(
            self.path, overwrite=overwrite, **kwargs
        )

class PedigreeResource(BaseResource):
    """
    A pedigree resource.

    :param path: The Pedigree path (typically ending in .fam or .ped)
    :param import_args: Any sources that are required for the import and need to be kept track of and/or passed to the import_func (e.g. .vcf path for an imported VCF)
    :param import_func: A function used to import the Pedigree. `import_func` will be passed the `import_args` dictionary as kwargs.
    :param quant_pheno: If ``True``, phenotype is interpreted as quantitative.
    :param delimiter: Field delimiter regex.
    :param missing: The string used to denote missing values. For case-control, 0, -9, and non-numeric are also treated as missing.
    """

    expected_file_extensions: List[str] = [".fam", ".ped"]

    def __init__(
        self,
        path: Optional[str] = None,
        import_args: Optional[Dict[str, Any]] = None,
        import_func: Optional[Callable[..., hl.Pedigree]] = None,
        quant_pheno: bool = False,
        delimiter: str = r"\\s+",
        missing: str = "NA",
    ):
        super().__init__(
            path=path,
            import_args=import_args,
            import_func=import_func,
        )

        self.quant_pheno = quant_pheno
        self.delimiter = delimiter
        self.missing = missing

    def ht(self) -> hl.Table:
        """
        Read the pedigree into a family HT using hl.import_fam().

        :return: Family table
        """
        return hl.import_fam(
            self.path,
            quant_pheno=self.quant_pheno,
            delimiter=self.delimiter,
            missing=self.missing,
        )

    def pedigree(self) -> hl.Pedigree:
        """
        Read the pedigree into an hl.Pedigree using hl.Pedigree.read().

        :param delimiter: Delimiter used in the ped file
        :return: pedigree
        """
        return hl.Pedigree.read(self.path, delimiter=self.delimiter)

    def import_resource(self, overwrite: bool = True, **kwargs) -> None:
        """
        Import the Pedigree resource using its import_func and writes it in its path.

        :param overwrite: If set, existing file(s) will be overwritten. IMPORTANT: Currently there is no implementation of this method when `overwrite` is set the `False`
        :param kwargs: Any other parameters to be passed to hl.Pedigree.write
        :return: Nothing
        """
        if not overwrite:
            raise NotImplementedError

        self.import_func(**self.import_args).write(self.path)

class BlockMatrixResource(BaseResource):
    """
    A Hail BlockMatrix resource.

    :param path: The BlockMatrix path (typically ending in .bm)
    :param import_args: Any sources that are required for the import and need to be kept track of and/or passed to the import_func.
    :param import_func: A function used to import the BlockMatrix. `import_func` will be passed the `import_args` dictionary as kwargs.
    """

    expected_file_extensions: List[str] = [".bm"]

    def bm(self) -> BlockMatrix:
        """
        Read and return the Hail MatrixTable resource.

        :return: Hail MatrixTable resource
        """
        return BlockMatrix.read(self.path)

    def import_resource(self, overwrite: bool = True, **kwargs) -> None:
        """
        Import the BlockMatrixResource using its import_func and writes it in its path.

        :param overwrite: If ``True``, overwrite an existing file at the destination.
        :param kwargs: Any additional parameters to be passed to BlockMatrix.write
        :return: Nothing
        """
        self.import_func(**self.import_args).write(
            self.path, overwrite=overwrite, **kwargs
        )

class ExpressionResource(BaseResource):
    """
    A Hail Expression resource.

    :param path: The Expression path (typically ending in .he).
    :param import_args: Any sources that are required for the import and need to be
        kept track of and/or passed to the import_func (e.g. .vcf path for an imported
        VCF).
    :param import_func: A function used to import the Expression. `import_func` will be
        passed the `import_args` dictionary as kwargs.
    """

    expected_file_extensions: List[str] = [".he"]

    def he(self, force_import: bool = False) -> hl.expr.Expression:
        """
        Read and return the Hail Expression resource.

        :return: Hail Expression resource.
        """
        if self.path is None or force_import:
            return self.import_func(**self.import_args)
        else:
            return hl.experimental.read_expression(self.path)

    def import_resource(self, overwrite: bool = True, **kwargs) -> None:
        """
        Import the Expression resource using its import_func and writes it in its path.

        :param overwrite: If set, existing file(s) will be overwritten.
        :param kwargs: Any other parameters to be passed to hl.experimental.
            write_expression.
        :return: Nothing.
        """
        self.import_func(**self.import_args).write(
            self.path, overwrite=overwrite, **kwargs
        )

class BaseVersionedResource:
    """
    Class for a versioned resource.

    The attributes and methods of the versioned resource are those of the default version of the resource.
    In addition, all versions of the resource are stored in the `versions` attribute.

    :param default_version: The default version of this resource (must be in the `versions` dict)
    :param versions: A dict of version name -> resource.
    """

    resource_class = BaseResource

    __slots__ = {"default_version", "versions"}

    def __init__(self, default_version: str, versions: Dict[str, BaseResource]):
        default_resource = versions[default_version]

        for version_resource in versions.values():
            if not isinstance(version_resource, self.resource_class):
                raise TypeError(
                    f"{self.__class__.__name__} requires all versions to be of type"
                    f" {self.resource_class.__name__}"
                )

            if version_resource.__class__ is not default_resource.__class__:
                raise TypeError(
                    f"{self.__class__.__name__} requires all versions to be of the same"
                    " type"
                )

        self.default_version = default_version
        self.versions = versions

    def __repr__(self):
        return (
            "{cls}(default_version={default_version}, versions={{{versions}}})".format(
                cls=self.__class__.__name__,
                default_version=self.default_version,
                versions=", ".join(
                    f'"{k}": {repr(v)}' for k, v in self.versions.items()
                ),
            )
        )

    def __getattr__(self, name):
        # If __getattr__ is called for 'default_version', 'version', etc. then
        # something has gone wrong.
        if name in self.__slots__:
            raise ValueError("VersionedResource has not been initialized")

        return getattr(self.versions[self.default_version], name)

class VersionedTableResource(BaseVersionedResource):
    """
    Versioned Table resource.

    The attributes (path, import_args and import_func) of the versioned resource are those of the default version of the resource.
    In addition, all versions of the resource are stored in the `versions` attribute.

    :param default_version: The default version of this Table resource (must to be in the `versions` dict)
    :param versions: A dict of version name -> TableResource.
    """

    resource_class = TableResource

    def __init__(self, default_version: str, versions: Dict[str, TableResource]):
        super().__init__(default_version, versions)

class VersionedMatrixTableResource(BaseVersionedResource):
    """
    Versioned MatrixTable resource.

    The attributes (path, import_args and import_func) of the versioned resource are those of the default version of the resource.
    In addition, all versions of the resource are stored in the `versions` attribute.

    :param default_version: The default version of this MatrixTable resource (must to be in the `versions` dict)
    :param versions: A dict of version name -> MatrixTableResource.
    """

    resource_class = MatrixTableResource

    def __init__(self, default_version: str, versions: Dict[str, MatrixTableResource]):
        super().__init__(default_version, versions)

class VersionedVariantDatasetResource(BaseVersionedResource):
    """
    Versioned VariantDataset resource.

    The attributes (path, import_args and import_func) of the versioned resource are those of the default version of the resource.
    In addition, all versions of the resource are stored in the `versions` attribute.
    :param default_version: The default version of this VariantDataset resource (must to be in the `versions` dict)

    :param versions: A dict of version name -> VariantDatasetResource.
    """

    resource_class = VariantDatasetResource

    def __init__(
        self, default_version: str, versions: Dict[str, VariantDatasetResource]
    ):
        super().__init__(default_version, versions)

class VersionedPedigreeResource(BaseVersionedResource, PedigreeResource):
    """
    Versioned Pedigree resource.

    The attributes (path, import_args and import_func) of the versioned resource are those of the default version of the resource.
    In addition, all versions of the resource are stored in the `versions` attribute.

    :param default_version: The default version of this Pedigree resource (must be in the `versions` dict)
    :param versions: A dict of version name -> PedigreeResource.
    """

    resource_class = PedigreeResource

    def __init__(self, default_version: str, versions: Dict[str, PedigreeResource]):
        super().__init__(default_version, versions)

class VersionedBlockMatrixResource(BaseVersionedResource, BlockMatrixResource):
    """
    Versioned BlockMatrix resource.

    The attributes (path, import_args and import_func) of the versioned resource are those of the default version of the resource.
    In addition, all versions of the resource are stored in the `versions` attribute.

    :param default_version: The default version of this BlockMatrix resource (must to be in the `versions` dict)
    :param versions: A dict of version name -> BlockMatrixResource.
    """

    resource_class = BlockMatrixResource

    def __init__(self, default_version: str, versions: Dict[str, BlockMatrixResource]):
        super().__init__(default_version, versions)

class ResourceNotAvailable(Exception):
    """Exception raised if a resource is not available from the selected source."""

class GnomadPublicResource(BaseResource, ABC):
    """Base class for the gnomAD project's public resources."""

    def __init_subclass__(cls, *, read_resource_methods: Iterable[str] = []) -> None:
        super().__init_subclass__()

        # Some resources may not be available from all sources due to delays in syncing, etc.
        # This wraps all methods that read the resource and adds a check for if the resource
        # is available from the selected source. If the resource is not available, this
        # throws a more helpful error than if the read were simply allowed to fail.
        def _wrap_read_resource_method(method_name):
            original_method = getattr(cls, method_name)

            @wraps(original_method)
            def read_resource(self, *args, **kwargs):
                # If one of the known sources is selected, check if the resource is available.
                # For custom sources, skip the check and attempt to read the resource.
                resource_source = gnomad_public_resource_configuration.source
                if not self.is_resource_available():
                    if resource_source == GnomadPublicResourceSource.GNOMAD:
                        message = (
                            "This resource is not currently available from the gnomAD"
                            " project public buckets."
                        )
                    elif isinstance(resource_source, GnomadPublicResourceSource):
                        message = (
                            "This resource is not currently available from"
                            f" {resource_source.value}."
                        )
                    else:
                        message = (
                            "This resource is not currently available from"
                            f" {resource_source}."
                        )

                    raise ResourceNotAvailable(
                        f"{message}\n\nTo load resources from a different source (for"
                        " example, Google Cloud Public Datasets) instead, use:\n\n>>>"
                        " from gnomad.resources.config import"
                        " gnomad_public_resource_configuration,"
                        " GnomadPublicResourceSource\n>>>"
                        " gnomad_public_resource_configuration.source ="
                        " GnomadPublicResourceSource.GOOGLE_CLOUD_PUBLIC_DATASETS\n\nTo"
                        " get all available sources for gnomAD resources, use:\n\n>>>"
                        " from gnomad.resources.config import"
                        " GnomadPublicResourceSource\n>>>"
                        " list(GnomadPublicResourceSource)"
                    )

                return original_method(self, *args, **kwargs)

            setattr(cls, method_name, read_resource)

        for method_name in read_resource_methods:
            _wrap_read_resource_method(method_name)

    def _get_path(self) -> str:
        resource_source = gnomad_public_resource_configuration.source
        if resource_source == GnomadPublicResourceSource.GNOMAD:
            return self._path

        relative_path = reduce(
            lambda path, bucket: (
                path[5 + len(bucket) :] if path.startswith(f"gs://{bucket}/") else path
            ),
            GNOMAD_PUBLIC_BUCKETS,
            self._path,
        )

        if resource_source == GnomadPublicResourceSource.GOOGLE_CLOUD_PUBLIC_DATASETS:
            return f"gs://gcp-public-data--gnomad{relative_path}"

        if resource_source == GnomadPublicResourceSource.REGISTRY_OF_OPEN_DATA_ON_AWS:
            return f"s3a://gnomad-public-us-east-1{relative_path}"

        if resource_source == GnomadPublicResourceSource.AZURE_OPEN_DATASETS:
            return f"wasbs://dataset@datasetgnomad.blob.core.windows.net{relative_path}"

        return (
            f"{resource_source.rstrip('/')}{relative_path}"  # pylint: disable=no-member
        )

    def _set_path(self, path):
        if not any(
            path.startswith(f"gs://{bucket}/") for bucket in GNOMAD_PUBLIC_BUCKETS
        ):
            raise ValueError(
                "GnomadPublicResource requires a path to a file in one of the public"
                f" gnomAD buckets ({', '.join(GNOMAD_PUBLIC_BUCKETS)})"
            )

        return super()._set_path(path)

    def is_resource_available(self) -> bool:
        """
        Check if this resource is available from the selected source.

        :return: True if the resource is available.
        """
        path = self.path

        # Hail Tables, MatrixTables, and BlockMatrices are directories.
        # For those, check for the existence of the _SUCCESS object.
        path_to_test = (
            f"{path}/_SUCCESS"
            if any(path.endswith(ext) for ext in (".ht", ".mt", ".bm"))
            else path
        )

        return hl.current_backend().fs.exists(path_to_test)

class GnomadPublicTableResource(
    TableResource, GnomadPublicResource, read_resource_methods=("ht",)
):
    """Resource class for a public Hail Table published by the gnomAD project."""

class GnomadPublicMatrixTableResource(
    MatrixTableResource, GnomadPublicResource, read_resource_methods=("mt",)
):
    """Resource class for a public Hail MatrixTable published by the gnomAD project."""

class GnomadPublicPedigreeResource(
    PedigreeResource, GnomadPublicResource, read_resource_methods=("ht", "pedigree")
):
    """Resource class for a public pedigree published by the gnomAD project."""

class GnomadPublicBlockMatrixResource(
    BlockMatrixResource, GnomadPublicResource, read_resource_methods=("bm",)
):
    """Resource class for a public Hail BlockMatrix published by the gnomAD project."""

class DataException(Exception):  # noqa: D101
    pass

NO_CHR_TO_CHR_CONTIG_RECODING = {
    "1": "chr1",
    "2": "chr2",
    "3": "chr3",
    "4": "chr4",
    "5": "chr5",
    "6": "chr6",
    "7": "chr7",
    "8": "chr8",
    "9": "chr9",
    "10": "chr10",
    "11": "chr11",
    "12": "chr12",
    "13": "chr13",
    "14": "chr14",
    "15": "chr15",
    "16": "chr16",
    "17": "chr17",
    "18": "chr18",
    "19": "chr19",
    "20": "chr20",
    "21": "chr21",
    "22": "chr22",
    "X": "chrX",
    "Y": "chrY",
    "MT": "chrM",
}

DBSNP_B154_CHR_CONTIG_RECODING = {
    "NC_000001.11": "chr1",
    "NC_000002.12": "chr2",
    "NC_000003.12": "chr3",
    "NC_000004.12": "chr4",
    "NC_000005.10": "chr5",
    "NC_000006.12": "chr6",
    "NC_000007.14": "chr7",
    "NC_000008.11": "chr8",
    "NC_000009.12": "chr9",
    "NC_000010.11": "chr10",
    "NC_000011.10": "chr11",
    "NC_000012.12": "chr12",
    "NC_000013.11": "chr13",
    "NC_000014.9": "chr14",
    "NC_000015.10": "chr15",
    "NC_000016.10": "chr16",
    "NC_000017.11": "chr17",
    "NC_000018.10": "chr18",
    "NC_000019.10": "chr19",
    "NC_000020.11": "chr20",
    "NC_000021.9": "chr21",
    "NC_000022.11": "chr22",
    "NC_000023.11": "chrX",
    "NC_000024.10": "chrY",
}

def import_sites_vcf(**kwargs) -> hl.Table:
    """Import site-level data from a VCF into a Hail Table."""
    return hl.import_vcf(**kwargs).rows()


import pickle

import matplotlib.pyplot as plt
import numpy as np

from common_data import scenarios, hours, units, unit_to_generation_type
plt.rcParams.update({'font.size': 18})

# 0: coal
# 1: gas
# 2: ccgt
# 3: oil
# 4: biomass
# 5: oil shale
# 6: nuclear
# 7: hydro
# 8: wind
# 9: pv
# different chp types (e=extraction, b=back pressure):
# 10: coal-e
# 11: gas-b
# 12: gas-e
# 13: oil-b
# 14: oil-e
# 15: biomass-e
# 16: waste-e
# 17: peat-e
fuel_types = [
    "coal",
    "gas",
    "oil",
    "biomass",
    "oil shale",
    "nuclear",
    "hydro",
    "wind",
    "solar",
]

order = [6, 8, 7, 3, 5, 0, 1, 4, 2]

generation_type_to_fuel_type = {
    0: 0,
    1: 1,
    2: 1,
    3: 2,
    4: 3,
    5: 4,
    6: 5,
    7: 6,
    8: 7,
    9: 8,
    10: 0,
    11: 1,
    12: 1,
    13: 2,
    14: 2,
    15: 3,
    16: 3,
    17: 3,
}

titles = ["no emission constraint", "with emission constraint"]
# light gray, red, black, green, dark gray, yellow, dark blue, light blue, orange
colors = [
    "#a6a6a6",
    "#ff0000",
    "#000000",
    "#00ff08",
    "#636363",
    "#fffb00",
    "#00058a",
    "#99dbff",
    "#ffa200",
]

def mysort(X):
    return [X[o] for o in order]

fig, ax = plt.subplots(2, 2, figsize=(14, 12))

with open("stoch.pickle", "rb") as file_:
    data = pickle.load(file_)

idx = 0

models = list(data.keys())
if "stoch" in models[0]:
    sorted_models = [models[1], models[0]]
else:
    sorted_models = models

for model in sorted_models:
    operation_decisions = data[model]
    g = operation_decisions["g"]
    f = operation_decisions["f"]

    for idx2, (offset, year) in enumerate([(0, 1), (len(hours) - 24, 10)]):
        g_by_fuel = [0.0] * len(fuel_types)

        for o in scenarios:
            for u in units:
                generation_type = unit_to_generation_type[u]
                fuel = generation_type_to_fuel_type[generation_type]

                for t in range(24):
                    g_by_fuel[fuel] += g[o, offset + t, u]

        currrent_labels = [
            l + " %.1f" % (g_by_fuel[i] / sum(g_by_fuel) * 100.0) + "%"
            #l + " %.1f" % (g_by_fuel[i] / 1000.0) + "GW"
            for i, l in enumerate(fuel_types)
        ]

        model_name = "SARO"
        if "stoch" in model:
            model_name = "SP"

        current_title = model_name + ", t = %d" % year

        my_pie, texts = ax[idx2, idx].pie(
            mysort(g_by_fuel),
            labels=mysort(currrent_labels),
            colors=mysort(colors),
            radius=0.7,
            # autopct="%.2f",
            #pctdistance=0.9,
            labeldistance=1.1,
            explode=mysort([0.15, 0.15, 0.15, 0.1, 0.15, 0.1, 0.1, 0.1, 0.1]),
        )

        #import pdb; pdb.set_trace()

        for txt in texts:
            t = txt.get_text()

            if "oil shale" in t:
                pos = txt.get_position()
                pos2 = (pos[0], pos[1]-0.025)
                txt.set_position(pos2)

            if "oil" in t and "oil shale" not in t:
                pos = txt.get_position()
                pos2 = (pos[0], pos[1]+0.05)
                txt.set_position(pos2)

            if "gas" in t or "coal" in t:
                pos = txt.get_position()
                pos2 = (pos[0], pos[1]-0.05)
                txt.set_position(pos2)

            if "wind" in t:
                pos = txt.get_position()
                pos2 = (pos[0], pos[1]-0.05)
                txt.set_position(pos2)

            if "biomass" in t:
                pos = txt.get_position()
                pos2 = (pos[0], pos[1]-0.1)
                txt.set_position(pos2)

        ax[idx2, idx].set_title(current_title)

    idx = idx + 1

plt.savefig("generation_mixes_stoch.png", bbox_inches="tight")

# fuel_types = [
#     "coal",       0
#     "gas",        1
#     "oil",        2
#     "biomass",    3
#     "oil shale",  4
#     "nuclear",    5
#     "hydro",      6
#     "wind",       7
#     "solar",      8
# ]


# Simple wrapper for tableau functions

from pandleau import *
import datetime
import os

path_tableau = "./tableau/"
path_logs = "./logs/"

def makeConversion(df, filename) :
    """convert dataframe to .hyper extract"""

    if not os.path.isdir(path_tableau):
        os.mkdir(path_tableau)

    df_tmp = pandleau(df)

    # remove if file exists, write out new file
    file_out = os.path.join(path_tableau, "{0}.hyper".format(filename))

    if os.path.isfile(file_out):
        os.remove(file_out)

    df_tmp.to_tableau(file_out, add_index=False)

    print("PYTHON: {0}".format(datetime.datetime.now().strftime("%d/%m/%Y %H:%M")))

def cleanLogs():
    """clean log files function"""

    if not os.path.isdir(path_logs):
        os.mkdir(path_logs)

    files_logs = [
        f for f in os.listdir("./")
        if os.path.isfile(os.path.join("./", f)) and ".log" in f
        or "hyper_db_" in f
    ]

    for file in files_logs:
        os.rename(file, os.path.join(path_logs, file))

    print("\nPYTHON: Directory cleaned")
    print("PYTHON: {0}".format(datetime.datetime.now().strftime("%d/%m/%Y %H:%M")))


import os
import binascii

# print (file_list)

def find_reach(file_path):
	with open(file_path,"rb") as f :

		while(True):
			rich = f.read(4)
			if (rich == b'\x52\x69\x63\x68'): #Rich -> \x52\x69\x63\x68
				data = f.read(4)
				#print (file_path + " : " + str(binascii.b2a_hex(data)))
				return binascii.b2a_hex(data)
			if (rich ==b''):
				return ''

def main():
	path_dir = ".\\yara_sample"
	file_list = os.listdir(path_dir)

	rich_list = []

	for file in file_list :
		rich_result = find_reach(path_dir + "\\" +file)
		# print (rich_result)
		if (rich_result !=''):
			rich_list.append(rich_result)

	# print (rich_list)
	rich_list = list(set(rich_list))
	# print (rich_list)
#b'de86971f'
	# for rich in rich_list:
	# 	print (rich)

	# 	break
	i = 0
	for rich in rich_list:
		i +=1
		print ("$rich_" + str(i) + " = { " + "52 69 63 68 "+ str(rich[0:2])[2:4] + " " + str(rich[2:4])[2:4] + " " + str(rich[4:6])[2:4] + " " + str(rich[6:8])[2:4] + " }")
		# print (rich[0:2])
		# print (rich[2:4])
		# print (rich[4:6])
		# print (rich[6:8])
		# break

	# i = 0
	# for rich in rich_list :
	# 	i +=1
	# 	rich = str(rich).replace("'","")
	# 	print ("$rich_" + str(i) + " = { " + rich[0:2] + " " + rich[2:4] + " " + rich[4:6] + " " + rich[6:8] + " }")

if __name__ == "__main__":
	main()


import csv
import random
import pickle

from itertools import combinations

import numpy as np

from matplotlib import pyplot as plt
from svmutil import (
    svm_parameter,
    svm_predict,
    svm_problem,
    svm_train,
)

from common import plot_confusion, miss_rate, accuracy

DATA = "data/mnist/"

def read_data(fn):
    y, x = [], []
    with open(DATA + fn + ".csv") as f:
        for row in csv.reader(f, delimiter=','):
            y.append(int(row[-1]))
            x.append([int(n) for n in row[:-1]])
    return y, x

def svm_convert_data(fn):
    """
    Convert our data set into a format that libsvm can read.
    """

    with open(DATA + fn + "-svm", "w") as out:
        for y, x in zip(*read_data(fn)):
            out.write("%d " % y)
            out.write(" ".join(
                ["%d:%d" % (i, x) for i, x in enumerate(x) if x])
            )
            out.write("\n")

def normalize(data):
    """Scale down features to range 0-1, for faster convergence."""
    X = np.asarray(data)

    mn = np.min(X, axis=0)
    mx = np.max(X, axis=0)

    with np.errstate(divide='ignore', invalid='ignore'):
        X = (X - mn) / (mx - mn)
        X = np.nan_to_num(X, copy=False)

    return X

# This is the part a of the assignment
def pegasos(X, y, lmbd=1):
    """
    Pegasos: Primal Estimated sub-GrAdient SOlver for SVM.

    A batch SGD algorithm to find parameters w, b in SVM.

    X: Design matrix (training data's features)
    y: Labels (should be +1 / -1)

    Differences from the Pegasos paper:

    lambda is fixed to 1
    k is 1/C
    """

    m, n = X.shape

    # Batch size
    r = 100
    C = 1.0

    # Initial guess of W
    # What if this gets changed?
    W = np.zeros(n)
    b = 0

    iters = 0
    converged = False

    while not converged:

        iters += 1

        # Because this is stochastic descent
        # we decrease eta as we go ahead
        eta = 1 / iters

        # Find indices of elements in this batch
        batch = np.array(random.sample(range(m), r))
        Xb, yb = X[batch], y[batch]

        # Find examples in this batch for which T < 1
        # Are these points the "support vectors" ?
        T = yb * ((W @ Xb.T) + b)
        Tl1 = np.where(T < 1)

        Wp = W

        W = (1 - eta) * W + eta * C * np.sum(yb[Tl1] * Xb[Tl1].T, axis=1)
        b = b + eta * C * np.sum(yb[Tl1])

        # Convergence criteria
        if all(abs(Wp - W) <= 10**-3):
            converged = True

        # Prevent us from going in infinite loops
        if iters >= 10000:
            print("Abrupt Stop")
            break

    return W, b

def pegasos_train(train_x, train_y):
    """Build 1vs1 SVM classifiers using Pegasos."""

    classifiers = {}
    for classes in combinations(set(train_y), 2):

        # Let's call the first class positive
        # and the other negative
        pos, neg = classes

        # print("Training classifier between classes %d & %d" % (pos, neg))

        # Find examples of these classes
        Xpos = train_x[np.where(train_y == pos)]
        Xneg = train_x[np.where(train_y == neg)]
        X = np.concatenate((Xpos, Xneg))

        # The data contains classes from 1 - 10
        # but SVMs deal with +1 / -1
        y = np.array([1] * len(Xpos) + [-1] * len(Xneg))

        # Fit a classifier and store the parameters
        classifiers[classes] = pegasos(X, y)

    return classifiers

def pegasos_predict(data, classifiers):
    """Make predictions on data using 1v1 classifiers."""

    # Make predictions on test set
    predictions = []

    # Iterate over training set
    for x in data:

        # Prediction for this particular example
        p = []

        # Pass each example to all classifiers
        for classes, params in classifiers.items():
            pos, neg = classes
            W, b = params

            if W.T @ x + b > 0:
                p.append(pos)
            # In case of ties, predict digit with bigger value
            elif W.T @ x + b == 0:
                p.append(max(pos, neg))
            else:
                p.append(neg)

        # Find the class with the most count
        predictions.append(max(p, key=p.count))

    return predictions

def part_b():
    print("\n--- Part B ---\n")

    print("Reading Data")
    train_y, train_x = read_data("train")
    test_y, test_x = read_data("test")

    print("Normalizing")
    # train_x = np.asarray(train_x)
    # test_x = np.asarray(test_x)
    train_x = normalize(train_x)
    test_x = normalize(test_x)
    train_y = np.asarray(train_y)
    test_y = np.asarray(test_y)

    # Build 10_C_2 classifiers
    print("Building Classifiers")
    classifiers = pegasos_train(train_x, train_y)

    with open("models/svm-model-1", "wb") as m:
        pickle.dump(classifiers, m)

    print("Finding Accuracy")
    predictions_test = pegasos_predict(test_x, classifiers)
    acc = accuracy(test_y.tolist(), predictions_test)
    print("Testing Accuracy: %.2f" % (acc * 100))

    predictions_train = pegasos_predict(train_x, classifiers)
    acc = accuracy(train_y.tolist(), predictions_train)
    print("Training Accuracy: %.2f" % (acc * 100))

def part_c():
    print("\n--- Part C ---\n")

    print("Reading Data")
    train_y, train_x = read_data("train")
    test_y, test_x = read_data("test")

    print("Normalizing")
    train_x = normalize(train_x).tolist()
    test_x = normalize(test_x).tolist()

    problem = svm_problem(train_y, train_x)
    params = svm_parameter("-q -s 0 -c 1")

    # Timing calculations
    print("Training SVM (linear kernel)")
    params.parse_options("-t 0")
    model = svm_train(problem, params)

    _, p_acc, _ = svm_predict(test_y, test_x, model)
    print("Accuracy: ", p_acc)

    print("Training SVM (gaussian kernel)")
    params.parse_options("-t 2 -g 0.05")
    model = svm_train(problem, params)

    _, p_acc, _ = svm_predict(test_y, test_x, model)
    print("Accuracy: ", p_acc)

def part_d():
    print("\n--- Part D ---\n")

    print("Reading Data")
    train_y, train_x = read_data("train")
    test_y, test_x = read_data("test")

    print("Normalizing")
    train_x = normalize(train_x)
    test_x = normalize(test_x)

    problem = svm_problem(train_y, train_x)
    params = "-q -s 0 -t 2 -g 0.05"

    results = []
    for c in [10 ** -5, 10 ** -3, 1, 5, 10]:

        c = " -c %f " % c
        print("10-fold CV using" + c)
        cv_acc = svm_train(problem, params + c + "-v 10")

        print("On test data using" + c)
        model = svm_train(problem, params + c)
        _, test_acc, _ = svm_predict(test_y, test_x, model)
        print("C, Accuracy: ", c, cv_acc, test_acc)

        results.append((c, cv_acc, test_acc[0]))

def part_d_2():
    cvals = [0.00001, 0.001, 1, 5, 10]

    # These values were found by running the libsvm CLI tools
    c_acc = [71.59, 71.59, 97.355, 97.455, 97.455]
    t_acc = [72.11, 72.11, 97.23, 97.29, 97.29]

    c_line, = plt.plot(cvals, c_acc, label="Avg. Acc. after 10 fold cross-validation.",
                       linestyle='-', color='r', marker='x')

    t_line, = plt.plot(cvals, t_acc, label="Acc. on Test Set",
                       linestyle='-', color='b', marker='o')

    plt.legend(handles=[c_line, t_line])

    plt.xscale('log')
    plt.xlabel("C")
    plt.ylabel("Accuracy")
    plt.title("Effect of varying value of C on accuracy of Gaussian kernel SVM")
    plt.savefig("part_d_2.png")
    plt.close()

def part_e():

    def imshow(arr, name):
        """Display image from array of values."""
        arr = np.asarray(arr)
        plt.imshow(arr.reshape((28, 28)), cmap=plt.get_cmap('gray'))
        plt.axis('off')
        plt.savefig("miss/%s.png" % name, bbox_inches='tight', pad_inches=0)
        plt.close()

    actual, train_x = read_data("test")

    # These labels were generated by running the
    predicted = np.loadtxt(
        "models/gaussian-test-labels-5", dtype="int").tolist()
    plot_confusion(actual, predicted, range(10), "Gaussian SVM (C=5)")

    predicted = np.loadtxt(
        "models/gaussian-test-labels-10", dtype="int").tolist()
    plot_confusion(actual, predicted, range(10), "Gaussian SVM (C=10)")

    # Find the class that is the hardest to classify using misclassification rate
    mr = miss_rate(actual, predicted)
    print(mr)

    # Examples that actually belong to class 8 but we predict them to be in class 9
    # Found while making the confusion matrix in make_confusion
    a9_p8 = [151, 241, 448, 1107, 2406, 6081, 6091, 6112, 6157, 6168, 617]
    a7_p2 = [810, 1226, 1283, 1754, 1941, 2016, 2325, 2607, 3767,
             4690, 4837, 5887, 7432, 8316, 9009, 9015, 9019, 9024, 9036, 9045]

    # Let us plot these to see what they are
    for idx in a9_p8:
        imshow(train_x[idx], "a9_p8_ex_%d" % idx)

    for idx in a7_p2:
        imshow(train_x[idx], "a7_p2_ex_%d" % idx)

if __name__ == '__main__':

    # In part a we just have to implement pegasos

    part_b()

    # Convert data to a format that libsvm can recognize
    # svm_convert_data("test")
    # svm_convert_data("train")

    # I wrote these parts first and only later found out that we could
    # directly use the C programs provided with libsvm: svm-train etc.
    # Those programs are highly optimized and have very low memory footprint.

    # Look in libsvm.sh on how to call those functions

    # part_c()
    # part_d()

    # part_d_2()

    # part_e()