import os
import vs
import random
from PySide import QtCore
from PySide import QtGui

# To import the shared functions, we need to update sys.path. This is fucking ugly. Python, fix your fucking import system.
# UPDATE JUNE 3: Making this import mod-agnostic and case-insensitive just made it worse.
# Holy fuck Python. FIX YOUR FUCKING IMPORT SYSTEM.

SysPath_StaticCut = "sdktools\\python\\2.7\\win32\\lib\\site-packages\\distribute-0.6.45-py2.7.egg"
newPath = sys.path[0].replace(SysPath_StaticCut,"")

latestTime = 0
latestFilepath = ""

for f in os.listdir(newPath):
	pypath = os.path.join(newPath, f, "scripts\\sfm")
	pyfile = os.path.join(pypath, "lordaardvarkshared.py")

	if os.path.isfile(pyfile):
		modTime = os.path.getmtime(pyfile)

		if modTime > latestTime:
			latestTime = modTime
			latestFilepath = pypath

sys.path.append(latestFilepath)

for f in os.listdir(latestFilepath):
	if f == "lordaardvarkshared.py":
		import lordaardvarkshared as LordAardvarkShared

	if f == "LordAardvarkShared.py":
		import LordAardvarkShared

# Actual code.
# Because holy shit that import code got ugly.
# Jesus Fucking Christ.

## Conversions
def QColorToVec3(inClr):
    clrRgb = inClr.getRgb()
    
    clrVec = vs.Vector(clrRgb[0] / 255.0, clrRgb[1] / 255.0, clrRgb[2] / 255.0)
    
    return clrVec
    
    
# Classes
class HairStyle:
    def __init__(self, displayName, hairModel, hairBodygroupSet, hairBodygroupElement):
        global HMF_HAIR_STYLES
        
        self.displayName = displayName
        self.hairModel = hairModel
        self.bodygroupSet = hairBodygroupSet
        self.bodygroupElement = hairBodygroupElement
        
        HMF_HAIR_STYLES[displayName] = self
        
class GenericCharacter:
    def __init__(self, headName, bodyTypeName, headNameInternal, bodyTypeInternal, mdlName):
        self.headName = headName
        self.headNameInternal = headNameInternal
        self.bodyTypeName = bodyTypeName
        self.bodyTypeInternal = bodyTypeInternal
        self.modelName = mdlName
        
        HMF_GENERIC_CHARACTERS[GenericCharacter.GetKey(headNameInternal, bodyTypeInternal)] = self
        
    @staticmethod
    def GetKey(headNameInternal, bodyTypeInternal):
        return headNameInternal + "_" + bodyTypeInternal
        
    def ContextualizeString(self, rawStr):
        outStr = rawStr
        
        for genCharKey in HMF_GENERIC_CHARACTERS:
            genChar = HMF_GENERIC_CHARACTERS[genCharKey]
            
            outStr = outStr.replace(genChar.headNameInternal, self.headNameInternal)
            outStr = outStr.replace(genChar.bodyTypeInternal, self.bodyTypeInternal)
            
        return outStr
        
class ParameterPair:
    def __init__(self, paramName, paramValue):
        self.parameterName = paramName
        self.parameterValue = paramValue
        
    def Apply(self, mat):
        paramValue = self.parameterValue
        
        if type(self.parameterValue) == str:
            mat.AddAttributeAsString(self.parameterName)
            
        if type(self.parameterValue) == int:
            mat.AddAttributeAsBool(self.parameterName)
            
        if type(self.parameterValue) == float:
            mat.AddAttributeAsFloat(self.parameterName)
            
        if type(self.parameterValue) == vs.Vector:
            mat.AddAttributeAsVector3(self.parameterName)
            
        mat.SetValue(self.parameterName, self.parameterValue)
            
        
class Tint:
    def __init__(self, displayName, colorHex):
        self.displayName = displayName
        self.colorHex = colorHex
        self.colorObj = QtGui.QColor("#" + colorHex)
        self.colorVector = QColorToVec3(self.colorObj)
        
class MaterialOverride:
    def __init__(self, displayName, materialName, lstParamPairs, lstTints):
        self.displayName = displayName
        self.materialName = materialName
        self.parameterPairs = {}
        self.tints = {}
        
        for tint in lstTints:
            self.tints[tint.displayName] = tint
            
        for paramPair in HMF_OVERRIDE_DEFAULTS:
            self.parameterPairs[paramPair.parameterName] = paramPair
                
        for paramPair in lstParamPairs:
            self.parameterPairs[paramPair.parameterName] = paramPair
        
    def Apply(self, animset, tintName):
        matMap = GetOrCreateMaterialOverrides(animset)
        
        mat = matMap[self.materialName]
        
        for paramPairName in self.parameterPairs:
            paramPair = self.parameterPairs[paramPairName]
            
            paramPair.Apply(mat)
            
        if tintName is not None:
            tint = self.tints[tintName]

            mat.AddAttributeAsVector3("$color2")
            mat.SetValue("$color2",tint.colorVector)
        
class BodygroupData:
    def __init__(self, bodygroupSetDisplay, bodygroupSetInternal, bodygroupElementDisplay, bodygroupElementInternal, lstMaterialOverrides):
        self.bodygroupSetDisplay = bodygroupSetDisplay
        self.bodygroupSetInternal = bodygroupSetInternal
        self.bodygroupElementDisplay = bodygroupElementDisplay
        self.bodygroupElementInternal = bodygroupElementInternal
        self.materialOverrides = {}
        
        for matOverride in lstMaterialOverrides:
            #if matOverride.displayName not in self.materialOverrides:
            #    self.materialOverrides[matOverride.displayName] = []
            #    
            #self.materialOverrides[matOverride.displayName].append(matOverride)
            self.materialOverrides[matOverride.displayName] = matOverride
        
    @staticmethod
    def GetKey(bodygroupSetDisplay, bodygroupElementDisplay):
        return bodygroupSetDisplay + "_" + bodygroupElementDisplay
        
    def Apply(self, animset, matOverrideName, tintName):
        parentAnimset = getattr(animset, "parentAnimset")
        genChar = GetGenericCharacterFromAnimset(parentAnimset)
        
        SetActiveBodygroup(animset, self.bodygroupSetInternal, genChar.ContextualizeString(self.bodygroupElementInternal))
        
        #matOverrides = self.materialOverrides[matOverrideName]
        #
        #for matOverride in matOverrides:
        #    matOverride.Apply(animset, tintName)
        
        matOverride = self.materialOverrides[matOverrideName]
        matOverride.Apply(animset, tintName)
        
class Outfit:
    global HMF_OUTFITS
    
    def __init__(self, displayName, mdlName, lstBodygroupDatas):
        self.displayName = displayName
        self.mdlName = mdlName
        self.bodygroupDatas = {}
        self.bodygroupSets = {}
        
        for bodygroupData in lstBodygroupDatas:
            bodygroupKey = BodygroupData.GetKey(bodygroupData.bodygroupSetDisplay, bodygroupData.bodygroupElementDisplay)
            
            self.bodygroupDatas[bodygroupKey] = bodygroupData
            
            if bodygroupData.bodygroupSetDisplay not in self.bodygroupSets:
                self.bodygroupSets[bodygroupData.bodygroupSetDisplay] = {}
                
            if bodygroupData.bodygroupElementDisplay not in self.bodygroupSets[bodygroupData.bodygroupSetDisplay]:
                self.bodygroupSets[bodygroupData.bodygroupSetDisplay][bodygroupData.bodygroupElementDisplay] = []
                
            self.bodygroupSets[bodygroupData.bodygroupSetDisplay][bodygroupData.bodygroupElementDisplay] = bodygroupData
        
        HMF_OUTFITS[displayName] = self
        
    def ApplyBodygroup(self, animset, bodygroupSetDisplay, bodygroupElementDisplay, matOverrideName, tintName):
        bodygroupData = self.bodygroupDatas[BodygroupData.GetKey(bodygroupSetDisplay, bodygroupElementDisplay)]
        
        bodygroupData.Apply(animset, matOverrideName, tintName)
        
# Globals
if "HMF_GameModFolderNames" not in globals():
    HMF_GameInfoPath = "usermod/gameinfo.txt"
    HMF_GameModFolderNames = []

HMF_CurCharAnimset = None

HMF_GENERIC_CHARACTERS = {}
HMF_HAIR_COLORS = {}
HMF_HAIR_STYLES = {}
HMF_OUTFITS = {}

HMF_OVERRIDE_DEFAULTS = [
    ParameterPair("$color2", vs.Vector(1,1,1)),
    ParameterPair("$blendtintbybasealpha", 1 )
]

# Hard-coded data for testing
HMF_TINT_MUTED = [
    Tint("White", "bbbbbb"),
    Tint("Pink", "c66c99"),
    Tint("Black", "212426"),
    Tint("Dark Blue", "38414c"),
    Tint("Light Blue", "7197bb"),
    Tint("Dark Red", "832534"),
    Tint("Gray", "5e5f64"),
    Tint("Chestnut", "7a5b55"),
    Tint("Forest Green", "24372f"),
    Tint("Navy Blue", "232d41"),
    Tint("Pistachio", "b0b589"),
    Tint("Tan", "a39160")
]

HMF_TINT_GAUDY = [
    Tint("Cherry Red", "da0c29"),
    Tint("Daisy Yellow", "f5f500"),
    Tint("Neon Blue", "0087c0"),
    Tint("Neon Green", "7cf500"),
    Tint("Safety Green", "d0f600"),
    Tint("Safety Orange", "ea6218"),
    Tint("Safety Pink", "e6487b")
]

HMF_TINT_NONE = []

HMF_HAIR_COLOR_DATA = [
    [ "Mousey", "99794f" ],
    [ "Brunette", "554737" ],
    [ "Auburn", "5c2b05" ],
    [ "Raven", "2d222c" ],
    [ "Ginger", "b5533b" ],
    [ "Platinum", "f3ebe0" ],
    [ "Gold", "e3d29e" ],
    [ "Pale", "f5f0d2" ]
]

HMF_HAIR_STYLE_DATA = [
    HairStyle("Crazy Morning",  "models/lordaardvark/sfm/dazgeneric/female_hitman_head08_hair.mdl", "Hair", "head08_hair_04"),
    HairStyle("Young At Heart", "models/lordaardvark/sfm/dazgeneric/female_hitman_head08_hair.mdl", "Hair", "head08_hair_05"),
    HairStyle("On The Go",      "models/lordaardvark/sfm/dazgeneric/female_hitman_head08_hair.mdl", "Hair", "head08_hair_06"),
    HairStyle("Miss Noir",      "models/lordaardvark/sfm/dazgeneric/female_hitman_head08_hair.mdl", "Hair", "head08_hair_07"),
    HairStyle("No Apologies",   "models/lordaardvark/sfm/dazgeneric/female_hitman_head08_hair.mdl", "Hair", "head08_hair_08"),
    HairStyle("So Sultry",      "models/lordaardvark/sfm/dazgeneric/female_hitman_head08_hair.mdl", "Hair", "head08_hair_09")
]

HMF_GENERIC_CHARACTER_DATA = [
    GenericCharacter("Head 08", "Average",    "head08", "average",    "models/lordaardvark/sfm/dazgeneric/female_hitman_head08-average.mdl"),
    GenericCharacter("Head 08", "Curvy",      "head08", "curvy",      "models/lordaardvark/sfm/dazgeneric/female_hitman_head08-curvy.mdl"),
    GenericCharacter("Head 08", "DOAFantasy", "head08", "doafantasy", "models/lordaardvark/sfm/dazgeneric/female_hitman_head08-doafantasy.mdl"),
    GenericCharacter("Head 08", "Petite",     "head08", "petite",     "models/lordaardvark/sfm/dazgeneric/female_hitman_head08-petite.mdl"),
    GenericCharacter("Head 08", "Strong",     "head08", "strong",     "models/lordaardvark/sfm/dazgeneric/female_hitman_head08-strong.mdl")
]

HMF_OUTFIT_DATA = [
    Outfit("Casual", "models/LordAardvark/SFM/DazGeneric/female_hitman/outfit_casual-average.mdl", [
        BodygroupData("Shirt", "shirt", "Sport Tanktop", "average_casual_shirt_sports_full", [
            MaterialOverride("Plain", "kok_shirt_d", [ ParameterPair("$basetexture", "models/LordAardvark/DazGeneric/FemaleHitman/Outfits/Casual/kok_shirt_plain") ], HMF_TINT_MUTED),
            MaterialOverride("White Stripes", "kok_shirt_d", [ ParameterPair("$basetexture", "models/LordAardvark/DazGeneric/FemaleHitman/Outfits/Casual/kok_shirt_stripes-white") ], HMF_TINT_MUTED),
            MaterialOverride("Gray Stripes Two-Tone", "kok_shirt_d", [ ParameterPair("$basetexture", "models/LordAardvark/DazGeneric/FemaleHitman/Outfits/Casual/kok_shirt_stripes-gray-bicolor") ], HMF_TINT_MUTED),
            MaterialOverride("Gray Stripes Three-Tone", "kok_shirt_d", [ ParameterPair("$basetexture", "models/LordAardvark/DazGeneric/FemaleHitman/Outfits/Casual/kok_shirt_stripes-gray-tricolor") ], HMF_TINT_MUTED),
            MaterialOverride("Black Stripes Two-Tone", "kok_shirt_d", [ ParameterPair("$basetexture", "models/LordAardvark/DazGeneric/FemaleHitman/Outfits/Casual/kok_shirt_stripes-black-bicolor") ], HMF_TINT_MUTED),
            MaterialOverride("Black Stripes Three-Tone", "kok_shirt_d", [ ParameterPair("$basetexture", "models/LordAardvark/DazGeneric/FemaleHitman/Outfits/Casual/kok_shirt_stripes-black-tricolor") ], HMF_TINT_MUTED),
            MaterialOverride("Dazzle Camo", "kok_shirt_d", [ ParameterPair("$basetexture", "models/LordAardvark/DazGeneric/FemaleHitman/Outfits/Casual/kok_shirt_dazzle") ], HMF_TINT_MUTED),
            MaterialOverride("Cowprint", "kok_shirt_d", [ ParameterPair("$basetexture", "models/LordAardvark/DazGeneric/FemaleHitman/Outfits/Casual/kok_shirt_cowprint") ], HMF_TINT_MUTED),
            MaterialOverride("Beads", "kok_shirt_d", [ ParameterPair("$basetexture", "models/LordAardvark/DazGeneric/FemaleHitman/Outfits/Casual/kok_shirt_beads") ], HMF_TINT_MUTED),
            MaterialOverride("Light Bubble-Blob", "kok_shirt_d", [ ParameterPair("$basetexture", "models/LordAardvark/DazGeneric/FemaleHitman/Outfits/Casual/kok_shirt_bubble-blob_light") ], HMF_TINT_MUTED),
            MaterialOverride("Dark Bubble-Blob", "kok_shirt_d", [ ParameterPair("$basetexture", "models/LordAardvark/DazGeneric/FemaleHitman/Outfits/Casual/kok_shirt_bubble-blob_dark") ], HMF_TINT_MUTED)
        ]),
        BodygroupData("Pants", "pants", "Sport Shorts",  "average_casual_pants_sports_shorts_full", [
            MaterialOverride("Plain", "kok_pant_d", [
                ParameterPair("$basetexture", "models/LordAardvark/DazGeneric/FemaleHitman/Outfits/Casual/kok_pant_plain"),
                ParameterPair("$blendtintbybasealpha", 0)
            ], HMF_TINT_MUTED),
            MaterialOverride("Racing Stripes", "kok_pant_d", [ ParameterPair("$basetexture", "models/LordAardvark/DazGeneric/FemaleHitman/Outfits/Casual/kok_pant_stripes-racing") ], HMF_TINT_MUTED),
            MaterialOverride("Navy Camo", "kok_pant_d", [ ParameterPair("$basetexture", "models/LordAardvark/DazGeneric/FemaleHitman/Outfits/Casual/kok_pant_camo-navy") ], HMF_TINT_NONE),
            MaterialOverride("Triangle Camo", "kok_pant_d", [ ParameterPair("$basetexture", "models/LordAardvark/DazGeneric/FemaleHitman/Outfits/Casual/kok_pant_camo-triangle") ], HMF_TINT_NONE),
            MaterialOverride("Dazzle Camo", "kok_pant_d", [ ParameterPair("$basetexture", "models/LordAardvark/DazGeneric/FemaleHitman/Outfits/Casual/kok_pant_camo-dazzle") ], HMF_TINT_MUTED),
            MaterialOverride("Fishscale", "kok_pant_d", [
                ParameterPair("$basetexture", "models/LordAardvark/DazGeneric/FemaleHitman/Outfits/Casual/kok_pant_fishscale-d"),
                ParameterPair("$bumpmap", "models/LordAardvark/DazGeneric/FemaleHitman/Outfits/Casual/kok_pant_fishscale-n")
            ], HMF_TINT_MUTED),
            MaterialOverride("Dark Gradient", "kok_pant_d", [ ParameterPair("$basetexture", "models/LordAardvark/DazGeneric/FemaleHitman/Outfits/Casual/kok_pant_gradient-dark") ], HMF_TINT_GAUDY),
            MaterialOverride("Light Gradient", "kok_pant_d", [ ParameterPair("$basetexture", "models/LordAardvark/DazGeneric/FemaleHitman/Outfits/Casual/kok_pant_gradient-light") ], HMF_TINT_GAUDY),
            MaterialOverride("Leopard Print Gradient", "kok_pant_d", [ ParameterPair("$basetexture", "models/LordAardvark/DazGeneric/FemaleHitman/Outfits/Casual/kok_pant_leopard-gradient") ], HMF_TINT_GAUDY),
            MaterialOverride("Leopard Print Solid", "kok_pant_d", [
                ParameterPair("$basetexture", "models/LordAardvark/DazGeneric/FemaleHitman/Outfits/Casual/kok_pant_leopard-gradient"),
                ParameterPair("$blendtintbybasealpha", 0)
            ], HMF_TINT_MUTED)
        ])
        #BodygroupData("Shoes", "shoes", "Sport Shoes",   "average_casual_shoes_sports")
    ])
]

# Utilities
def SetCurAnimset(animset=None):
    global HMF_CurCharAnimset
    
    if animset is None:
        HMF_CurCharAnimset = sfm.GetCurrentAnimationSet()
        
    HMF_CurCharAnimset = animset
    
def GetCurAnimset():
    global HMF_CurCharAnimset
    
    if HMF_CurCharAnimset is None:
        HMF_CurCharAnimset = sfm.GetCurrentAnimationSet()
        
    return HMF_CurCharAnimset
    
def GetGenericCharacterFromAnimset(animset):
    if not hasattr(animset, "gameModel"):
        return None
        
    mdlName = str(animset.gameModel.modelName)
    
   #if not hasattr(animset, "headInternal"):
   #    animset.AddAttributeAsString("headInternal")
   #    
   #    for genCharKey in HMF_GENERIC_CHARACTERS:
   #        genChar = HMF_GENERIC_CHARACTERS[genCharKey]
   #        
   #        if genChar.headNameInternal in mdlName:
   #            animset.SetValue("headInternal",genChar.headNameInternal)
   #            break
   #    
   #if not hasattr(animset, "bodytypeInternal"):
   #    animset.AddAttributeAsString("bodyTypeInternal")
   #    
   #    for genCharKey in HMF_GENERIC_CHARACTERS:
   #        genChar = HMF_GENERIC_CHARACTERS[genCharKey]
   #        
   #        if genChar.bodyTypeInternal in mdlName:
   #            animset.SetValue("bodyTypeInternal", genChar.bodyTypeInternal)
   
    headNameInternal = None
    bodyTypeInternal = None
    
    for genCharKey in HMF_GENERIC_CHARACTERS:
        genChar = HMF_GENERIC_CHARACTERS[genCharKey]

        if genChar.bodyTypeInternal in mdlName:
            bodyTypeInternal = genChar.bodyTypeInternal
            
        if genChar.headNameInternal in mdlName:
            headNameInternal = genChar.headNameInternal
                
    #headNameInternal = str(animset.GetAttribute("headInternal"))
    #bodyTypeInternal = str(animset.GetAttribute("bodyTypeInternal"))
    
    genCharKey = GenericCharacter.GetKey(headNameInternal, bodyTypeInternal)
    
    if genCharKey in HMF_GENERIC_CHARACTERS:
        genChar = HMF_GENERIC_CHARACTERS[genCharKey]
        
        return genChar
        
    return None
    
def GetGenericCharacterFromNamePair(headNameInternal, bodyTypeInternal):
    genCharKey = GenericCharacter.GetKey(headNameInternal, bodyTypeInternal)
    
    if genCharKey in HMF_GENERIC_CHARACTERS:
        return HMF_GENERIC_CHARACTERS[genCharKey]
        
    return None
    
## Material related stuff
def GetOrCreateMaterial(animset, materialName):
    materialName = str(materialName)
    
    if hasattr(animset.gameModel,"materials"):
        for mat in animset.gameModel.materials:
            if mat is None:
                continue
                
            if str(mat.name) == materialName:
                return mat
                
    newMat = vs.CreateElement("DmeMaterial", materialName, 0)
    
    if not hasattr(animset.gameModel,"materials"):
        animset.gameModel.AddAttribute('materials', vs.AT_ELEMENT_ARRAY)
        
    animset.gameModel.materials.AddToTail(newMat)
    
    return newMat
    
def GetMaterialNames(animset):
    names = []
    
    shdr = animset.gameModel.GetStudioHdr()
    for i in range(shdr.numtextures):
        matname = shdr.pTexture(i).pszName()
        
        names.append(matname)
        
    return names
    
def AnimsetHasMaterialOverrides(animset):
    return (hasattr(animset,"gameModel") and hasattr(animset.gameModel,"materials"))
    
## Bodygroup-related stuff
def IsInt(v):
    try:
        _ = int(v)
    except Exception:
        return False
        
    return True
    
def ComputeBodygroupValue(animset, lstValues):
    mdl = animset.gameModel
    numBodygroups = mdl.GetBodyPartCount()
    
    bgBase = 1
    bgValue = 0
    
    for i in range(numBodygroups):
        if i > 0:
            numParts = mdl.GetBodyPart(i-1).nummodels
            
            bgBase = bgBase * numParts
            
        bgValue = bgValue + (bgBase * lstValues[i])
        
    return bgValue
      
def GetBodygroupName(bodygroup):
    bgName = str(bodygroup.pszName())
    
    if len(bgName) < 1:
        return "<none>"
        
    return bgName
    
def GetBodygroupIndices(animset, grpNameOrIdx, elemNameOrIdx):
    mdl = animset.gameModel
    numBodygroups = mdl.GetBodyPartCount()
    
    idx = [grpNameOrIdx,elemNameOrIdx]
    
    # Simplest case: Already given indices. Just return as is.
    if IsInt(grpNameOrIdx) and IsInt(elemNameOrIdx):
        return idx
        
    # Next simplest case: the group is an index, but the element is not.
    if IsInt(grpNameOrIdx):
        bgObj = mdl.GetBodyPart(idx[0])
        
        numSubs = bgObj.nummodels
        
        for sub in range(numSubs):
            subObj = bgObj.pModel(sub)
            
            subName = GetBodygroupName(subObj)
            
            if subName == elemNameOrIdx:
                idx[1] = sub
                
                return idx
                
        return [None, None] # If we get this far, it means we failed to find the sub-element by name. So we return Nones to represent that we failed.
        
    # Most annoying case: the group is not an index. In this case, we don't care what the element is, because we're going to actually recurse to save a bit of code and condition checking.
    for bg in range(numBodygroups):
        bgObj = mdl.GetBodyPart(bg)
        bgName = GetBodygroupName(bgObj)
        
        if bgName == grpNameOrIdx:
            return GetBodygroupIndices(animset, bg, elemNameOrIdx) # Once we've found the index of the group, we can recurse to hit the previous two cases to handle whether the element is an index or a name.
            
    return [None, None] # If we get this far, then it means we failed to find the group by name. So we return Nones to represent that we failed.
    
def GetBodygroupNames(animset, grpIdx, elemIdx):
    mdl = animset.gameModel
    numBodygroups = mdl.GetBodyPartCount()
    
    bgObj = mdl.GetBodyPart(grpIdx)
    subObj = bgObj.pModel(elemIdx)
    
    return [GetBodygroupName(bgObj), GetBodygroupName(subObj)]
    
def GetBodygroupValues(animset):
    mdl = animset.gameModel
    
    numBodygroups = mdl.GetBodyPartCount()
    
    lstValues = [0] * numBodygroups
    
    bgBase = 1.0
    bgValue = float(str(mdl.body))
    
    for i in range(numBodygroups):
        if i > 0:
            numParts = mdl.GetBodyPart(i-1).nummodels
            
            bgBase = bgBase * numParts

        lstValues[i] = int((bgValue / bgBase) % mdl.GetBodyPart(i).nummodels) # Just truncating these feels hella dirty. But it seems to be correct...
        
    return lstValues
    
def GetActiveBodygroupNames(animset):
    mdl = animset.gameModel
    numBodygroups = mdl.GetBodyPartCount()

    names = []
    
    bgValues = GetBodygroupValues(animset)
    
    for bg in range(numBodygroups):
        names.append(GetBodygroupNames(animset, bg, bgValues[bg]))
        
    return names
    
def SetActiveBodygroups(animset, lstNamePairs):
    mdl = animset.gameModel
    numBodygroups = mdl.GetBodyPartCount()
    
    bodygroupValues = [0] * numBodygroups
    
    for namePair in lstNamePairs:
        grpName, elemName = namePair
        
        grpIdx, elemIdx = GetBodygroupIndices(animset, grpName, elemName)
        
        bodygroupValues[grpIdx] = elemIdx
        
    mdl.body = ComputeBodygroupValue(animset, bodygroupValues)
    
def SetActiveBodygroup(animset, grpNameOrIdx, elemNameOrIdx):
    mdl = animset.gameModel
    numBodygroups = mdl.GetBodyPartCount()
    
    grpIdx, elemIdx = GetBodygroupIndices(animset, grpNameOrIdx, elemNameOrIdx)
    
    bodygroupValues = GetBodygroupValues(animset)
    
    bodygroupValues[grpIdx] = elemIdx
    
    mdl.body = ComputeBodygroupValue(animset, bodygroupValues)
    
## Animset related stuff
def GetRawFileName(fName):
    return os.path.splitext(os.path.basename(fName))[0]
    
# Given a DmeDag with children, return the first DmeDag with the provided name.
def GrabChild(parent,childName):
    return parent.GetChild(parent.FindChild(childName))
    
def AddOrCreateSceneGroup(grpName):
    shot = sfm.GetCurrentShot()
    
    for sceneElement in shot.scene.children:
        sceneElementName = str(sceneElement.name)
        
        if sceneElementName == grpName:
            return sceneElement
            
    grp = vs.CreateElement("DmeDag", grpName, shot.GetFileId())
    
    shot.scene.AddChild(grp)
    
    return grp
    
def CountInstances(mdlName):
    shot = sfm.GetCurrentShot()
    cnt = 0
    
    animsetNameRoot = GetRawFileName(mdlName).lower()
    
    for child in shot.animationSets:
        if hasattr(child,"gameModel"):
            childName = str(child.name).lower()
            
            if animsetNameRoot in str(childName):
                strNumber = childName.replace(animsetNameRoot,"")
                castNumber = SCast(strNumber, int, -1)
                
                if castNumber > cnt:
                    cnt = castNumber
                
    return cnt
    
def GetAssociatedModelByFilePath(animset, arrayName, mdlName):
    if not hasattr(animset,arrayName):
        animset.AddAttribute(arrayName, vs.AT_ELEMENT_ARRAY)
        
    arrayAnimsets = getattr(animset,arrayName)
    genChar = GetGenericCharacterFromAnimset(animset)
    mdlName = genChar.ContextualizeString(mdlName)
    
    for arrayAnimset in arrayAnimsets:
        #mdlName = GetGenericCharacterFromAnimset(animset).ContextualizeString(str(arrayAnimset.gameModel.modelName))
        if str(arrayAnimset.gameModel.modelName) == mdlName:
            return arrayAnimset
            
    return None
    
def ZeroCtrl(tgtCtrl):
    logs = []
    channels = [tgtCtrl.positionChannel, tgtCtrl.orientationChannel]
    
    for channel in channels:
        layers = channel.log.layers[0]
        
        logs.append(layers.times)
        logs.append(layers.values)
        
    for log in logs:
        log.SetCount(0)
        log.SetCount(1)
      
def LockAndZero(tgtCtrl, parentCtrl):
    ctrlDag = tgtCtrl.GetDag()
    parentDag = parentCtrl.GetDag()
    
    ctrlDag.EnableOverrideParent(True)
    ctrlDag.SetOverrideParent(parentDag, True, True)
        
    ZeroCtrl(tgtCtrl)

# Core logic
def CreateAndAttachAnimset(targetAnimset, mdlName):
    targetAnimsetRoot = targetAnimset.FindControl( "rootTransform" )
    targetAnimsetName = str(targetAnimset.name)
    
    pos = targetAnimsetRoot.GetPosition()
    rot = targetAnimsetRoot.GetOrientation().AsRadianEuler() # Because fuck Quaternions
    
    # Go through all of the master's bones, and just store their names
    targetAnimsetBones = []
     
    for ctrl in targetAnimset.controls:
        if ctrl.GetTypeString() == "DmeTransformControl": # We only want bones
            targetAnimsetBones.append(str(ctrl.name))
            
    attachAnimsetName = GetRawFileName(mdlName) + str(CountInstances(mdlName) + 1)
    
    attachAnimset = sfmUtils.CreateModelAnimationSet(str(attachAnimsetName), str(mdlName))
    attachAnimset.visible = True
    
    # Position this attachmentst's rootTransform atop the master's
    sfm.SetOperationMode( "Record" ) # Change the operation mode so changes can be made

    sfm.PushSelection() # Push the current selection to preserve it

    sfm.ClearSelection()
    sfm.Select(attachAnimsetName + ":rootTransform")

    sfm.Move(pos[0], pos[1], pos[2])
    sfm.Rotate(math.degrees(rot.x), math.degrees(rot.y), math.degrees(rot.z))

    sfm.PopSelection() # Pop the current selection to restore the previous selection
    
    sfm.SetOperationMode( "Pass" ) # Restore the operation mode
    
    # Parent all of the bones that can be parented.
    for bone in targetAnimsetBones:
        attachBone = attachAnimset.FindControl(bone)
        
        if attachBone != None:
            parentName = targetAnimsetName + ":" + bone
            childName = attachAnimsetName + ":" + bone
            
            parentBone = targetAnimset.FindControl(bone)
            
            LockAndZero(attachBone, parentBone)
            
    # Store a reference on the attached object back to the object it is attached to
    attachAnimset.AddAttribute("parentAnimset", vs.AT_ELEMENT)
    attachAnimset.SetValue("parentAnimset", targetAnimset)
    
    return attachAnimset
    
def AddAnimsetToGroup(animset, grpName):
    shot = sfm.GetCurrentShot()
    
    grp = AddOrCreateSceneGroup(grpName)
    
    animsetDag = GrabChild(shot.scene, animset.GetName())
    
    shot.scene.RemoveChild(animsetDag)
    grp.AddChild(animsetDag)
        
def GetOrCreateMaterialOverrides(animset):
    matMap = {}
    
    matNames = GetMaterialNames(animset)
    
    if AnimsetHasMaterialOverrides(animset): # This animset already has material overrides. Just quickly populate the material map with the names of all the materials and their respective objects
        for matName in matNames:
            matMap[matName] =  GetOrCreateMaterial(animset,matName)
            
        return matMap
        
    # Otherwise, we need to build the material overrides
    shdr = animset.gameModel.GetStudioHdr()
    
    for matName in matNames:
        for j in range(shdr.numcdtextures):
            texpath = shdr.pCdtexture(j)
            fullname = texpath + matName
            
            absPath = LordAardvarkShared.GetAbsolutePath(HMF_GameModFolderNames, os.path.join("materials",fullname + ".vmt"))
            
            # Only apply this material if it exists. This is to deal with multiple $cdmaterial's on models.
            if not absPath:
                continue
                
            mat = GetOrCreateMaterial(animset,matName)
            mat.SetValue("mtlName",str(fullname))
            mat.SetValue("groupName","Material Overrides")      

            matMap[matName] = mat
            
    return matMap
    
def CreateHairModel(charAnimset, hairStyleObject):
    mdlName = hairStyleObject.hairModel
    
    grpName = charAnimset.GetName() + " :: hair"
    
    hairAnimset = CreateAndAttachAnimset(charAnimset, mdlName)
    AddAnimsetToGroup(hairAnimset, grpName)
    
    if not hasattr(charAnimset,"hairModels"):
        charAnimset.AddAttribute("hairModels", vs.AT_ELEMENT_ARRAY)
        
    charAnimset.hairModels.AddToTail(hairAnimset)
    
    return hairAnimset
    
def ApplyHairColor(hairAnimset, hairColorNameOrObject):
    if type(hairColorNameOrObject) == str:
        hairColorName = hairColorNameOrObject
        
        if hairColorName not in HMF_HAIR_COLORS:
            return False
            
        hairColorNameOrObject = HMF_HAIR_COLORS[hairColorName]
        
    hairColorObject = hairColorNameOrObject
    
    matMap = GetOrCreateMaterialOverrides(hairAnimset)
    
    for matName in matMap:
        mat = matMap[matName]
        
        mat.AddAttributeAsVector3("$color2")
        mat.SetValue("$color2", QColorToVec3(hairColorObject))
        
def SetActiveHair(charAnimset, hairAnimset):
    if not hasattr(charAnimset,"hairModels"):
        charAnimset.AddAttribute("hairModels", vs.AT_ELEMENT_ARRAY)
        
    hairModels = charAnimset.hairModels
    
    for animset in hairModels:
        animset.visible = (str(animset) == str(hairAnimset))
        
    if not hasattr(charAnimset,"activeHair"):
        charAnimset.AddAttribute("activeHair", vs.AT_ELEMENT)
        
    charAnimset.SetValue("activeHair", hairAnimset)
        
def GetActiveHair(charAnimset):
    if not hasattr(charAnimset, "activeHair"):
        return None
        
    return charAnimset.activeHair
    
def SetHairStyle(charAnimset, hairStyleNameOrObject):
    hairStyleName = hairStyleNameOrObject
    hairStyleObject = hairStyleNameOrObject
    
    if type(hairStyleObject) == str:
        if hairStyleName not in HMF_HAIR_STYLES:
            return False
            
        hairStyleObject = HMF_HAIR_STYLES[hairStyleName]

    hairAnimset = GetAssociatedModelByFilePath(charAnimset, "hairModels", hairStyleObject.hairModel)

    if hairAnimset is None: # We need to create the hair animset, attach it, and record it
        hairAnimset = CreateHairModel(charAnimset, hairStyleObject)
        
    SetActiveHair(charAnimset, hairAnimset)
    SetActiveBodygroup(hairAnimset, hairStyleObject.bodygroupSet, hairStyleObject.bodygroupElement)
    
    return hairAnimset
    
def SetHairColor(charAnimset, hairColorName):
    activeHair = GetActiveHair(charAnimset)
    
    if activeHair is None:
        return False
        
    ApplyHairColor(activeHair, hairColorName)
    
def CreateOutfitModel(charAnimset, outfitObject):
    #mdlName = outfitObject.mdlName
    mdlName = GetGenericCharacterFromAnimset(charAnimset).ContextualizeString(outfitObject.mdlName)
    
    grpName = charAnimset.GetName() + " :: outfit"
    
    charAnimset.gameModel.evaluateProceduralBones = False
    
    outfitAnimset = CreateAndAttachAnimset(charAnimset, mdlName)
    AddAnimsetToGroup(outfitAnimset, grpName)
    
    if not hasattr(charAnimset,"outfitModels"):
        charAnimset.AddAttribute("outfitModels", vs.AT_ELEMENT_ARRAY)
        
    charAnimset.outfitModels.AddToTail(outfitAnimset)
    
    outfitAnimset.AddAttributeAsString("outfitName")
    outfitAnimset.SetValue("outfitName", outfitObject.displayName)
    
    return outfitAnimset
    
def SetActiveOutfit(charAnimset, outfitAnimset):
    if not hasattr(charAnimset,"outfitModels"):
        charAnimset.AddAttribute("outfitModels", vs.AT_ELEMENT_ARRAY)
        
    outfitModels = charAnimset.outfitModels
    
    for animset in outfitModels:
        animset.visible = (str(animset) == str(outfitAnimset))
        
    if not hasattr(charAnimset,"activeOutfit"):
        charAnimset.AddAttribute("activeOutfit", vs.AT_ELEMENT)
        
    charAnimset.SetValue("activeOutfit", outfitAnimset)
        
def GetActiveOutfit(charAnimset):
    if not hasattr(charAnimset, "activeOutfit"):
        return None
        
    return charAnimset.activeOutfit
    
def EquipOutfit(charAnimset, outfitNameOrObject):
    outfitName = outfitNameOrObject
    outfitObject = outfitNameOrObject
    
    if type(outfitObject) == str:
        if outfitName not in HMF_OUTFITS:
            return False
            
        outfitObject = HMF_OUTFITS[outfitName]
        
    outfitAnimset = GetAssociatedModelByFilePath(charAnimset, "outfitModels", outfitObject.mdlName)
            
    if outfitAnimset is None: # We need to create the hair animset, attach it, and record it
        outfitAnimset = CreateOutfitModel(charAnimset, outfitObject)
        
    SetActiveOutfit(charAnimset, outfitAnimset)
    
    #SetActiveBodygroup(hairAnimset, hairStyleObject.bodygroupSet, hairStyleObject.bodygroupElement)
    
    return outfitAnimset
    
def ApplyOutfitBodygroup(charAnimset, bodygroupSetDisplay, bodygroupElementDisplay, matOverrideName, tintName):
    outfitAnimset = GetActiveOutfit(charAnimset)
    outfitName = str(outfitAnimset.outfitName)
    
    if outfitName not in HMF_OUTFITS:
        return False
        
    outfitObject = HMF_OUTFITS[outfitName]
    
    outfitObject.ApplyBodygroup(outfitAnimset, bodygroupSetDisplay, bodygroupElementDisplay, matOverrideName, tintName)
    
# File management
def LoadHairColorData(fName):
    global HMF_HAIR_COLORS
    
    # Testing with hard-coded values
    
    for colorData in HMF_HAIR_COLOR_DATA:
        hairName, hairHex = colorData
        hairColor = QtGui.QColor("#" + hairHex)
        
        HMF_HAIR_COLORS[hairName] = hairColor
        
def LoadHairStyleData(fName):
    pass
    
# UI
## Globals
HMF_UiElements = {}

## Utilities
def RegisteredElement(elementClass, elementName):
    elementObj = elementClass()
    
    HMF_UiElements[elementName] = elementObj
    
    return elementObj
    
def GetUiElement(elementName):
    return HMF_UiElements[elementName]
    
def RandomizeComboBox(drpBox):
    randomIdx = random.randrange(drpBox.count())
    
    drpBox.setCurrentIndex(randomIdx)
    
def CreateCheckableTreeNode(parentNode, nodeDisplay, nodeData, checkState=QtCore.Qt.Unchecked):
    hint = QtCore.QSize()
    hint.setHeight(24)
        
    newNode = QtGui.QTreeWidgetItem(parentNode)
    newNode.setText(0, nodeDisplay)
    newNode.setData(0, QtCore.Qt.UserRole, nodeData)
    newNode.setSizeHint(0, hint)
    newNode.setCheckState(0, checkState)
    #newNode.setChildIndicatorPolicy(QtGui.QTreeWidgetItem.ChildIndicatorPolicy.DontShowIndicator)
    
    return newNode
    
def GetTreeWidgetSiblingNodes(node):
    parentNode = node.parent()
    
    siblingNodes = []
    
    if parentNode is None:
        treeWidget = node.treeWidget()
        
        for idx in range(treeWidget.topLevelItemCount()):
            childNode = treeWidget.topLevelItem(idx)
            
            siblingNodes.append(childNode)
            
        return siblingNodes
        
    for idx in range(parentNode.childCount()):
        childNode = parentNode.child(idx)
        
        siblingNodes.append(childNode)
        
    return siblingNodes
    
def GetCheckedTreeNodePath(treeWidget):
    checkedNodePath = []
    
    curNode = treeWidget
    
    for idx in range(treeWidget.topLevelItemCount()):
        topLevelNode = treeWidget.topLevelItem(idx)
        
        if topLevelNode.checkState(0) == QtCore.Qt.Checked:
            curNode = topLevelNode
            checkedNodePath.append(curNode)
            
    while (curNode.childCount() > 0):
        selectedNode = None
        
        for idx in range(curNode.childCount()):
            childNode = curNode.child(idx)
            
            if childNode.checkState(0) == QtCore.Qt.Checked:
                selectedNode = childNode
                break
                
        checkedNodePath.append(selectedNode)
        curNode = selectedNode
        
    return checkedNodePath
    
def CheckAllFirstNodes(treeWidget):
    curNode = treeWidget.topLevelItem(0)
    
    treeWidget.blockSignals(True)
    
    curNode.setCheckState(0,QtCore.Qt.Checked) # Check the first top-level item
    
    nodesToVisit = []
    for idx in range(treeWidget.topLevelItemCount()): # And begin by visiting all of the top-level items
        nodesToVisit.append(treeWidget.topLevelItem(idx)) 
        
    while len(nodesToVisit) > 0:
        curNode = nodesToVisit.pop()
        childNodeCount = curNode.childCount()
        
        if childNodeCount > 0: # Only check the first child of every node
            firstNode = curNode.child(0)
            firstNode.setCheckState(0, QtCore.Qt.Checked)
            
        for idx in range(childNodeCount): # But visit every child of a given node
            nodesToVisit.append(curNode.child(idx))
            
    treeWidget.blockSignals(False)
    
def ForceTreeExpansion(treeWidget):
    nodesToVisit = []
    
    for idx in range(treeWidget.topLevelItemCount()):
        nodesToVisit.append(treeWidget.topLevelItem(idx))
        
    treeWidget.blockSignals(True)
    
    while len(nodesToVisit) > 0:
        curNode = nodesToVisit.pop()
        
        curNode.setExpanded(curNode.checkState(0) == QtCore.Qt.Checked)
        
        for idx in range(curNode.childCount()):
            nodesToVisit.append(curNode.child(idx))
        
    treeWidget.blockSignals(False)

def InitializeBodygroupsTree(treeWidget):
    treeWidget.blockSignals(True)
    
    curAnimset = GetCurAnimset()
    outfitAnimset = GetActiveOutfit(curAnimset)
    
    outfitName = str(outfitAnimset.outfitName)
    
    outfitObject = HMF_OUTFITS[outfitName]
    
    outfitBodygroupSetMap = {}
    
    for bodygroupKey in outfitObject.bodygroupDatas:
        bodygroupData = outfitObject.bodygroupDatas[bodygroupKey]
        
        bodygroupSetNode = None
        bodygroupElementNode = None
        
        if bodygroupData.bodygroupSetDisplay not in outfitBodygroupSetMap:
            newNode = CreateCheckableTreeNode(treeWidget, bodygroupData.bodygroupSetDisplay, bodygroupData.bodygroupSetDisplay)
            
            outfitBodygroupSetMap[bodygroupData.bodygroupSetDisplay] = newNode
            
        bodygroupSetNode = outfitBodygroupSetMap[bodygroupData.bodygroupSetDisplay]
        bodygroupElementNode = CreateCheckableTreeNode(bodygroupSetNode, bodygroupData.bodygroupElementDisplay, bodygroupData)
        
        for matOverrideName in bodygroupData.materialOverrides:
            matOverride = bodygroupData.materialOverrides[matOverrideName]
            
            matOverrideNode = CreateCheckableTreeNode(bodygroupElementNode, matOverride.displayName, matOverride)
            
            for tintName in matOverride.tints:
                tint = matOverride.tints[tintName]
                
                tintNode = CreateCheckableTreeNode(matOverrideNode, tint.displayName, tint)
                    
    treeWidget.blockSignals(False)
    
    CheckAllFirstNodes(treeWidget)
    ForceTreeExpansion(treeWidget)
    
## Hooks
def onRandomizeHairStyle():
    RandomizeComboBox(GetUiElement("drpHairStyle"))
    
def onRandomizeHairColor():
    RandomizeComboBox(GetUiElement("drpHairColor"))
    
def onApplyHairStyle(window):
    drpHairStyle = GetUiElement("drpHairStyle")
    drpHairColor = GetUiElement("drpHairColor")
    
    hairStyleName = str(drpHairStyle.currentText())
    hairColorName = str(drpHairColor.currentText())
    
    SetHairStyle(GetCurAnimset(), hairStyleName)
    SetHairColor(GetCurAnimset(), hairColorName)
    
    window.close()
    
def onTreeBodygroupItemChecked(item):
    treeWidget = item.treeWidget()
    
    treeWidget.blockSignals(True)
    
    item.setCheckState(0, QtCore.Qt.Checked) # Force a node to always be checked. Even if the user attempts to uncheck a node, force it checked anyways. Only the sibling-check function can uncheck nodes. The user cannot
    
    childNodes = GetTreeWidgetSiblingNodes(item)
    for childNode in childNodes:
        if childNode is not item:
            childNode.setCheckState(0, QtCore.Qt.Unchecked)
            
    treeWidget.blockSignals(False)
    
    ForceTreeExpansion(treeWidget)
    
def onTreeBodygroupApply():
    curAnimset = GetCurAnimset()
    
    checkedPath = GetCheckedTreeNodePath(GetUiElement("treeBodygroups"))
    
    bodygroupSetDisplay = str(checkedPath[0].text(0))
    bodygroupElementDisplay = str(checkedPath[1].text(0))
    matOverrideName = str(checkedPath[2].text(0))
    tintName = None
    
    if len(checkedPath) > 3:
        tintName = str(checkedPath[3].text(0))
        
    ApplyOutfitBodygroup(curAnimset, bodygroupSetDisplay, bodygroupElementDisplay, matOverrideName, tintName)
    
## Windows
def CreateUI_HairStyle():
    animset = GetCurAnimset()
    
    if not hasattr(animset,"gameModel"):
        CreateAnimsetUI()
        return 0
    
    # Create the base UI window
    window = RegisteredElement(QtGui.QDialog,"window")
    window.resize(360,80)
    window.setWindowTitle("HMF v1.0.0 - HairStyle")

    grid = QtGui.QGridLayout()
    grid.setSpacing(12)
    grid.setColumnMinimumWidth(0, 72)
    
    # Labels along the top
    lblHairStyle = QtGui.QLabel()
    lblHairStyle.setText("Hair Style:")
    grid.addWidget(lblHairStyle, 0, 0, 1, 1)
    
    lblHairColor = QtGui.QLabel()
    lblHairColor.setText("Hair Color:")
    grid.addWidget(lblHairColor, 0, 1, 1, 1)
    
    # Dropdowns and apply button
    hairStyleNames = [hairStyleName for hairStyleName in HMF_HAIR_STYLES]
    hairColorNames = [hairColorName for hairColorName in HMF_HAIR_COLORS]
    
    hairStyleNames.reverse()
    hairColorNames.reverse()
    
    drpHairStyle = RegisteredElement(QtGui.QComboBox,"drpHairStyle")
    drpHairStyle.addItems(hairStyleNames)
    grid.addWidget(drpHairStyle, 1, 0, 1, 1)
    
    drpHairColor = RegisteredElement(QtGui.QComboBox,"drpHairColor")
    drpHairColor.addItems(hairColorNames)
    grid.addWidget(drpHairColor, 1, 1, 1, 1)
    
    btnApplyHair = RegisteredElement(QtGui.QPushButton,"btnApplyHair")
    btnApplyHair.setText("Apply")
    btnApplyHair.clicked.connect(lambda: onApplyHairStyle(window))
    grid.addWidget(btnApplyHair, 1, 2, 1, 1)
    
    # Randomize buttons
    btnRandomizeHairStyle = RegisteredElement(QtGui.QPushButton, "btnRandomizeHairStyle")
    btnRandomizeHairStyle.setText("Randomize")
    btnRandomizeHairStyle.clicked.connect(lambda: onRandomizeHairStyle())
    grid.addWidget(btnRandomizeHairStyle, 2, 0, 1, 1)
    
    btnRandomizeHairColor = RegisteredElement(QtGui.QPushButton, "btnRandomizeHairColor")
    btnRandomizeHairColor.setText("Randomize")
    btnRandomizeHairColor.clicked.connect(lambda: onRandomizeHairColor())
    grid.addWidget(btnRandomizeHairColor, 2, 1, 1, 1)
    
    # Display the window
    window.setLayout(grid)
    window.setModal(True)
    window.exec_()    

def CreateUI_OutfitBodygroups():
    animset = GetCurAnimset()
    
    if not hasattr(animset,"gameModel"):
        CreateAnimsetUI()
        return 0
        
    # Create the base UI window
    window = RegisteredElement(QtGui.QDialog,"window")
    window.resize(360,720)
    window.setWindowTitle("HMF v1.0.0 - Outfit Bodygroups")

    grid = QtGui.QGridLayout()
    grid.setSpacing(12)
    grid.setColumnMinimumWidth(1, 72)   

    # Tree widget for the outfit bodygroups
    treeBodygroups = RegisteredElement(QtGui.QTreeWidget,"treeBodygroups")
    treeBodygroups.setColumnCount(1)
    treeBodygroups.setHeaderLabels(["Bodygroups"])
    treeBodygroups.header().setVisible(False)
    treeBodygroups.setDragDropMode(QtGui.QAbstractItemView.InternalMove)
    treeBodygroups.setDropIndicatorShown(True)
    treeBodygroups.setItemsExpandable(False)
    treeBodygroups.setRootIsDecorated(False)
    treeBodygroups.setStyleSheet("QTreeWidget::branch { border-image: url(none.png); }")
    #treeBodygroups.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
    
    treeBodygroups.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
    #treeBodygroups.customContextMenuRequested.connect(TreeContext)
    
    treeBodygroups.itemChanged.connect(lambda item: onTreeBodygroupItemChecked(item))
    #treeBodygroups.itemClicked.connect(lambda item: fuckYou())
    #treeBodygroups.doubleClicked.connect(TreeItem_OnDoubleClick)
    grid.addWidget(treeBodygroups, 0, 0, 1, 2)
    
    InitializeBodygroupsTree(treeBodygroups)
    
    # Buttons
    btnApplyBodygroup = RegisteredElement(QtGui.QPushButton, "btnApplyBodygroup")
    btnApplyBodygroup.setText("Apply")
    btnApplyBodygroup.clicked.connect(lambda: onTreeBodygroupApply())
    grid.addWidget(btnApplyBodygroup, 1, 0, 1, 1)
    
    btnConfirmBodygroups = RegisteredElement(QtGui.QPushButton, "btnConfirmBodygroups")
    btnConfirmBodygroups.setText("Confirm")
    btnConfirmBodygroups.clicked.connect(lambda: window.close())
    grid.addWidget(btnConfirmBodygroups, 1, 1, 1, 1)
    
    # Display the window
    window.setLayout(grid)
    window.setModal(True)
    window.exec_()       
    
def CreateUI():
    animset = GetCurAnimset()
    
    if not hasattr(animset,"gameModel"):
        CreateAnimsetUI()
        return 0
        
    # Create the base UI window
    window = RegisteredElement(QtGui.QDialog,"window")
    window.resize(120,120)
    window.setWindowTitle("Hitman Female Customizer")

    grid = QtGui.QGridLayout()
    grid.setSpacing(12)

    # Buttons
    btnSetHairStyle = RegisteredElement(QtGui.QPushButton,"btnSetHairStyle")
    btnSetHairStyle.setText("Hair Style")
    btnSetHairStyle.clicked.connect(lambda: CreateUI_HairStyle())
    grid.addWidget(btnSetHairStyle, 0, 0, 1, 1)
    
    btnSetBodygroup = RegisteredElement(QtGui.QPushButton,"btnSetBodygroup")
    btnSetBodygroup.setText("Outfit")
    btnSetBodygroup.clicked.connect(lambda: CreateUI_OutfitBodygroups())
    grid.addWidget(btnSetBodygroup, 1, 0, 1, 1)
    
    btnConfirm = RegisteredElement(QtGui.QPushButton,"btnConfirm")
    btnConfirm.setText("Confirm")
    btnConfirm.clicked.connect(lambda: window.close())
    grid.addWidget(btnConfirm, 2, 0, 1, 1)
    
    # Display the window
    window.setLayout(grid)
    window.setModal(True)
    window.exec_()   
    
# Initialization
if len(HMF_GameModFolderNames) < 1:
    LordAardvarkShared.ParseGameInfo(HMF_GameInfoPath, HMF_GameModFolderNames)
    
LoadHairColorData("fuck you")
LoadHairStyleData("fuck you")

SetHairStyle(sfm.GetCurrentAnimationSet(), "Crazy Morning")
EquipOutfit(sfm.GetCurrentAnimationSet(), "Casual")

CreateUI()

