bl_info = {
    'name': 'GuildEmblems',
    'author': 'Kruithne <patreon.com/kruithne>',
    'version': (1, 0, 0),
    'blender': (2, 92, 0),
    'description': 'Quickly configure guild emblems for objects and characters.',
    'category': 'Material'
}

import bpy
import os
from mathutils import Vector
from random import randrange

TEXTURE_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'textures')

MAX_TABARD_BACKGROUNDS = 51
MAX_TABARD_BORDERS = 6
MAX_TABARD_EMBLEMS = 196
MAX_TABARD_EMBLEM_COLORS = 17
MAX_TABARD_BORDER_COLORS = 17

ITEMS_EMBLEM_TYPES = [
    ('TABARD', 'Tabard', ''),
    ('SHIRT', 'Shirt', ''),
    ('CLOAK', 'Guild Cloak', ''),
    ('MONUMENT', 'Garrison Monument', ''),
    ('CHEST', 'Bank Chest', ''),
    ('CAULDRON', 'Alchemy Cauldron', ''),
    ('BANNER', 'Reputation Banner', '')
]

ITEMS_EMBLEM_MODES = [
    ('ADD', 'Add New Material', 'A new emblem material is created on each object'),
    ('EDIT', 'Append to Active Material', 'Emblem nodes are appended to the active material')
]

ITEMS_TABARD_TYPES = [
    ('COMMON', 'Common', ''),
    ('RARE', 'Rare', ''),
    ('EPIC', 'Epic', '')
]

ITEMS_SHIRT_QUALITY = [
    ('COMMON', 'Common', ''),
    ('RARE', 'Rare (Tailored)', ''),
    ('EPIC', 'Epic (Artisan)', '')
]

ITEMS_SHIRT_RANK = [
    ('INITIATE', 'Initiate', ''),
    ('MEMBER', 'Member', ''),
    ('OFFICER', 'Officer', '')
]

ITEMS_CLOAK_RANK = [
    ('UNCOMMON', 'Shroud of Cooperation', ''),
    ('RARE', 'Wrap of Unity', ''),
    ('EPIC', 'Cloak of Coordination', '')
]

ITEMS_FACTION_TYPES = [
    ('ALLIANCE', 'Alliance', ''),
    ('HORDE', 'Horde', '')
]

SHIRT_RANK_PREFIX = {
    'INITIATE': 'shirt01_',
    'MEMBER': 'shirt02_',
    'OFFICER': 'shirt03_'
}

TABARD_BG_PREFIX = {
    'COMMON': 'background_',
    'RARE': 'rarebg_',
    'EPIC': 'epicbg_'
}

TABARD_BORDER_PREFIX = {
    'COMMON': 'border_',
    'RARE': 'rareborder_',
    'EPIC': 'epicborder_'
}

CLOAK_BG_ID = {
    'UNCOMMON': 'a',
    'RARE': 'b',
    'EPIC': 'c'
}

DB_BACKGROUND_RBG = {
    0: (255, 136, 32),
    1: (189, 91, 0),
    2: (158, 54, 0),
    3: (255, 27, 137),
    4: (225, 0, 69),
    5: (177, 46, 0),
    6: (255, 23, 179),
    7: (246, 0, 135),
    8: (174, 0, 75),
    9: (255, 20, 252),
    10: (243, 0, 202),
    11: (196, 0, 155),
    12: (255, 20, 255),
    13: (216, 0, 221),
    14: (166, 0, 172),
    15: (227, 24, 246),
    16: (183, 3, 192),
    17: (142, 0, 151),
    18: (188, 27, 246),
    19: (136, 3, 186),
    20: (88, 0, 128),
    21: (30, 104, 255),
    22: (4, 71, 195),
    23: (0, 15, 130),
    24: (30, 193, 247),
    25: (4, 143, 183),
    26: (0, 97, 144),
    27: (33, 255, 220),
    28: (0, 197, 157),
    29: (0, 145, 99),
    30: (77, 218, 142),
    31: (44, 174, 106),
    32: (0, 130, 53),
    33: (211, 200, 74),
    34: (173, 172, 41),
    35: (134, 154, 15),
    36: (255, 250, 56),
    37: (201, 195, 0),
    38: (155, 166, 0),
    39: (255, 191, 31),
    40: (211, 135, 0),
    41: (163, 104, 0),
    42: (197, 50, 129),
    43: (135, 19, 85),
    44: (79, 0, 35),
    45: (35, 35, 35),
    46: (100, 100, 100),
    47: (180, 168, 187),
    48: (215, 203, 221),
    49: (255, 255, 255),
    50: (252, 145, 104)
}

DB_BORDER_RBG = {
    0: (103, 33, 0),
    1: (103, 0, 35),
    2: (103, 0, 69),
    3: (103, 0, 86),
    4: (99, 0, 148),
    5: (99, 0, 163),
    6: (99, 0, 179),
    7: (0, 31, 103),
    8: (0, 144, 142),
    9: (0, 147, 103),
    10: (0, 124, 49),
    11: (109, 119, 0),
    12: (123, 103, 0),
    13: (84, 10, 55),
    14: (255, 255, 255),
    15: (15, 21, 20),
    16: (249, 48, 204)
}

DB_EMBLEM_RBG = {
    0: (103, 33, 0),
    1: (103, 0, 35),
    2: (103, 0, 69),
    3: (103, 0, 86),
    4: (99, 0, 103),
    5: (81, 0, 103),
    6: (55, 0, 103),
    7: (0, 31, 103),
    8: (0, 87, 103),
    9: (0, 103, 72),
    10: (9, 93, 42),
    11: (86, 93, 9),
    12: (93, 79, 9),
    13: (84, 10, 55),
    14: (177, 177, 184),
    15: (16, 23, 21),
    16: (223, 90, 165)
}

def to_linear(x):
    if x < 0:
        return 0

    if x < 0.04045:
        return x / 12.92

    return ((x + 0.055) / 1.055) ** 2.4

def update_callback(self, context):
    bpy.ops.guildemblems.apply_design()

def find_node_by_name_or_label(node_tree, node_name, node_label = None):
    if not node_label:
        node_label = node_name

    for node in node_tree.nodes:
        if node.name == node_name or node.label == node_label:
            return node

    return None

class GuildEmblems_Randomize(bpy.types.Operator):
    bl_label = 'Randomize Design'
    bl_idname = 'guildemblems.randomize'
    bl_description = 'Randomize guild emblem design'
    bl_options = {'UNDO'}

    def invoke(self, context, event):
        scene = context.scene
        scene.guild_emblem_background_color = randrange(MAX_TABARD_BACKGROUNDS) + 1
        scene.guild_emblem_border = randrange(MAX_TABARD_BORDERS) + 1
        scene.guild_emblem_border_color = randrange(MAX_TABARD_BORDER_COLORS) + 1
        scene.guild_emblem_design = randrange(MAX_TABARD_EMBLEMS) + 1
        scene.guild_emblem_color = randrange(MAX_TABARD_EMBLEM_COLORS) + 1

        bpy.ops.guildemblems.apply_design()

        return {'FINISHED'}

class GuildEmblems_CopyFromActive(bpy.types.Operator):
    bl_label = 'Copy From Active'
    bl_idname = 'guildemblems.copy'
    bl_description = 'Copy the design settings from the active object'

    def invoke(self, context, event):
        scene = context.scene
        if context.active_object:
            for mat_slot in context.active_object.material_slots:
                material = mat_slot.material
                if material and 'IsEmblemMaterial' in material:
                    context.active_object['SkipEmblemUpdate'] = True
                    scene.guild_emblem_background_color = 'EmblemBackground' in material and material['EmblemBackground'] or scene.guild_emblem_background_color
                    scene.guild_emblem_border_color = 'EmblemBorderColor' in material and material['EmblemBorderColor'] or scene.guild_emblem_border_color
                    scene.guild_emblem_border = 'EmblemBorder' in material and material['EmblemBorder'] or scene.guiild_emblem_border
                    scene.guild_emblem_design = 'EmblemDesign' in material and material['EmblemDesign'] or scene.guild_emblem_design
                    scene.guild_emblem_color = 'EmblemColor' in material and material['EmblemColor'] or scene.guild_emblem_color
                    del context.active_object['SkipEmblemUpdate']

                    self.report({'INFO'}, 'Design copied from active object')
                    break

        return {'FINISHED'}

class GuildEmblems_ApplyDesign(bpy.types.Operator):
    bl_label = 'Apply Design'
    bl_idname = 'guildemblems.apply_design'
    bl_description = 'Applies the configured design to valid selected objects'
    bl_options = {'INTERNAL'}

    def update_node(self, node, image_name):
        if not node:
            return

        old_image = node.image

        if not image_name in bpy.data.images:
            bpy.data.images.load(os.path.join(TEXTURE_DIR, image_name))

        node.image = bpy.data.images[image_name]

        if old_image and old_image != node.image and old_image.users == 0:
            bpy.data.images.remove(old_image)

    def set_rbg(self, node, rbg):
        node.outputs['Color'].default_value = (to_linear(rbg[0] / 255), to_linear(rbg[2] / 255), to_linear(rbg[1] / 255), 1.0)

    def execute(self, context):
        scene = context.scene
        touched_materials = []
        for obj in context.selected_objects:
            if 'SkipEmblemUpdate' in obj:
                continue

            for mat_slot in obj.material_slots:
                material = mat_slot.material
                if material and 'IsEmblemMaterial' in material and not material in touched_materials:
                    touched_materials.append(material)

                    material['EmblemBackground'] = scene.guild_emblem_background_color
                    material['EmblemBorder'] = scene.guild_emblem_border
                    material['EmblemBorderColor'] = scene.guild_emblem_border_color
                    material['EmblemDesign'] = scene.guild_emblem_design
                    material['EmblemColor'] = scene.guild_emblem_color

                    node_tree = material.node_tree

                    prop_type = 'EmblemPropType' in material and material['EmblemPropType']
                    if prop_type == 'CAULDRON' or prop_type == 'MONUMENT' or prop_type == 'CHEST' or prop_type == 'BANNER' or prop_type == 'CLOAK':
                        type_tag = prop_type.title()
                        emblem = find_node_by_name_or_label(node_tree, 'Guild' + type_tag + 'Emblem')
                        emblem_rgb = find_node_by_name_or_label(node_tree, 'Guild' + type_tag + 'EmblemRGB')
                        background_rgb = find_node_by_name_or_label(node_tree, 'Guild' + type_tag + 'BackgroundRGB')
                        border_rgb = find_node_by_name_or_label(node_tree, 'Guild' + type_tag + 'BorderRGB')

                        emblem_id = str(scene.guild_emblem_design - 1).rjust(2, '0')
                        emblem and self.update_node(emblem, 'emblem_' + emblem_id + '.png')
                        emblem_rgb and self.set_rbg(emblem_rgb, DB_EMBLEM_RBG[scene.guild_emblem_color - 1])
                        background_rgb and self.set_rbg(background_rgb, DB_BACKGROUND_RBG[scene.guild_emblem_background_color - 1])
                        border_rgb and self.set_rbg(border_rgb, DB_BORDER_RBG[scene.guild_emblem_border_color - 1])

                    if 'EmblemShirtQuality' in material:
                        shirt_quality = material['EmblemShirtQuality']
                        shirt_rank = material['EmblemShirtRank']

                        include_border = shirt_quality != 'COMMON'
                        rank_prefix = SHIRT_RANK_PREFIX[shirt_rank]

                        background_id = str(scene.guild_emblem_background_color - 1).rjust(2, '0')
                        background_prefix = rank_prefix + 'commonbg_' + background_id
                        background_upper = find_node_by_name_or_label(node_tree, 'GuildShirtBackgroundUpper')
                        background_lower = find_node_by_name_or_label(node_tree, 'GuildShirtBackgroundLower')

                        background_upper and self.update_node(background_upper, background_prefix + '_tu_u.png')
                        background_lower and self.update_node(background_lower, background_prefix + '_tl_u.png')

                        border_color_id = str(scene.guild_emblem_border_color - 1).rjust(2, '0')
                        border_prefix = rank_prefix + shirt_quality.lower() + 'border_' + border_color_id
                        if include_border:
                            border_upper = find_node_by_name_or_label(node_tree, 'GuildShirtBorderUpper')
                            border_lower = find_node_by_name_or_label(node_tree, 'GuildShirtBorderLower')

                            border_upper and self.update_node(border_upper, border_prefix + '_tu_u.png')
                            border_lower and self.update_node(border_lower, border_prefix + '_tl_u.png')

                        emblem_id = str(scene.guild_emblem_design - 1).rjust(2, '0')
                        emblem_color_id = str(scene.guild_emblem_color - 1).rjust(2, '0')
                        emblem_upper = find_node_by_name_or_label(node_tree, 'GuildShirtEmblemUpper')
                        emblem_lower = find_node_by_name_or_label(node_tree, 'GuildShirtEmblemLower')

                        emblem_upper and self.update_node(emblem_upper, 'emblem_' + emblem_id + '_' + emblem_color_id + '_tu_u.png')
                        emblem_lower and self.update_node(emblem_lower, 'emblem_' + emblem_id + '_' + emblem_color_id + '_tl_u.png')

                        if shirt_rank == 'MEMBER' or shirt_rank == 'OFFICER':
                            background_arm_upper = find_node_by_name_or_label(node_tree, 'GuildShirtBackgroundArmUpper')
                            border_arm_upper = find_node_by_name_or_label(node_tree, 'GuildShirtBorderArmUpper')

                            background_arm_upper and self.update_node(background_arm_upper, background_prefix + '_au_u.png')
                            border_arm_upper and self.update_node(border_arm_upper, border_prefix + '_au_u.png')

                        if shirt_rank == 'OFFICER':
                            background_arm_lower = find_node_by_name_or_label(node_tree, 'GuildShirtBackgroundArmLower')
                            border_arm_lower = find_node_by_name_or_label(node_tree, 'GuildShirtBorderArmLower')

                            background_arm_lower and self.update_node(background_arm_lower, background_prefix + '_al_u.png')
                            border_arm_lower and self.update_node(border_arm_lower, border_prefix + '_al_u.png')

                    if 'EmblemTabardType' in material:
                        tabard_type = material['EmblemTabardType']

                        prefix_background = TABARD_BG_PREFIX[tabard_type]
                        prefix_border = TABARD_BORDER_PREFIX[tabard_type]

                        background_id = str(scene.guild_emblem_background_color - 1).rjust(2, '0')
                        background_upper = find_node_by_name_or_label(node_tree, 'GuildTabardBackgroundUpper')
                        background_lower = find_node_by_name_or_label(node_tree, 'GuildTabardBackgroundLower')

                        background_upper and self.update_node(background_upper, prefix_background + background_id + '_tu_u.png')
                        background_lower and self.update_node(background_lower, prefix_background + background_id + '_tl_u.png')

                        border_color_id = str(scene.guild_emblem_border_color - 1).rjust(2, '0')
                        border_upper = find_node_by_name_or_label(node_tree, 'GuildTabardBorderUpper')
                        border_lower = find_node_by_name_or_label(node_tree, 'GuildTabardBorderLower')

                        border_id = ''
                        if tabard_type == 'COMMON':
                            border_id = str(scene.guild_emblem_border - 1).rjust(2, '0') + '_'

                        border_upper and self.update_node(border_upper, prefix_border + border_id + border_color_id + '_tu_u.png')
                        border_lower and self.update_node(border_lower, prefix_border + border_id + border_color_id + '_tl_u.png')

                        emblem_id = str(scene.guild_emblem_design - 1).rjust(2, '0')
                        emblem_color_id = str(scene.guild_emblem_color - 1).rjust(2, '0')
                        emblem_upper = find_node_by_name_or_label(node_tree, 'GuildTabardEmblemUpper')
                        emblem_lower = find_node_by_name_or_label(node_tree, 'GuildTabardEmblemLower')

                        emblem_upper and self.update_node(emblem_upper, 'emblem_' + emblem_id + '_' + emblem_color_id + '_tu_u.png')
                        emblem_lower and self.update_node(emblem_lower, 'emblem_' + emblem_id + '_' + emblem_color_id + '_tl_u.png')

        return {'FINISHED'}

class GuildEmblems_Apply(bpy.types.Operator):
    bl_label = 'Apply Guild Emblem'
    bl_idname = 'guildemblems.apply_emblem'
    bl_description = 'Apply guild emblem to the selected object'
    bl_options  = {'UNDO'}

    def create_mapping_node(self, node_tree, uv, location, scale, name = None, label = None):
        mapping = node_tree.nodes.new('ShaderNodeMapping')
        if name:
            mapping.label = label or name
            mapping.name = name

        mapping.vector_type = 'TEXTURE'
        mapping.inputs['Location'].default_value = location
        mapping.inputs['Scale'].default_value = scale
        mapping.hide = True

        node_tree.links.new(uv.outputs['UV'], mapping.inputs['Vector'])

        return mapping

    def create_image_node(self, node_tree, name, mapping, node_y):
        image = node_tree.nodes.new('ShaderNodeTexImage')
        image.extension = 'CLIP'
        image.name = image.label = name
        image.hide = True
        image.location = (-674, node_y)

        node_tree.links.new(mapping.outputs['Vector'], image.inputs['Vector'])

        return image

    def create_rgb_image_node(self, node_tree, name, mapping, node_y, alpha_target, image_src, enable_alpha):
        rgb = node_tree.nodes.new('ShaderNodeRGB')
        rgb.name = name + 'RGB'
        rgb.label = rgb.name
        rgb.location = (-931, node_y)

        image = node_tree.nodes.new('ShaderNodeTexImage')
        image.extension = 'CLIP'
        image.name = name
        image.label = name
        image.location = (-777, node_y)

        if image_src:
            if not image_src in bpy.data.images:
                bpy.data.images.load(os.path.join(TEXTURE_DIR, image_src))

            image.image = bpy.data.images[image_src]
            if enable_alpha:
                image.image.alpha_mode = 'STRAIGHT'

        mix = node_tree.nodes.new('ShaderNodeMixRGB')
        mix.blend_type = 'COLOR'
        mix.location = (-523, node_y)
        mix.inputs['Fac'].default_value = 1.0

        node_tree.links.new(rgb.outputs['Color'], mix.inputs['Color2'])
        node_tree.links.new(image.outputs['Color'], mix.inputs['Color1'])

        if alpha_target:
            node_tree.links.new(image.outputs['Alpha'], alpha_target)

        if mapping:
            socket_name = mapping.type == 'MAPPING' and 'Vector' or 'UV'
            node_tree.links.new(mapping.outputs[socket_name], image.inputs['Vector'])

        return mix, image

    def create_mix(self, node_tree, node_a, node_b, mix_target, math_a, math_b = None):
        nodes = node_tree.nodes
        mix = nodes.new('ShaderNodeMixRGB')
        mix.blend_type = 'MIX'
        mix.location = node_a.location + Vector((296, -28))

        math = nodes.new('ShaderNodeMath')
        math.operation = 'ADD'
        math.hide = True
        math.location = node_a.location + Vector((296, 5))

        node_tree.links.new(node_a.outputs['Alpha'], math.inputs[0])
        node_tree.links.new(node_b.outputs['Alpha'], math.inputs[1])

        node_tree.links.new(node_a.outputs['Color'], mix.inputs['Color1'])
        node_tree.links.new(node_b.outputs['Color'], mix.inputs['Color2'])
        node_tree.links.new(node_b.outputs['Alpha'], mix.inputs['Fac'])

        if mix_target:
            node_tree.links.new(mix.outputs['Color'], mix_target)

        if math_a:
            node_tree.links.new(math.outputs['Value'], math_a)

        if math_b:
            node_tree.links.new(math.outputs['Value'], math_b)

    def find_node_by_type(self, node_tree, node_type):
        for node in node_tree.nodes:
            if node.type == node_type:
                return node

        return None

    def create_mix_math_pair(self, node_tree, mix_loc):
        nodes = node_tree.nodes
        mix = nodes.new('ShaderNodeMixRGB')
        mix.location = mix_loc

        math = nodes.new('ShaderNodeMath')
        math.hide = True
        math.operation = 'ADD'

        math.location = (mix.location.x, mix.location.y + 38)
        return mix, math

    def create_rgb(self, node_tree, name, location):
        rgb = node_tree.nodes.new('ShaderNodeRGB')
        rgb.name = name
        rgb.location = location

        return rgb

    def create_rgb_mix(self, node_tree, rgb, image, location):
        mix = node_tree.nodes.new('ShaderNodeMixRGB')
        mix.blend_type = 'COLOR'
        mix.inputs['Fac'].default_value = 1.0
        mix.location = location

        node_tree.links.new(image.outputs['Color'], mix.inputs['Color1'])
        node_tree.links.new(rgb.outputs['Color'], mix.inputs['Color2'])
        return mix

    def apply_material(self, material, is_edit = True):
        scene = bpy.context.scene
        emblem_type = scene.guild_emblem_type

        node_tree = material.node_tree
        nodes = node_tree.nodes

        if is_edit:
            for node in nodes:
                node.select = False
        else:
            for node in nodes:
                nodes.remove(node)

        if emblem_type == 'CLOAK':
            material['EmblemPropType'] = emblem_type

            cloak_faction = scene.guild_emblem_faction_type
            material['EmblemFactionType'] = cloak_faction

            cloak_rank = scene.guild_emblem_cloak
            material['EmblemCloakRank'] = cloak_rank

            uv_map = self.find_node_by_type(node_tree, 'UVMAP')
            if not uv_map:
                uv_map = nodes.new('ShaderNodeUVMap')
                uv_map.location = (-981, 485)

            mapping = self.create_mapping_node(node_tree, uv_map, (0.98, 0.6, 0), (-0.6, 0.350, 1.00))
            mapping.hide = False
            mapping.location = (-799, 548)

            rgb_emblem = self.create_rgb(node_tree, 'GuildCloakEmblemRGB', (-625, 547))
            rgb_background = self.create_rgb(node_tree, 'GuildCloakBackgroundRGB', (-625, 325))
            rgb_border = self.create_rgb(node_tree, 'GuildCloakBorderRGB', (-625, 96))

            image_emblem = nodes.new('ShaderNodeTexImage')
            image_emblem.extension = 'CLIP'
            image_emblem.name = 'GuildCloakEmblem'
            image_emblem.location = (-421, 535)

            image_cloak = nodes.new('ShaderNodeTexImage')
            image_cloak.name = 'GuildCloakBase'
            image_cloak.location = (-417, 118)

            cloak_id = CLOAK_BG_ID[cloak_rank]
            background_src = 'cape_guild_' + cloak_faction.lower() + '_' + cloak_id + '_01.png'

            if not background_src in bpy.data.images:
                bpy.data.images.load(os.path.join(TEXTURE_DIR, background_src))

            image_cloak.image = bpy.data.images[background_src]

            node_tree.links.new(mapping.outputs['Vector'], image_emblem.inputs['Vector'])

            mix_a = self.create_rgb_mix(node_tree, rgb_emblem, image_emblem, (-96, 578))
            mix_b = self.create_rgb_mix(node_tree, rgb_background, image_cloak, (-96, 307))
            mix_c = self.create_rgb_mix(node_tree, rgb_border, image_cloak, (-96, 42))

            mix_d = nodes.new('ShaderNodeMixRGB')
            mix_d.location = (173, 265)

            mix_e = nodes.new('ShaderNodeMixRGB')
            mix_e.location = (368, 265)

            node_tree.links.new(mix_a.outputs['Color'], mix_e.inputs['Color2'])
            node_tree.links.new(mix_b.outputs['Color'], mix_d.inputs['Color1'])
            node_tree.links.new(mix_c.outputs['Color'], mix_d.inputs['Color2'])

            node_tree.links.new(image_cloak.outputs['Alpha'], mix_d.inputs['Fac'])
            node_tree.links.new(image_emblem.outputs['Alpha'], mix_e.inputs['Fac'])
            node_tree.links.new(mix_d.outputs['Color'], mix_e.inputs['Color1'])

            if not is_edit:
                diffuse = nodes.new('ShaderNodeBsdfDiffuse')
                output = nodes.new('ShaderNodeOutputMaterial')

                diffuse.location = (584, 276)
                output.location = (790, 276)

                node_tree.links.new(diffuse.outputs['BSDF'], output.inputs['Surface'])
                node_tree.links.new(mix_e.outputs['Color'], diffuse.inputs['Color'])

        if emblem_type == 'CAULDRON' or emblem_type == 'MONUMENT' or emblem_type == 'CHEST' or emblem_type == 'BANNER':
            material['EmblemPropType'] = emblem_type

            uv_map = None
            for node in nodes:
                if node.type == 'UVMAP' and node.uv_map == 'UV2Map':
                    uv_map = node
                    break

                if not uv_map:
                    uv_map = nodes.new('ShaderNodeUVMap')
                    uv_map.location = (-1094, 665)
                    uv_map.uv_map = 'UV2Map'

            mix_a = nodes.new('ShaderNodeMixRGB')
            mix_b = nodes.new('ShaderNodeMixRGB')

            mix_a.location = (-305, 278)
            mix_b.location = (-123, 278)

            background_src, border_src = None, None
            if emblem_type == 'CAULDRON' or emblem_type == 'CHEST' or emblem_type == 'BANNER':
                faction_type = scene.guild_emblem_faction_type
                material['EmblemFactionType'] = faction_type

                if emblem_type == 'BANNER':
                    prefix = 'battlestandard_guild_cloth' + faction_type.lower()
                    background_src = prefix + '_a_01.png'
                    border_src = prefix + 'border_a_01.png'
                else:
                    prefix = 'guild' + emblem_type.lower() + 'cloth_' + faction_type.lower()
                    background_src = prefix + '_01.png'
                    border_src = prefix + 'border_01.png'
            elif emblem_type == 'MONUMENT':
                background_src = 'flagpart_monument_raid.png'
                border_src = 'flagpart_monument_raid_border.png'

            type_tag = emblem_type.title()
            emblem, emblem_image = self.create_rgb_image_node(node_tree, 'Guild' + type_tag + 'Emblem', uv_map, 665, mix_a.inputs['Fac'], None, False)
            background, background_image = self.create_rgb_image_node(node_tree, 'Guild' + type_tag + 'Background', None, 278, None, background_src, True)
            border, border_image = self.create_rgb_image_node(node_tree, 'Guild' + type_tag + 'Border', None, -115, mix_b.inputs['Fac'], border_src, True)

            node_tree.links.new(emblem.outputs['Color'], mix_a.inputs['Color2'])
            node_tree.links.new(background.outputs['Color'], mix_a.inputs['Color1'])
            node_tree.links.new(mix_a.outputs['Color'], mix_b.inputs['Color1'])
            node_tree.links.new(border.outputs['Color'], mix_b.inputs['Color2'])

            if not is_edit:
                diffuse = nodes.new('ShaderNodeBsdfDiffuse')
                output = nodes.new('ShaderNodeOutputMaterial')

                diffuse.location = (66, 278)

                if emblem_type == 'BANNER' and faction_type == 'HORDE':
                    trans = nodes.new('ShaderNodeBsdfTransparent')
                    mix = nodes.new('ShaderNodeMixShader')

                    trans.location = (71, 370)
                    mix.location = (296, 273)
                    output.location = (516, 278)

                    node_tree.links.new(mix.outputs['Shader'], output.inputs['Surface'])
                    node_tree.links.new(diffuse.outputs['BSDF'], mix.inputs[2])
                    node_tree.links.new(trans.outputs['BSDF'], mix.inputs[1])
                    node_tree.links.new(background_image.outputs['Alpha'], mix.inputs['Fac'])

                    material.blend_method = 'CLIP'
                else:
                    output.location = (diffuse.location.x + 180, diffuse.location.y)
                    node_tree.links.new(diffuse.outputs['BSDF'], output.inputs['Surface'])

                node_tree.links.new(mix_b.outputs['Color'], diffuse.inputs['Color'])

        elif emblem_type == 'SHIRT':
            shirt_quality = scene.guild_emblem_shirt_quality
            shirt_rank = scene.guild_emblem_shirt_rank

            material['EmblemShirtQuality'] = shirt_quality
            material['EmblemShirtRank'] = shirt_rank

            uv_map = self.find_node_by_type(node_tree, 'UVMAP')
            if not uv_map:
                uv_map = nodes.new('ShaderNodeUVMap')
                uv_map.location = (-1112, 280)

            include_border = shirt_quality != 'COMMON'
            include_arm_upper = shirt_rank == 'MEMBER' or shirt_rank == 'OFFICER'
            include_arm_lower = shirt_rank == 'OFFICER'
            include_full_arms = include_arm_upper and include_arm_lower

            mapping_upper = is_edit and find_node_by_name_or_label(node_tree, 'MapUpper', '3 (TorsoUpper)')
            mapping_lower = is_edit and find_node_by_name_or_label(node_tree, 'MapTorsoLower', '4 (TorsoLower)')
            mapping_arm_upper = include_arm_upper and is_edit and find_node_by_name_or_label(node_tree, 'MapArmUpper', '0 (ArmUpper)')
            mapping_arm_lower = include_arm_lower and is_edit and find_node_by_name_or_label(node_tree, 'MapArmLower', '1 (ArmLower)')

            if not mapping_upper:
                mapping_upper = self.create_mapping_node(node_tree, uv_map, (0.25, 0.75, 0.0), (0.25, 0.25, 1.0), 'MapUpper', '3 (TorsoUpper)')
                mapping_upper.location = (-900, 314)

            if not mapping_lower:
                mapping_lower = self.create_mapping_node(node_tree, uv_map, (0.25, 0.625, 0.0), (0.25, 0.125, 1.0), 'MapTorsoLower', '4 (TorsoLower)')
                mapping_lower.location = (-900, 270)

            if not mapping_arm_upper and include_arm_upper:
                mapping_arm_upper = self.create_mapping_node(node_tree, uv_map, (0.0, 0.75, 0.0), (0.25, 0.25, 1.0), 'MapArmUpper', '0 (ArmUpper)')
                mapping_arm_upper.location = (-900, 225)

            if not mapping_arm_lower and include_arm_lower:
                mapping_arm_lower = self.create_mapping_node(node_tree, uv_map, (0, 0.5, 0), (0.25, 0.25, 1.0), 'MapArmLower', '1 (ArmLower)')
                mapping_arm_lower.location = (-900, 178)

            emblem_upper = self.create_image_node(node_tree, 'GuildShirtEmblemUpper', mapping_upper, 924)
            emblem_lower = self.create_image_node(node_tree, 'GuildShirtEmblemLower', mapping_lower, 763)

            background_upper = self.create_image_node(node_tree, 'GuildShirtBackgroundUpper', mapping_upper, 596)
            background_lower = self.create_image_node(node_tree, 'GuildShirtBackgroundLower', mapping_lower, 441)

            background_arm_upper = include_arm_upper and self.create_image_node(node_tree, 'GuildShirtBackgroundArmUpper', mapping_arm_upper, 284)
            background_arm_lower = include_arm_lower and self.create_image_node(node_tree, 'GuildShirtBackgroundArmLower', mapping_arm_lower, 129)

            border_upper = include_border and self.create_image_node(node_tree, 'GuildShirtBorderUpper', mapping_upper, -48)
            border_lower = include_border and self.create_image_node(node_tree, 'GuildShirtBorderLower', mapping_lower, -203)

            border_arm_upper = include_border and include_arm_upper and self.create_image_node(node_tree, 'GuildShirtBorderArmUpper', mapping_arm_upper, -360)
            border_arm_lower = include_border and include_arm_lower and self.create_image_node(node_tree, 'GuildShirtBorderArmLower', mapping_arm_lower, -515)

            mix_a, math_a = self.create_mix_math_pair(node_tree, (98, 261))
            mix_b, math_b = include_border and self.create_mix_math_pair(node_tree, (314, 261)) or (None, None)

            mix_c, math_c = include_arm_upper and self.create_mix_math_pair(node_tree, (-162, 446)) or (None, None)
            mix_d, math_d = include_border and include_arm_upper and self.create_mix_math_pair(node_tree, (-162, -222)) or (None, None)

            if include_border:
                node_tree.links.new(math_a.outputs['Value'], math_b.inputs[0])
                node_tree.links.new(mix_a.outputs['Color'], mix_b.inputs['Color1'])

            self.create_mix(node_tree, emblem_upper, emblem_lower, mix_a.inputs['Color2'], math_a.inputs[1], mix_a.inputs['Fac'])

            if include_arm_upper:
                self.create_mix(node_tree, background_upper, background_lower, mix_c.inputs['Color1'], math_c.inputs[0])

                if include_border:
                    self.create_mix(node_tree, border_upper, border_lower, mix_d.inputs['Color1'], math_d.inputs[0])

            if include_full_arms:
                self.create_mix(node_tree, background_arm_upper, background_arm_lower, mix_c.inputs['Color2'], math_c.inputs[1], mix_c.inputs['Fac'])

                if include_border:
                    self.create_mix(node_tree, border_arm_upper, border_arm_lower, mix_d.inputs['Color2'], math_d.inputs[1], mix_d.inputs['Fac'])
            elif include_arm_upper:
                node_tree.links.new(background_arm_upper.outputs['Color'], mix_c.inputs['Color2'])
                node_tree.links.new(background_arm_upper.outputs['Alpha'], mix_c.inputs['Fac'])

                if include_border:
                    node_tree.links.new(border_arm_upper.outputs['Color'], mix_d.inputs['Color2'])
                    node_tree.links.new(border_arm_upper.outputs['Alpha'], mix_d.inputs['Fac'])
                    node_tree.links.new(border_arm_upper.outputs['Alpha'], math_d.inputs[1])

                node_tree.links.new(background_arm_upper.outputs['Alpha'], math_c.inputs[1])
            else:
                self.create_mix(node_tree, background_upper, background_lower, mix_a.inputs['Color1'], math_a.inputs[0])

                if include_border:
                    self.create_mix(node_tree, border_upper, border_lower, mix_b.inputs['Color2'], math_b.inputs[1], mix_b.inputs['Fac'])

            if include_arm_upper or include_arm_lower:
                node_tree.links.new(math_c.outputs['Value'], math_a.inputs[0])
                node_tree.links.new(mix_c.outputs['Color'], mix_a.inputs['Color1'])

                if include_border:
                    node_tree.links.new(math_d.outputs['Value'], math_b.inputs[1])
                    node_tree.links.new(math_d.outputs['Value'], mix_b.inputs['Fac'])
                    node_tree.links.new(mix_d.outputs['Color'], mix_b.inputs['Color2'])

            if is_edit:
                for node in nodes:
                    if node.type == 'BSDF_DIFFUSE':
                        for link in node.inputs['Color'].links:
                            if link.from_node and link.from_node.type == 'MIX_RGB':
                                target_mix = mix_b or mix_a
                                target_math = math_b or math_a

                                layer = nodes.new('ShaderNodeMixRGB')
                                layer.location = (link.from_node.location.x + 100, link.from_node.location.y + 200)

                                node_tree.links.new(link.from_node.outputs['Color'], layer.inputs['Color1'])
                                node_tree.links.new(layer.outputs['Color'], node.inputs['Color'])
                                node_tree.links.new(target_mix.outputs['Color'], layer.inputs['Color2'])
                                node_tree.links.new(target_math.outputs['Value'], layer.inputs['Fac'])
                                break
            else:
                diffuse = nodes.new('ShaderNodeBsdfDiffuse')
                output = nodes.new('ShaderNodeOutputMaterial')

                diffuse.location = (519, 266)
                output.location = (diffuse.location.x + 180, diffuse.location.y)

                node_tree.links.new(mix_b and mix_b.outputs['Color'] or mix_a.outputs['Color'], diffuse.inputs['Color'])
                node_tree.links.new(diffuse.outputs['BSDF'], output.inputs['Surface'])

        elif emblem_type == 'TABARD':
            tabard_type = scene.guild_emblem_tabard_type
            material['EmblemTabardType'] = tabard_type

            uv_map = self.find_node_by_type(node_tree, 'UVMAP')
            if not uv_map:
                uv_map = nodes.new('ShaderNodeUVMap')
                uv_map.location = (-1112, 280)

            mapping_upper = is_edit and find_node_by_name_or_label(node_tree, 'MapUpper', '3 (TorsoUpper)')
            mapping_lower = is_edit and find_node_by_name_or_label(node_tree, 'MapTorsoLower', '4 (TorsoLower)')

            if not mapping_upper:
                mapping_upper = self.create_mapping_node(node_tree, uv_map, (0.25, 0.75, 0.0), (0.25, 0.25, 1.0), 'MapUpper', '3 (TorsoUpper)')
                mapping_upper.location = (-900, 268)

            if not mapping_lower:
                mapping_lower = self.create_mapping_node(node_tree, uv_map, (0.25, 0.625, 0.0), (0.25, 0.125, 1.0), 'MapTorsoLower', '4 (TorsoLower)')
                mapping_lower.location = (-900, 218)

            background_upper = self.create_image_node(node_tree, 'GuildTabardBackgroundUpper', mapping_upper, 298)
            background_lower = self.create_image_node(node_tree, 'GuildTabardBackgroundLower', mapping_lower, 142)

            border_upper = self.create_image_node(node_tree, 'GuildTabardBorderUpper', mapping_upper, -9)
            border_lower = self.create_image_node(node_tree, 'GuildTabardBorderLower', mapping_lower, -163)

            emblem_upper = self.create_image_node(node_tree, 'GuildTabardEmblemUpper', mapping_upper, 609)
            emblem_lower = self.create_image_node(node_tree, 'GuildTabardEmblemLower', mapping_lower, 452)

            mix_a, math_a = self.create_mix_math_pair(node_tree, (-145, 280))
            mix_b, math_b = self.create_mix_math_pair(node_tree, (59, 280))

            node_tree.links.new(mix_a.outputs['Color'], mix_b.inputs['Color1'])
            node_tree.links.new(math_a.outputs['Value'], math_b.inputs[1])

            self.create_mix(node_tree, background_upper, background_lower, mix_a.inputs['Color1'], None, math_a.inputs[0])
            self.create_mix(node_tree, border_upper, border_lower, mix_b.inputs['Color2'], mix_b.inputs['Fac'], math_a.inputs[1])
            self.create_mix(node_tree, emblem_upper, emblem_lower, mix_a.inputs['Color2'], mix_a.inputs['Fac'], math_b.inputs[0])

            if is_edit:
                for node in nodes:
                    if node.type == 'BSDF_DIFFUSE':
                        for link in node.inputs['Color'].links:
                            if link.from_node and link.from_node.type == 'MIX_RGB':
                                layer = nodes.new('ShaderNodeMixRGB')
                                layer.location = (link.from_node.location.x + 100, link.from_node.location.y + 200)

                                node_tree.links.new(link.from_node.outputs['Color'], layer.inputs['Color1'])
                                node_tree.links.new(layer.outputs['Color'], node.inputs['Color'])
                                node_tree.links.new(mix_b.outputs['Color'], layer.inputs['Color2'])
                                node_tree.links.new(math_b.outputs['Value'], layer.inputs['Fac'])
                                break
            else:
                diffuse = nodes.new('ShaderNodeBsdfDiffuse')
                output = nodes.new('ShaderNodeOutputMaterial')

                diffuse.location = (253, 280)
                output.location = (diffuse.location.x + 180, diffuse.location.y)

                node_tree.links.new(mix_b.outputs['Color'], diffuse.inputs['Color'])
                node_tree.links.new(diffuse.outputs['BSDF'], output.inputs['Surface'])

        material['IsEmblemMaterial'] = 1

    def invoke(self, context, event):
        apply_mode = context.scene.guild_emblem_mode

        add_material = None
        success_count = 0

        for obj in context.selected_objects:
            if apply_mode == 'EDIT':
                slot = obj.material_slots[obj.active_material_index]
                if not slot or not slot.material:
                    continue

                self.apply_material(slot.material, True)
                success_count += 1
            elif apply_mode == 'ADD':
                if not add_material:
                    add_material = bpy.data.materials.new('mat_guild_' + context.scene.guild_emblem_type.lower())
                    add_material.use_nodes = True

                    self.apply_material(add_material, False)

                obj.data.materials.append(add_material)

                for i, material in enumerate(obj.data.materials):
                    if material == add_material:
                        obj.active_material_index = i
                        break

                success_count += 1

        bpy.ops.guildemblems.apply_design()

        self.report({'INFO'}, 'Applied emblem to ' + str(success_count) + ' objects')
        return {'FINISHED'}

class GuildEmblems_PT_Panel(bpy.types.Panel):
    bl_label = 'Guild Emblems'
    bl_idname = 'OBJECT_PT_GUILDEMBLEMS'
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = 'Guild'

    def draw(self, context):
        scene = context.scene
        layout = self.layout

        box = layout.box()

        box.row().prop(scene, 'guild_emblem_type', text='Type')

        emblem_type = scene.guild_emblem_type
        if emblem_type == 'TABARD':
            box.row().prop(scene, 'guild_emblem_tabard_type', text = 'Quality')
        elif emblem_type == 'SHIRT':
            box.row().prop(scene, 'guild_emblem_shirt_quality', text = 'Quality')
            box.row().prop(scene, 'guild_emblem_shirt_rank', text = 'Rank')
        elif emblem_type == 'CAULDRON' or emblem_type == 'CHEST' or emblem_type == 'BANNER':
            box.row().prop(scene, 'guild_emblem_faction_type', text = 'Faction')
        elif emblem_type == 'CLOAK':
            box.row().prop(scene, 'guild_emblem_faction_type', text = 'Faction')
            box.row().prop(scene, 'guild_emblem_cloak', text = 'Rank')

        box.row().prop(scene, 'guild_emblem_mode', text='Mode')
        box.row().operator('guildemblems.apply_emblem', text='Apply to Selected')

        has_valid_target = False
        for obj in context.selected_objects:
            for mat_slot in obj.material_slots:
                material = mat_slot.material
                if material and 'IsEmblemMaterial' in material:
                    has_valid_target = True
                    break

            if has_valid_target:
                break

        if has_valid_target:
            layout.label(text='Configure Design')
            box = layout.box()
            box.row().prop(scene, 'guild_emblem_background_color', text = 'Background Color')
            box.row().prop(scene, 'guild_emblem_border', text = 'Border Style')
            box.row().prop(scene, 'guild_emblem_border_color', text = 'Border Color')
            box.row().prop(scene, 'guild_emblem_design', text = 'Emblem Design')
            box.row().prop(scene, 'guild_emblem_color', text = 'Emblem Color')
            box.row().operator('guildemblems.randomize', text='Randomize')
            box.row().operator('guildemblems.copy', text='Copy Active')
            box.row().operator('guildemblems.apply_design', text='Apply to Selected')

classes = (
    GuildEmblems_PT_Panel,
    GuildEmblems_Apply,
    GuildEmblems_Randomize,
    GuildEmblems_ApplyDesign,
    GuildEmblems_CopyFromActive,
)

def register():
    scene = bpy.types.Scene
    scene.guild_emblem_type = bpy.props.EnumProperty(name='Emblem Type',items=ITEMS_EMBLEM_TYPES, default='TABARD')
    scene.guild_emblem_tabard_type = bpy.props.EnumProperty(name='Quality', items=ITEMS_TABARD_TYPES, default='COMMON')
    scene.guild_emblem_shirt_quality = bpy.props.EnumProperty(name='Quality', items=ITEMS_SHIRT_QUALITY, default='COMMON')
    scene.guild_emblem_shirt_rank = bpy.props.EnumProperty(name='Rank', items=ITEMS_SHIRT_RANK, default='INITIATE')
    scene.guild_emblem_faction_type = bpy.props.EnumProperty(name='Faction', items=ITEMS_FACTION_TYPES, default='ALLIANCE')
    scene.guild_emblem_mode = bpy.props.EnumProperty(name='Apply Mode',items=ITEMS_EMBLEM_MODES, default='ADD')
    scene.guild_emblem_cloak = bpy.props.EnumProperty(name='Rank', items=ITEMS_CLOAK_RANK, default='UNCOMMON')
    scene.guild_emblem_background_color = bpy.props.IntProperty(name='Background Color', default=1, min=1, max=MAX_TABARD_BACKGROUNDS, update=update_callback)
    scene.guild_emblem_border = bpy.props.IntProperty(name='Border Style', default=1, min=1, max=MAX_TABARD_BORDERS, update=update_callback)
    scene.guild_emblem_border_color = bpy.props.IntProperty(name='Border Color', default=1, min=1, max=MAX_TABARD_BORDER_COLORS, update=update_callback)
    scene.guild_emblem_design = bpy.props.IntProperty(name='Emblem Design', default=1, min=1, max=MAX_TABARD_EMBLEMS, update=update_callback)
    scene.guild_emblem_color = bpy.props.IntProperty(name='Emblem Color', default=1, min=1, max=MAX_TABARD_EMBLEM_COLORS, update=update_callback)

    for cls in classes:
        bpy.utils.register_class(cls)

def unregister():
    scene = bpy.types.Scene
    del scene.guild_emblem_type
    del scene.guild_emblem_tabard_type
    del scene.guild_emblem_shirt_quality
    del scene.guild_emblem_shirt_rank
    del scene.guild_emblem_faction_type
    del scene.guild_emblem_mode
    del scene.guild_emblem_cloak
    del scene.guild_emblem_background_color
    del scene.guild_emblem_border
    del scene.guild_emblem_border_color
    del scene.guild_emblem_design
    del scene.guild_emblem_color

    for cls in classes:
        bpy.utils.unregister_class(cls)

if __name__ == '__main__':
    register()