# -*- coding: utf-8 -*-
from maya import mel
from maya import cmds
from . import lang
from . import common
import os
import json
import re

class WeightCopyPaste():
    def main(self, skinMeshes, mode='copy', saveName='default', method='index', weightFile='auto', 
                        threshold=0.2, engine='maya', tgt=1, path='default', viewmsg=False):
        if viewmsg:
            cmds.inViewMessage( amg='<hl>Simple Weight</hl> : '+mode, pos='midCenterTop', fade=True, ta=0.75, a=0.5)
        '''
        ウェイトデータの保存、読み込み関数
        mode→コピーするかペーストするか'copy'or'paste'
        saveName→ウェイトデータの保存フォルダ名。ツール、モデル名とかで分けたい場合に指定
        method→ペーストの仕方,「index」、「nearest」、「barycentric」、「over」
        「index」法は、頂点インデックスを使用してウェイトをオブジェクトにマッピングします。マッピング先のオブジェクトと書き出し後のデータのトポロジが同じ場合、これが最も便利な手法です。
        「nearest」法は、読み込んだデータのニアレスト頂点を検索し、ウェイト値をその値に設定します。これは、高解像度メッシュを低解像度メッシュにマッピングする場合に最適です。
        「barycentric」法はポリゴン メッシュでのみサポートされます。ターゲット ジオメトリのニアレスト三角を検索し、
        ソース ポイントと頂点の距離に応じてウェイトを再スケールします。これは通常、高解像度メッシュにマッピングされる粗いメッシュで使用されます。
        「over」法は「index」法に似ていますが、マッピング前に対象メッシュのウェイトがクリアされないため、一致していないインデックスのウェイトがそのまま維持されます。

        nearest と barycentricは不具合のため現状仕様不可能(処理が終わらない)2016/11/03現在
        →barycentric、bylinearはMaya2016Extention2から利用可能

        weightFile→メッシュ名検索でなく手動指定したい場合にパスを指定。methodのnearest、barycentricとセットで使う感じ。
        →Mayaコピー時にファイル名指定すると複数保存できないので注意。
        
        threshold→nearest,barycentricの位置検索範囲
        '''
        self.skinMeshes = skinMeshes
        self.saveName = saveName
        self.method = method
        self.weightFile = weightFile
        self.threshold = threshold
        self.engine = engine
        self.memShapes = {}
        self.target = tgt
        self.pasteMode = {'index':1, 'nearest':3}
        # リストタイプじゃなかったらリストに変換する
        if not isinstance(self.skinMeshes, list):
            temp = self.skinMeshes
            self.skinMeshes = []
            self.skinMeshes.append(temp)
        # ファイルパスを生成しておく
        if path == 'default':
            self.filePath = os.getenv('MAYA_APP_DIR') + os.sep +'Scripting_Files'+ os.sep + 'weight' + os.sep + self.saveName
        elif path == 'project':
            self.scene_path = os.sep.join(cmds.file(q=True, sceneName=True).split(os.sep)[:-1])
            self.protect_path = os.path.join(self.scene_path, 'weight_protector')
            try:
                if not os.path.exists(self.protect_path):
                    os.makedirs(self.protect_path)
            except Exception as e:
                print e.message
                return
            self.filePath = self.protect_pat+os.sep + self.saveName
        self.fileName = os.path.join(self.filePath, self.saveName + '.json')
        self.apiName = os.path.join(self.filePath, self.saveName + '.skn')
        # コピーかペーストをそれぞれ呼び出し
        if mode == 'copy':
            self.weightCopy()
        if mode == 'paste':
            self.weightPaste()

    def weightPaste(self):
        dummy = cmds.spaceLocator()
        for skinMesh in self.skinMeshes:
            # 読みに行くセーブファイル名を指定、autoならメッシュ名
            if self.weightFile == 'auto':
                weightFile = skinMesh
            else:
                weightFile = self.weightFile
            dstSkinCluster = cmds.ls(cmds.listHistory(skinMesh), type='skinCluster')
            # スキンクラスタがない場合はあらかじめ取得しておいた情報をもとにバインドする
            if not dstSkinCluster:
                meshName = str(weightFile).replace('|', '__pipe__')
                if os.path.exists(self.fileName):
                    try:
                        with open(self.fileName, 'r') as f:  # ファイル開く'r'読み込みモード'w'書き込みモード
                            saveData = json.load(f)  # ロード
                            # self.visibility = saveData['visibility']#セーブデータ読み込み
                            skinningMethod = saveData[';skinningMethod']
                            dropoffRate = saveData[';dropoffRate']
                            maintainMaxInfluences = saveData[';maintainMaxInfluences']
                            maxInfluences = saveData[';maxInfluences']
                            bindMethod = saveData[';bindMethod']
                            normalizeWeights = saveData[';normalizeWeights']
                            influences = saveData[';influences']
                        # 子のノードがトランスフォームならダミーに親子付けして退避
                        common.TemporaryReparent().main(skinMesh, dummyParent=dummy, mode='cut')
                        influences = cmds.ls(influences, l=True, tr=True)
                        # バインド
                        dstSkinCluster = cmds.skinCluster(
                            skinMesh,
                            influences,
                            omi=maintainMaxInfluences,
                            mi=maxInfluences,
                            dr=dropoffRate,
                            sm=skinningMethod,
                            nw=normalizeWeights,
                            tsb=True,
                        )
                        dstSkinCluster = dstSkinCluster[0]
                        # 親子付けを戻す
                        common.TemporaryReparent().main(skinMesh, dummyParent=dummy, mode='parent')
                        tempSkinNode = skinMesh#親を取得するためスキンクラスタのあるノードを保存しておく
                    except Exception as e:
                        print e.message
                        print 'Error !! Skin bind failed : ' + skinMesh
                        continue
            else:
                dstSkinCluster = dstSkinCluster[0]
                tempSkinNode = skinMesh#親を取得するためスキンクラスタのあるノードを保存しておく
            if self.engine == 'maya':
                files = os.listdir(self.filePath)
                print files
                if len(files) == 2:
                    for file in files:
                        name, ext = os.path.splitext(file)
                        if ext == '.xml':
                            xml_name = file
                else:
                    # Pipeはファイル名に出来ないので変換しておく
                    meshName = str(weightFile).replace('|', '__pipe__')
                    # コロンはファイル名に出来ないので変換しておく
                    meshName = str(meshName).replace(':', '__colon__')
                    
                    xml_name = meshName + '.xml'
                    
                
                if os.path.isfile(self.filePath + os.sep + xml_name):
                    if self.method == 'index' or self.method == 'over':
                        cmds.deformerWeights(xml_name,
                                             im=True,
                                             method=self.method,
                                             deformer=dstSkinCluster,
                                             path=self.filePath + os.sep)
                    else:
                        cmds.deformerWeights(xml_name,
                                             im=True,
                                             deformer=dstSkinCluster,
                                             method=self.method,
                                             worldSpace=True,
                                             positionTolerance=self.threshold,
                                             path=self.filePath + os.sep)
                    cmds.skinCluster(dstSkinCluster, e=True, forceNormalizeWeights=True)
                    print 'Weight paste to : ' + str(skinMesh)
                else:
                    print 'Not exist seved weight XML file : ' + skinMesh
        # ダミー親削除
        cmds.delete(dummy)
        cmds.select(self.skinMeshes, r=True)
            
    # ウェイト情報を保存する関数
    def weightCopy(self):
        saveData = {}
        # 保存ディレクトリが無かったら作成
        if not os.path.exists(self.filePath):
            os.makedirs(os.path.dirname(self.filePath + os.sep))  # 末尾\\が必要なので注意
        else:  # ある場合は中身を削除
            files = os.listdir(self.filePath)
            if files is not None:
                for file in files:
                    os.remove(self.filePath + os.sep + file)
        skinFlag = False
        all_influences = []
        for skinMesh in self.skinMeshes:
            try:
                cmds.bakePartialHistory(skinMesh, ppt=True)
            except:
                pass
            # ノードの中からスキンクラスタを取得してくる#inMesh直上がSkinClusterとは限らないので修正
            srcSkinCluster = cmds.ls(cmds.listHistory(skinMesh), type='skinCluster')
            if not srcSkinCluster:
                continue  # スキンクラスタがなかったら次に移行
            tempSkinNode = skinMesh#親を取得するためスキンクラスタのあるノードを保存しておく
            # スキンクラスタのパラメータ色々を取得しておく
            srcSkinCluster = srcSkinCluster[0]
            skinningMethod = cmds.getAttr(srcSkinCluster + ' .skm')
            dropoffRate = cmds.getAttr(srcSkinCluster + ' .dr')
            maintainMaxInfluences = cmds.getAttr(srcSkinCluster + ' .mmi')
            maxInfluences = cmds.getAttr(srcSkinCluster + ' .mi')
            bindMethod = cmds.getAttr(srcSkinCluster + ' .bm')
            normalizeWeights = cmds.getAttr(srcSkinCluster + ' .nw')
            influences = cmds.skinCluster(srcSkinCluster, q=True, inf=True)
            saveData[';skinningMethod'] = skinningMethod
            saveData[';dropoffRate'] = dropoffRate
            saveData[';maintainMaxInfluences'] = maintainMaxInfluences
            saveData[';maxInfluences'] = maxInfluences
            saveData[';bindMethod'] = bindMethod
            saveData[';normalizeWeights'] = normalizeWeights
            all_influences += influences
            #saveData[';influences'] = influences
            skinFlag = True
        all_influences = list(set(all_influences))
        saveData[';influences'] = all_influences
        #インフルエンス数の変化に耐えられるようにあらかじめAddしてからコピーするS
        for skinMesh in self.skinMeshes:
            srcSkinCluster = cmds.ls(cmds.listHistory(skinMesh), type='skinCluster')
            if not srcSkinCluster:
                continue  # スキンクラスタがなかったらfor分の次に移行
            srcSkinCluster = srcSkinCluster[0]
            influences = cmds.skinCluster(srcSkinCluster, q=True, inf=True) 
            sub_influences = list(set(all_influences) - set(influences))
            if sub_influences:
                cmds.skinCluster(skinMesh, e=True, ai=sub_influences, lw=True, ug=True, wt=0, ps=0)
            if self.engine == 'maya':
                # 読みに行くセーブファイル名を指定、autoならメッシュ名
                if self.weightFile == 'auto':
                    weightFile = skinMesh
                else:
                    weightFile = self.weightFile
                # Pipeはファイル名に出来ないので変換しておく
                meshName = str(weightFile).replace('|', '__pipe__')
                # コロンはファイル名に出来ないので変換しておく
                meshName = str(meshName).replace(':', '__colon__')
                cmds.deformerWeights(meshName + '.xml', export=True, deformer=srcSkinCluster, path=self.filePath + os.sep)
        with open(self.fileName, 'w') as f:  # ファイル開く'r'読み込みモード'w'書き込みモード
            json.dump(saveData, f)

def transfer_weight(skinMesh, transferedMesh, transferWeight=True, returnInfluences=False, logTransfer=True):
    '''
    スキンウェイトの転送関数
    転送先がバインドされていないオブジェクトの場合は転送元のバインド情報を元に自動バインド
    ・引数
    skinMesh→転送元メッシュ(1個,リスト形式でも可)
    transferedMesh(リスト形式,複数可、リストじゃなくても大丈夫)
    transferWeight→ウェイトを転送するかどうか。省略可能、デフォルトはTrue
    logTransfer→ログ表示するかどうか
    returnInfluences→バインドされているインフルエンス情報を戻り値として返すかどうか。省略可能、デフォルトはFalse
    '''

    massege01 = lang.Lang(
        en=': It does not perform the transfer of weight because it is not a skin mesh.',
        ja=u': スキンメッシュではないのでウェイトの転送を行いません'
    ).output()
    massege02 = lang.Lang(
        en='Transfer the weight:',
        ja=u'ウェイトを転送:'
    ).output()
    massege03 = lang.Lang(
        en='Transfer bind influences:',
        ja=u'バインド状態を転送:'
    ).output()

    if isinstance(skinMesh, list):  # 転送元がリストだった場合、最初のメッシュのみ取り出す
        skinMesh = skinMesh[0]  # リストを渡されたときのための保険
        
    # ノードの中からスキンクラスタを取得してくる#inMesh直上がSkinClusterとは限らないので修正
    srcSkinCluster = cmds.ls(cmds.listHistory(skinMesh), type='skinCluster')
    # srcSkinCluster = cmds.listConnections(skinMesh+'.inMesh', s=True, d=False)
    if not srcSkinCluster:
        if logTransfer:
            print skinMesh + massege01
        return False  # スキンクラスタがなかったら関数抜ける
    # スキンクラスタのパラメータ色々を取得しておく
    srcSkinCluster = srcSkinCluster[0]
    skinningMethod = cmds.getAttr(srcSkinCluster + ' .skm')
    dropoffRate = cmds.getAttr(srcSkinCluster + ' .dr')
    maintainMaxInfluences = cmds.getAttr(srcSkinCluster + ' .mmi')
    maxInfluences = cmds.getAttr(srcSkinCluster + ' .mi')
    bindMethod = cmds.getAttr(srcSkinCluster + ' .bm')
    normalizeWeights = cmds.getAttr(srcSkinCluster + ' .nw')
    influences = cmds.skinCluster(srcSkinCluster, q=True, inf=True)  # qフラグは照会モード、ちなみにeは編集モード

    # リストタイプじゃなかったらリストに変換する
    if not isinstance(transferedMesh, list):
        temp = transferedMesh
        transferedMesh = []
        transferedMesh.append(temp)

    for dst in transferedMesh:
        #子供のノード退避用ダミーペアレントを用意
        dummy = common.TemporaryReparent().main(mode='create')
        common.TemporaryReparent().main(dst,dummyParent=dummy, mode='cut')
        
        shapes = cmds.listRelatives(dst, s=True, pa=True, type='mesh')
        if not shapes:  # もしメッシュがなかったら
            continue  # 処理を中断して次のオブジェクトへ
        # スキンクラスタの有無を取得
        dstSkinCluster = cmds.ls(cmds.listHistory(shapes[0]), type='skinCluster')
        # スキンクラスタがない場合はあらかじめ取得しておいた情報をもとにバインドする
        if not dstSkinCluster:
            # バインド
            dstSkinCluster = cmds.skinCluster(
                dst,
                influences,
                omi=maintainMaxInfluences,
                mi=maxInfluences,
                dr=dropoffRate,
                sm=skinningMethod,
                nw=normalizeWeights,
                tsb=True,
            )
            if logTransfer:
                print massege03 + '[' + skinMesh + '] >>> [' + dst + ']'
        dstSkinCluster = dstSkinCluster[0]

        if transferWeight:
            cmds.copySkinWeights(
                ss=srcSkinCluster,
                ds=dstSkinCluster,
                surfaceAssociation='closestPoint',
                influenceAssociation=['name', 'closestJoint', 'oneToOne'],
                normalize=True,
                noMirror=True
            )
            if logTransfer:
                print massege02 + '[' + skinMesh + '] >>> [' + dst + ']'
        #親子付けを戻す
        common.TemporaryReparent().main(dst,dummyParent=dummy, mode='parent')
        #ダミーペアレントを削除
        common.TemporaryReparent().main(dummyParent=dummy, mode='delete')
    if returnInfluences:
        return influences
    else:
        return True
        
def symmetry_weight(srcNode=None, dstNode=None, symWeight=True):
    '''
    ウェイトシンメトリする関数
    srcNode→反転元
    dstNode→反転先
    symWeight→ウェイトミラーするかどうか
    '''
    # スキンクラスタを取得
    if srcNode is None:
        return
    srcShapes = cmds.listRelatives(srcNode, s=True, pa=True, type='mesh')
    if srcShapes:
        srcSkinCluster = cmds.ls(cmds.listHistory(srcNode), type='skinCluster')
        # スキンクラスタがあったらジョイントラベルを設定してウェイトミラー
        if srcSkinCluster:
            # バインド状態を転送する関数呼び出し
            skinJointAll = cmds.skinCluster(srcSkinCluster, q=True, inf=True) #ジョイントを取得
            for skinJoint in skinJointAll:
                # ジョイントラベル設定関数呼び出し
                joint_label(skinJoint, visibility=False)
            if symWeight is False or dstNode is None:
                return
            transfer_weight(srcNode, dstNode, transferWeight=False, returnInfluences=True)
            dstShapes = cmds.listRelatives(dstNode, s=True, pa=True, type='mesh')
            dstSkinCluster = cmds.listConnections(dstShapes[0] + '.inMesh', s=True, d=False)
            cmds.copySkinWeights(ss=srcSkinCluster[0], ds=dstSkinCluster[0],
                                 mirrorMode='YZ', surfaceAssociation='closestComponent',
                                 influenceAssociation='label', normalize=True)
                                 
def load_joint_label_rules():
    #ロードできなかった時の初期値
    start_l_list = ['L_', 'l_', 'Left_', 'left_']
    start_r_list = ['R_', 'r_', 'Right_', 'right_']
    mid_l_list = ['_L_', '_l_', '_Left_', '_left_']
    mid_r_list = ['_R_', '_r_', '_Right_', '_right_']
    end_l_list = ['_L', '_l', '_L.', '_l.', '_L..', '_l..', '_Left', '_left']
    end_r_list = ['_R', '_r', '_R.', '_r.', '_R..', '_r..', '_Right', '_right']
    def_left_list_list = [start_l_list, mid_l_list, end_l_list]
    def_right_list_list = [start_r_list, mid_r_list, end_r_list]
    #左右対称設定ファイルからルールをロードする
    dir_path = os.path.join(
                    os.getenv('MAYA_APP_DIR'),
                    'Scripting_Files')
    start_file = dir_path+os.sep+'joint_rule_start.json'
    middle_file = dir_path+os.sep+'joint_rule_middle.json'
    end_file = dir_path+os.sep+'joint_rule_end.json'
    save_files = [start_file, middle_file, end_file]
    
    left_list_list = []
    right_list_list = []
    for i, save_file in enumerate(save_files):
        if os.path.exists(save_file):#保存ファイルが存在したら
            try:
                with open(save_file, 'r') as f:
                    save_data = json.load(f)
                    l_list = save_data.keys()
                    r_list = save_data.values()
                    left_list_list.append(l_list)
                    right_list_list.append(r_list)
            except Exception as e:
                print e.message
                left_list_list.append(def_left_list_list[i])
                right_list_list.append(def_right_list_list[i])
        else:
            left_list_list.append(def_left_list_list[i])
            right_list_list.append(def_right_list_list[i])
    return left_list_list, right_list_list
    
def joint_label(object, visibility=False):
    '''
    ジョイントラベル設定関数
    object→オブジェクト、リスト形式可
    visibility→ラベルの可視性、省略可能。デフォルトFalse。
    '''
    #ラベリングルールをロードしておく
    left_list_list, right_list_list = load_joint_label_rules()
    
    # リストタイプじゃなかったらリストに変換する
    if not isinstance(object, list):
        temp = object
        object = []
        object.append(temp)
    for skinJoint in object:
        objTypeName = cmds.objectType(skinJoint)
        
        if objTypeName == 'joint':
            split_name = skinJoint.split('|')[-1]
            
            # スケルトン名にLRが含まれているかどうかを判定
            side = 0
            side_name = ''
            for i, (l_list, r_list) in enumerate(zip(left_list_list, right_list_list)):
                for j, lr_list in enumerate([l_list, r_list]):
                    for k, lr in enumerate(lr_list):
                        if i == 0:
                            if re.match(lr, split_name):
                                side = j + 1
                        if i == 1:
                            if re.search(lr, split_name):
                                side = j + 1
                        if i == 2:
                            if re.match(lr[::-1], split_name[::-1]):
                                side = j + 1
                        if side:#対象が見つかってたら全部抜ける
                            side_name = lr
                            break
                    if side:
                        break
                if side:
                    break
            #print 'joint setting :', split_name, side, side_name
            # 左右のラベルを設定、どちらでもないときは中央
            cmds.setAttr(skinJoint + '.side', side)
            # ラベルタイプを”その他”に設定
            cmds.setAttr(skinJoint + '.type', 18)
            new_joint_name = split_name.replace(side_name.replace('.', ''), '')
                
            # スケルトン名設定
            cmds.setAttr(skinJoint + '.otherType', new_joint_name, type='string')
            # 可視性設定
            cmds.setAttr(skinJoint + '.drawLabel', visibility)
        else:
            print(str(skinJoint) + ' : ' + str(objTypeName) + ' Skip Command')
            
#ウェイトのミュートをトグル
def toggle_mute_skinning():
    msg01 = lang.Lang(
        en='No mesh selection.\nWould you like to process all of mesh in this scene?.',
        ja=u'選択メッシュがありません。\nシーン内のすべてのメッシュを処理しますか?').output()
    msg02 = lang.Lang(en='Yes', ja=u'はい').output()
    msg03 = lang.Lang(en='No', ja=u'いいえ').output()
    msg04 = lang.Lang(
        en='Skinning is disabled',
        ja=u'スキニングは無効になりました') .output()
    msg05 = lang.Lang(
        en='Skinning is enabled',
        ja=u'スキニングが有効になりました') .output()
    
    cmds.selectMode(o=True)
    objects = cmds.ls(sl=True, l=True)
    ad_node = []
    
    for node in objects:
        children = cmds.ls(cmds.listRelatives(node, ad=True, f=True), type ='transform')
        ad_node += [node]+children
    #print len(ad_node)
    objects = set(ad_node)
    #print len(objects)
    
    if not objects:
        all_mesh = cmds.confirmDialog(m=msg01, t='', b= [msg02, msg03], db=msg02, cb=msg03, icn='question',ds=msg03)
        if all_mesh == msg02:
            objects = cmds.ls(type='transform')
            
    if not objects:
        return
        
    mute_flag = 1
    skin_list = []
    for node in objects:
        skin = cmds.ls(cmds.listHistory(node), type='skinCluster')
        if not skin:
            continue
        skin_list.append(skin)
        if cmds.getAttr(skin[0]+'.envelope') > 0:
            mute_flag = 0
    for skin in skin_list:
        cmds.setAttr(skin[0]+'.envelope', mute_flag)
    if mute_flag == 0:
        cmds.confirmDialog(m=msg04)
    if mute_flag == 1:
        cmds.confirmDialog(m=msg05)