# -*- coding: UTF-8 -*-
# Copyright 2020 by Kurt Rathjen. All Rights Reserved.
#
# This library is free software: you can redistribute it and/or modify it 
# under the terms of the GNU Lesser General Public License as published by 
# the Free Software Foundation, either version 3 of the License, or 
# (at your option) any later version. This library is distributed in the 
# hope that it will be useful, but WITHOUT ANY WARRANTY; without even the 
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
# See the GNU Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with this library. If not, see <http://www.gnu.org/licenses/>.
"""
# mirrortable.py

import mutils

# Example 1:
# Create a MirrorTable instance from the given objects
mt = mutils.MirrorTable.fromObjects(objects, "_l_", "_r_", MirrorPlane.YZ)

# Example 2:
# Create a MirrorTable instance from the selected objects
objects = maya.cmds.ls(selection=True)
mt = mutils.MirrorTable.fromObjects(objects, "_l_", "_r_", MirrorPlane.YZ)

# Example 3:
# Save the MirrorTable to the given JSON path
path = "/tmp/mirrortable.json"
mt.save(path)

# Example 4:
# Create a MirrorTable instance from the given JSON path
path = "/tmp/mirrortable.json"
mt = mutils.MirrorTable.fromPath(path)

# Example 5:
# Mirror all the objects from file
mt.load()

# Example 6:
# Mirror only the selected objects
objects = maya.cmds.ls(selection=True) or []
mt.load(objects=objects)

# Example 7:
# Mirror all objects from file to the given namespaces
mt.load(namespaces=["character1", "character2"])

# Example 8:
# Mirror only the given objects
mt.load(objects=["character1:Hand_L", "character1:Finger_L"])

# Example 9:
# Mirror all objects from left to right
mt.load(option=mutils.MirrorOption.LeftToRight)

# Example 10:
# Mirror all objects from right to left
mt.load(option=mutils.MirrorOption.RightToLeft)

# Example 11:
# Mirror only the current pose
mt.load(animation=False)
"""

import re
import mutils
import logging
import traceback

from studiovendor import six

try:
    import maya.cmds
except ImportError:
    traceback.print_exc()


__all__ = [
    "MirrorTable",
    "MirrorPlane",
    "MirrorOption",
    "saveMirrorTable",
]


logger = logging.getLogger(__name__)


RE_LEFT_SIDE = "Left|left|Lf|lt_|_lt|lf_|_lf|_l_|_L|L_|:l_|^l_|_l$|:L|^L"
RE_RIGHT_SIDE = "Right|right|Rt|rt_|_rt|_r_|_R|R_|:r_|^r_|_r$|:R|^R"

VALID_NODE_TYPES = ["joint", "transform"]


class MirrorPlane:
    YZ = [-1, 1, 1]
    XZ = [1, -1, 1]
    XY = [1, 1, -1]


class MirrorOption:
    Swap = 0
    LeftToRight = 1
    RightToLeft = 2


class KeysOption:
    All = "All Keys"
    SelectedRange = "Selected Range"


def saveMirrorTable(path, objects, metadata=None, *args, **kwargs):
    """
    Convenience function for saving a mirror table to the given disc location.
    
    :type path: str
    :type objects: list[str]
    :type metadata: dict or None
    :type args: list
    :type kwargs: dict
    :rtype: MirrorTable 
    """
    mirrorTable = MirrorTable.fromObjects(objects, *args, **kwargs)

    if metadata:
        mirrorTable.updateMetadata(metadata)

    mirrorTable.save(path)

    return mirrorTable


class MirrorTable(mutils.TransferObject):

    @classmethod
    @mutils.timing
    @mutils.unifyUndo
    @mutils.showWaitCursor
    @mutils.restoreSelection
    def fromObjects(
        cls,
        objects,
        leftSide=None,
        rightSide=None,
        mirrorPlane=None
    ):
        """
        Create a new Mirror Table instance from the given Maya object/controls.
        
        :type objects: list[str]
        :type leftSide: str
        :type rightSide: str
        :type mirrorPlane: mirrortable.MirrorPlane or str
        
        :rtype: MirrorTable
        """
        mirrorPlane = mirrorPlane or MirrorPlane.YZ

        if isinstance(mirrorPlane, six.string_types):

            if mirrorPlane.lower() == "yz":
                mirrorPlane = MirrorPlane.YZ

            elif mirrorPlane.lower() == "xz":
                mirrorPlane = MirrorPlane.XZ

            elif mirrorPlane.lower() == "xy":
                mirrorPlane = MirrorPlane.XY

        mirrorTable = cls()
        mirrorTable.setMetadata(u"左标识", leftSide)
        mirrorTable.setMetadata(u"右标识", rightSide)
        mirrorTable.setMetadata("mirrorPlane", mirrorPlane)

        for obj in objects:
            nodeType = maya.cmds.nodeType(obj)
            if nodeType in VALID_NODE_TYPES:
                mirrorTable.add(obj)
            else:
                msg = "Node of type {0} is not supported. Node name: {1}"
                msg = msg.format(nodeType, obj)
                logger.info(msg)

        return mirrorTable

    @staticmethod
    def findLeftSide(objects):
        """
        Return the left side naming convention for the given objects
        
        :type objects: list[str]
        :rtype: str
        """
        return MirrorTable.findSide(objects, RE_LEFT_SIDE)

    @staticmethod
    def findRightSide(objects):
        """
        Return the right side naming convention for the given objects
        
        :type objects: list[str]
        :rtype: str
        """
        return MirrorTable.findSide(objects, RE_RIGHT_SIDE)

    @classmethod
    def findSide(cls, objects, reSides):
        """
        Return the naming convention for the given object names.
        
        :type objects: list[str]
        :type reSides: str or list[str]
        :rtype: str
        """
        if isinstance(reSides, six.string_types):
            reSides = reSides.split("|")

        # Compile the list of regular expressions into a re.object
        reSides = [re.compile(side) for side in reSides]

        for obj in objects:
            obj = obj.split("|")[-1]
            obj = obj.split(":")[-1]

            for reSide in reSides:

                m = reSide.search(obj)
                if m:
                    side = m.group()

                    if obj.startswith(side):
                        side += "*"

                    if obj.endswith(side):
                        side = "*" + side

                    return side

        return ""

    @staticmethod
    def matchSide(name, side):
        """
        Return True if the name contains the given side.

        :type name: str
        :type side: str
        :rtype: bool
        """
        if side:
            return MirrorTable.rreplace(name, side, "X") != name

        return False

    @staticmethod
    def rreplace(name, old, new):
        """
        convenience method.

        Example:
            print MirrorTable.rreplace("CHR1:RIG:RhandCON", ":R", ":L")
            "CHR1:RIG:LhandCON"

        :type name: str
        :type old: str
        :type new: str
        :rtype: str
        """
        names = []

        for n in name.split("|"):

            namespaces = ''
            if ':' in n:
                namespaces, n = n.rsplit(':', 1)

            other = mutils.mirrortable.MirrorTable.replace(n, old, new)

            if namespaces:
                other = ':'.join([namespaces, other])

            names.append(other)

        return "|".join(names)

    @staticmethod
    def replace(name, old, new):
        """
        A static method that is called by self.mirrorObject.

        :type name: str
        :type old: str
        :type new: str
        :rtype: str
        """
        # Support for replacing a prefix naming convention.
        if old.endswith("*") or new.endswith("*"):
            name = MirrorTable.replacePrefix(name, old, new)

        # Support for replacing a suffix naming convention.
        elif old.startswith("*") or new.startswith("*"):
            name = MirrorTable.replaceSuffix(name, old, new)

        # Support for all other naming conventions.
        else:
            name = name.replace(old, new)

        return name

    @staticmethod
    def replacePrefix(name, old, new):
        """
        Replace the given old prefix with the given new prefix.
        It should also support long names.

        # Example:
        self.replacePrefix("R_footRoll", "R", "L")
        # Result: "L_footRoll" and not "L_footLoll".

        self.replacePrefix("Grp|Ch1:R_footExtra|Ch1:R_footRoll", "R_", "L_")
        # Result: "Grp|Ch1:L_footExtra|Ch1:L_footRoll"

        :type name: str
        :type old: str
        :type new: str
        :rtype: str
        """
        old = old.replace("*", "")
        new = new.replace("*", "")

        if name.startswith(old):
            name = name.replace(old, new, 1)

        return name

    @staticmethod
    def replaceSuffix(name, old, new):
        """
        Replace the given old suffix with the given new suffix.
        It should also support long names.

        # Example:
        self.replaceSuffix("footRoll_R", "R", "L")
        # Result: "footRoll_L" and not "footLoll_L".

        self.replaceSuffix("Grp|Ch1:footExtra_R|Ch1:footRoll_R", "_R", "_L")
        # Result: "Grp|Ch1:footExtra_L|Ch1:footRoll_L"

        :type name: str
        :type old: str
        :type new: str
        :rtype: str
        """
        old = old.replace("*", "")
        new = new.replace("*", "")

        if name.endswith(old):
            name = name[:-len(old)] + new

        return name

    def mirrorObject(self, obj):
        """
        Return the other/opposite side for the given name.

        Example:
            print self.mirrorObject("FKSholder_L")
            # FKShoulder_R

        :type obj: str
        :rtype: str or None
        """
        leftSide = self.leftSide()
        rightSide = self.rightSide()
        return self._mirrorObject(obj, leftSide, rightSide)

    @staticmethod
    def _mirrorObject(obj, leftSide, rightSide):
        """
        A static method that is called by self.mirrorObject.

        :type obj: str
        :rtype: str or None
        """
        dstName = MirrorTable.rreplace(obj, leftSide, rightSide)

        if obj == dstName:
            dstName = MirrorTable.rreplace(obj, rightSide, leftSide)

        if dstName != obj:
            return dstName

        # Return None as the given name is probably a center control
        # and doesn't have an opposite side.
        return None

    @staticmethod
    def animCurve(obj, attr):
        """
        :type obj: str
        :type attr: str
        :rtype: str
        """
        fullname = obj + "." + attr
        connections = maya.cmds.listConnections(fullname, d=False, s=True)
        if connections:
            return connections[0]
        return None

    @staticmethod
    def scaleKey(obj, attr, time=None):
        """
        :type obj: str
        :type attr: str
        """
        curve = MirrorTable.animCurve(obj, attr)
        if curve:
            maya.cmds.selectKey(curve)
            if time:
                maya.cmds.scaleKey(time=time, iub=False, ts=1, fs=1, vs=-1, vp=0, animation="keys")
            else:
                maya.cmds.scaleKey(iub=False, ts=1, fs=1, vs=-1, vp=0, animation="keys")
    @staticmethod
    def formatValue(attr, value, mirrorAxis):
        """
        :type attr: str
        :type value: float
        :type mirrorAxis: list[int]
        :rtype: float
        """
        if MirrorTable.isAttrMirrored(attr, mirrorAxis):
            return value * -1
        return value

    @staticmethod
    def maxIndex(numbers):
        """
        Finds the largest number in a list
        :type numbers: list[float] or list[str]
        :rtype: int
        """
        m = 0
        result = 0
        for i in numbers:
            v = abs(float(i))
            if v > m:
                m = v
                result = numbers.index(i)
        return result

    @staticmethod
    def axisWorldPosition(obj, axis):
        """
        :type obj: str
        :type axis: list[int]
        :rtype: list[float]
        """
        transform1 = maya.cmds.createNode("transform", name="transform1")
        try:
            transform1, = maya.cmds.parent(transform1, obj, r=True)
            maya.cmds.setAttr(transform1 + ".t", *axis)
            maya.cmds.setAttr(transform1 + ".r", 0, 0, 0)
            maya.cmds.setAttr(transform1 + ".s", 1, 1, 1)
            return maya.cmds.xform(transform1, q=True, ws=True, piv=True)
        finally:
            maya.cmds.delete(transform1)

    @staticmethod
    def isAttrMirrored(attr, mirrorAxis):
        """
        :type attr: str
        :type mirrorAxis: list[int]
        :rtype: float
        """
        if mirrorAxis == [-1, 1, 1]:
            if attr == "translateX" or attr == "rotateY" or attr == "rotateZ":
                return True

        elif mirrorAxis == [1, -1, 1]:
            if attr == "translateY" or attr == "rotateX" or attr == "rotateZ":
                return True

        elif mirrorAxis == [1, 1, -1]:
            if attr == "translateZ" or attr == "rotateX" or attr == "rotateY":
                return True

        elif mirrorAxis == [-1, -1, -1]:
            if attr == "translateX" or attr == "translateY" or attr == "translateZ":
                return True
        return False

    @staticmethod
    def isAxisMirrored(srcObj, dstObj, axis, mirrorPlane):
        """
        :type srcObj: str
        :type dstObj: str
        :type axis: list[int]
        :type mirrorPlane: list[int]
        :rtype: int
        """
        old1 = maya.cmds.xform(srcObj, q=True, ws=True, piv=True)
        old2 = maya.cmds.xform(dstObj, q=True, ws=True, piv=True)

        new1 = MirrorTable.axisWorldPosition(srcObj, axis)
        new2 = MirrorTable.axisWorldPosition(dstObj, axis)

        mp = mirrorPlane
        v1 = mp[0]*(new1[0]-old1[0]), mp[1]*(new1[1]-old1[1]), mp[2]*(new1[2]-old1[2])
        v2 = new2[0]-old2[0], new2[1]-old2[1], new2[2]-old2[2]

        d = sum(p*q for p, q in zip(v1, v2))

        if d >= 0.0:
            return False
        return True

    @staticmethod
    def _calculateMirrorAxis(obj, mirrorPlane):
        """
        :type obj: str
        :rtype: list[int]
        """
        result = [1, 1, 1]
        transform0 = maya.cmds.createNode("transform", name="transform0")

        try:
            transform0, = maya.cmds.parent(transform0, obj, r=True)
            transform0, = maya.cmds.parent(transform0, w=True)
            maya.cmds.setAttr(transform0 + ".t", 0, 0, 0)

            t1 = MirrorTable.axisWorldPosition(transform0, [1, 0, 0])
            t2 = MirrorTable.axisWorldPosition(transform0, [0, 1, 0])
            t3 = MirrorTable.axisWorldPosition(transform0, [0, 0, 1])

            t1 = "%.3f" % t1[0], "%.3f" % t1[1], "%.3f" % t1[2]
            t2 = "%.3f" % t2[0], "%.3f" % t2[1], "%.3f" % t2[2]
            t3 = "%.3f" % t3[0], "%.3f" % t3[1], "%.3f" % t3[2]

            if mirrorPlane == MirrorPlane.YZ:  # [-1, 1, 1]:
                x = [t1[0], t2[0], t3[0]]
                i = MirrorTable.maxIndex(x)
                result[i] = -1

            if mirrorPlane == MirrorPlane.XZ:  # [1, -1, 1]:
                y = [t1[1], t2[1], t3[1]]
                i = MirrorTable.maxIndex(y)
                result[i] = -1

            if mirrorPlane == MirrorPlane.XY:  # [1, 1, -1]:
                z = [t1[2], t2[2], t3[2]]
                i = MirrorTable.maxIndex(z)
                result[i] = -1

        finally:
            maya.cmds.delete(transform0)

        return result

    def select(self, objects=None, namespaces=None, **kwargs):
        """
        Select the objects contained in file.
        
        :type objects: list[str] or None
        :type namespaces: list[str] or None
        :rtype: None
        """
        selectionSet = mutils.SelectionSet.fromPath(self.path())
        selectionSet.load(objects=objects, namespaces=namespaces, **kwargs)

    def leftSide(self):
        """
        :rtype: str | None
        """
        return self.metadata().get(u"左标识")

    def rightSide(self):
        """
        :rtype: str | None
        """
        return self.metadata().get(u"右标识")

    def mirrorPlane(self):
        """
        :rtype: lsit[int] | None
        """
        return self.metadata().get("mirrorPlane")

    def mirrorAxis(self, name):
        """
        :rtype: list[int]
        """
        return self.objects()[name]["mirrorAxis"]

    def leftCount(self, objects=None):
        """
        :type objects: list[str]
        :rtype: int
        """
        if objects is None:
            objects = self.objects()
        return len([obj for obj in objects if self.isLeftSide(obj)])

    def rightCount(self, objects=None):
        """
        :type objects: list[str]
        :rtype: int
        """
        if objects is None:
            objects = self.objects()
        return len([obj for obj in objects if self.isRightSide(obj)])

    def createObjectData(self, name):
        """
        :type name:
        :rtype:
        """
        result = {"mirrorAxis": self.calculateMirrorAxis(name)}
        return result

    def matchObjects(
        self,
        objects=None,
        namespaces=None,
    ):
        """
        :type objects: list[str]
        :type namespaces: list[str]
        :rtype: list[str]
        """
        srcObjects = self.objects().keys()

        matches = mutils.matchNames(
            srcObjects=srcObjects,
            dstObjects=objects,
            dstNamespaces=namespaces,
        )

        for srcNode, dstNode in matches:
            dstName = dstNode.name()
            mirrorAxis = self.mirrorAxis(srcNode.name())
            yield srcNode.name(), dstName, mirrorAxis

    def rightToLeft(self):
        """"""
        self.load(option=MirrorOption.RightToLeft)

    def leftToRight(self):
        """"""
        self.load(option=MirrorOption.LeftToRight)

    @mutils.timing
    @mutils.unifyUndo
    @mutils.showWaitCursor
    @mutils.restoreSelection
    def load(
        self,
        objects=None,
        namespaces=None,
        option=None,
        keysOption=None,
        time=None,
    ):
        """
        Load the mirror table for the given objects.

        :type objects: list[str]
        :type namespaces: list[str]
        :type option: mirrorOptions
        :type keysOption: None or KeysOption.SelectedRange
        :type time: None or list[int]
        """
        if option and not isinstance(option, int):
            if option.lower() == "swap":
                option = 0
            elif option.lower() == "left to right":
                option = 1
            elif option.lower() == "right to left":
                option = 2
            else:
                raise ValueError('Invalid load option=' + str(option))

        self.validate(namespaces=namespaces)

        results = {}
        animation = True
        foundObject = False
        srcObjects = self.objects().keys()

        if option is None:
            option = MirrorOption.Swap

        if keysOption == KeysOption.All:
            time = None
        elif keysOption == KeysOption.SelectedRange:
            time = mutils.selectedFrameRange()

        # Check to make sure that the given time is not a single frame
        if time and time[0] == time[1]:
            time = None
            animation = False

        matches = mutils.matchNames(
            srcObjects=srcObjects,
            dstObjects=objects,
            dstNamespaces=namespaces,
        )

        for srcNode, dstNode in matches:
            dstObj = dstNode.name()
            dstObj2 = self.mirrorObject(dstObj) or dstObj

            if dstObj2 not in results:
                results[dstObj] = dstObj2

                mirrorAxis = self.mirrorAxis(srcNode.name())
                dstObjExists = maya.cmds.objExists(dstObj)
                dstObj2Exists = maya.cmds.objExists(dstObj2)

                if dstObjExists and dstObj2Exists:
                    foundObject = True
                    if animation:
                        self.transferAnimation(dstObj, dstObj2, mirrorAxis=mirrorAxis, option=option, time=time)
                    else:
                        self.transferStatic(dstObj, dstObj2, mirrorAxis=mirrorAxis, option=option)
                else:
                    if not dstObjExists:
                        msg = "Cannot find destination object {0}"
                        msg = msg.format(dstObj)
                        logger.debug(msg)

                    if not dstObj2Exists:
                        msg = "Cannot find mirrored destination object {0}"
                        msg = msg.format(dstObj2)
                        logger.debug(msg)

        # Return the focus to the Maya window
        maya.cmds.setFocus("MayaWindow")

        if not foundObject:

            text = u"   加载数据时没有对象匹配.\n   打开调试模式以查看更多细节."

            raise mutils.NoMatchFoundError(text)

    def transferStatic(self, srcObj, dstObj, mirrorAxis=None, attrs=None, option=MirrorOption.Swap):
        """
        :type srcObj: str
        :type dstObj: str
        :type mirrorAxis: list[int]
        :type attrs: None | list[str]
        :type option: MirrorOption
        """
        srcValue = None
        dstValue = None
        srcValid = self.isValidMirror(srcObj, option)
        dstValid = self.isValidMirror(dstObj, option)

        if attrs is None:
            attrs = maya.cmds.listAttr(srcObj, keyable=True) or []

        for attr in attrs:
            dstAttr = dstObj + "." + attr
            srcAttr = srcObj + "." + attr
            if maya.cmds.objExists(dstAttr):
                if dstValid:
                    srcValue = maya.cmds.getAttr(srcAttr)

                if srcValid:
                    dstValue = maya.cmds.getAttr(dstAttr)

                if dstValid:
                    self.setAttr(dstObj, attr, srcValue, mirrorAxis=mirrorAxis)

                if srcValid:
                    self.setAttr(srcObj, attr, dstValue, mirrorAxis=mirrorAxis)
            else:
                logger.debug("Cannot find destination attribute %s" % dstAttr)

    def setAttr(self, name, attr, value, mirrorAxis=None):
        """
        :type name: str
        :type: attr: str
        :type: value: int | float
        :type mirrorAxis: Axis or None
        """
        if mirrorAxis is not None:
            value = self.formatValue(attr, value, mirrorAxis)
        try:
            maya.cmds.setAttr(name + "." + attr, value)
        except RuntimeError:
            msg = "Cannot mirror static attribute {name}.{attr}"
            msg = msg.format(name=name, attr=attr)
            logger.debug(msg)

    def transferAnimation(self, srcObj, dstObj, mirrorAxis=None, option=MirrorOption.Swap, time=None):
        """
        :type srcObj: str
        :type dstObj: str
        :type mirrorAxis: Axis or None
        """
        srcValid = self.isValidMirror(srcObj, option)
        dstValid = self.isValidMirror(dstObj, option)

        tmpObj, = maya.cmds.duplicate(srcObj, name='DELETE_ME', parentOnly=True)
        try:
            if dstValid:
                self._transferAnimation(srcObj, tmpObj, time=time)
            if srcValid:
                self._transferAnimation(dstObj, srcObj, mirrorAxis=mirrorAxis, time=time)
            if dstValid:
                self._transferAnimation(tmpObj, dstObj, mirrorAxis=mirrorAxis, time=time)
        finally:
            maya.cmds.delete(tmpObj)

    def _transferAnimation(self, srcObj, dstObj, attrs=None, mirrorAxis=None, time=None):
        """
        :type srcObj: str
        :type dstObj: str
        :type attrs: list[str]
        :type time: list[int]
        :type mirrorAxis: list[int]
        """
        maya.cmds.cutKey(dstObj, time=time or ())  # remove keys
        if maya.cmds.copyKey(srcObj, time=time or ()):
            if not time:
                maya.cmds.pasteKey(dstObj, option="replaceCompletely")
            else:
                maya.cmds.pasteKey(dstObj, time=time, option="replace")

        if attrs is None:
            attrs = maya.cmds.listAttr(srcObj, keyable=True) or []

        for attr in attrs:
            srcAttribute = mutils.Attribute(srcObj, attr)
            dstAttribute = mutils.Attribute(dstObj, attr)

            if dstAttribute.exists():
                if dstAttribute.isConnected():
                    if self.isAttrMirrored(attr, mirrorAxis):
                        maya.cmds.scaleKey(dstAttribute.name(), valueScale=-1, attribute=attr)
                else:
                    value = srcAttribute.value()
                    self.setAttr(dstObj, attr, value, mirrorAxis)

    def isValidMirror(self, obj, option):
        """
        :type obj: str
        :type option: MirrorOption
        :rtype: bool
        """
        if option == MirrorOption.Swap:
            return True

        elif option == MirrorOption.LeftToRight and self.isLeftSide(obj):
            return False

        elif option == MirrorOption.RightToLeft and self.isRightSide(obj):
            return False

        else:
            return True

    def isLeftSide(self, name):
        """
        Return True if the object contains the left side string.

        # Group|Character1:footRollExtra_L|Character1:footRoll_L
        # Group|footRollExtra_L|footRoll_LShape
        # footRoll_L
        # footRoll_LShape

        :type name: str
        :rtype: bool
        """
        side = self.leftSide()
        return self.matchSide(name, side)

    def isRightSide(self, name):
        """
        Return True if the object contains the right side string.

        # Group|Character1:footRollExtra_R|Character1:footRoll_R
        # Group|footRollExtra_R|footRoll_RShape
        # footRoll_R
        # footRoll_RShape

        :type name: str
        :rtype: bool
        """
        side = self.rightSide()
        return self.matchSide(name, side)

    def calculateMirrorAxis(self, srcObj):
        """
        :type srcObj: str
        :rtype: list[int]
        """
        result = [1, 1, 1]
        dstObj = self.mirrorObject(srcObj) or srcObj
        mirrorPlane = self.mirrorPlane()

        if dstObj == srcObj or not maya.cmds.objExists(dstObj):
            # The source object does not have an opposite side
            result = MirrorTable._calculateMirrorAxis(srcObj, mirrorPlane)
        else:
            # The source object does have an opposite side
            if MirrorTable.isAxisMirrored(srcObj, dstObj, [1, 0, 0], mirrorPlane):
                result[0] = -1

            if MirrorTable.isAxisMirrored(srcObj, dstObj, [0, 1, 0], mirrorPlane):
                result[1] = -1

            if MirrorTable.isAxisMirrored(srcObj, dstObj, [0, 0, 1], mirrorPlane):
                result[2] = -1

        return result
