from maya import cmds

import vec
reload(vec)




class NBaseVec(object):
    """
    This is the abstract class for the vectors definition
    """
    def __init__(self, attribute_name, base_name="Vector"):
        """
        This is the constructor

        The constructor is in charge to store the input data and it will also
        automatically create a name generator used internally to name nodes

        Args:

        :attribute_name: str, the name of the attribute that will be used as 
                               vector source
        :base_name: str, the base name used for the generated nodes
        """

      #Store the data
        self.attribute_name= attribute_name
        self._generator = None

        #Initialize the internal generator
        self.init_name_generator(base_name)


    def init_name_generator(self, base_name):
        """Initialize the internal generator

        This function is in charge to create the name generator used
        for generating the names used for the nodes.

        Args:
        
        :base_name: str, the base name used to init the generator
        """
        self._generator = (base_name + "%03d" % i for i in range(1, 9999))

 
    def set_generator(self, generator):
        """
        Function that sets in the class the generator we wish to use

        Args:

        :generator: generator, existing generator for the names
        """
        self._generator = generator


    def connect_to(self, target_attr):
        """
        Simple function that connects the internal attribute 
        to the target attribute

        Args:

        :target_attr: str, the attribute to connect to
        """
        cmds.connectAttr(self.attribute_name , target_attr)


    def as_list(self):
        """
        This function returns the value of the vector as a list

        :return: list
        """
        attr = cmds.getAttr(self.attribute_name)
        if not isinstance(attr,list) :
            return [attr]
        else:
            if isinstance(attr[0], tuple):
                return list(attr[0])
            else:
                return attr


    def as_vec(self):
        """
        This function returns the value of the vector as a Vec class

        :return: Vec instance
        """
        return vec.Vec(self.as_list())


    def _generate_node(self, node_type, suffix):
        """
        Generic function for generating nodes named properly

        Args:
        :node_type: str, the type of the node we want to create
        :suffix: str, the suffix for the node
        """
        return cmds.createNode(node_type, 
                n = self._generator.next()+ "_" + suffix)


class NScalar(NBaseVec):
    """
    This class represents a single float number used for vector operation, 
    in pratice it reflects a VEC1 (vec of length one) that is needed in 
    some vector operation, like scalar value operation
    """
    

    def __init__(self, attribute_name, base_name="Vector"):
        """
        This is the constructor

        Args:

        :attribute_name: the name of the attribute that will be used as 
                               vector source
        :base_name: the base name used for the generated nodes
        """ 

        NBaseVec.__init__(self,attribute_name,base_name="Vector")


    @classmethod
    def with_generator(cls, attribute_name, generator):
        """Alternative constructor from an attribute and an existing
        generator

        Args:

        :attribute_name: str, the name of the attribute that will be used as 
                               vector source
        :generator: generator, existing generator for the names
        :return: nVec instance
        """
        instance = cls(attribute_name)
        instance.set_generator(generator)
        return instance


    @classmethod
    def from_value(cls, value, base_name):
        """
        Generating a scalar vector from a value

        This function generates a node and a channel used to host the value
        and attach the channel to a NScalar vector class

        Args:

        :value: float,int, the value of the NScalar
        :base_name: str, the name we will use for the node + "_vec", the attribute name will
                        be generated with base_name + "_from_value"
        """

        node = cmds.createNode("transform", n= base_name + '_vec')
        attr_name = base_name + "_from_value"
        cmds.addAttr(node, ln = attr_name, at="float",
                        k=1)
        cmds.setAttr(node + '.' + attr_name, value)

        return cls(node + '.' + attr_name , base_name)


    def scalar_dynamic(self, scalB):
        """
        The dynamic mult for the NScalar class

        By dynamic I mean that the scalB value is going to be driven by a connection,
        thus needs to be a NScalar instance.

        :scalB: NScalar, the scalar to be multiplied by
        :return: NScalar instance
        """

        md = self._generate_node("multDoubleLinear", "mul")
        cmds.connectAttr(self.attribute_name, md + '.input1')
        cmds.connectAttr(scalB.attribute_name, md + '.input2')

        return NScalar.with_generator(md + ".output", self._generator)

    def scalar_static(self, value):
        """
        The static mult for the NScalar class

        By static  I mean that the scalB value is going to hardcoded in the 
        node performing the operation, which means it wont change over time

        :value: int,float, the value to be multiplied by
        :return: NScalar instance
        """

        md = self._generate_node("multDoubleLinear", "mul")
        cmds.connectAttr(self.attribute_name, md + '.input1')
        cmds.setAttr(md + '.input2',value)

        return NScalar.with_generator(md + ".output", self._generator)

    def division_dynamic(self, scalB):
        """
        The dynamic divison for the NScalar class

        By dynamic I mean that the scalB value is going to be driven by a connection,
        thus needs to be a NScalar instance.

        :scalB: NScalar, the scalar to be divided by
        :return: NScalar instance
        """

        md = self._generate_node("multiplyDivide", "div")
        cmds.connectAttr(self.attribute_name, md + '.input1X')
        cmds.connectAttr(scalB.attribute_name, md + '.input2X')
        cmds.setAttr(md + '.operation',2)

        return NScalar.with_generator(md + ".outputX", self._generator)


    def division_static(self,value):
        """
        The static divison for the NScalar class

        By static I mean that the scalB value is going to be static and hardcoded,
        in the node. Meant for value not changing over time

        Args:

        :value: float,int, the value needed for the operation
        :return: NScalar instance
        """

        md = self._generate_node("multiplyDivide", "div")
        cmds.connectAttr(self.attribute_name, md + '.input1X')
        cmds.setAttr(md + '.input2X', value)
        cmds.setAttr(md + '.operation',2)

        return NScalar.with_generator(md + ".outputX", self._generator)


    def blend(self, scalB, drv):
        """
        Blending function of two scalars

        This function is used to blend two different scalar where the current scalar
        will be at the 0 size of the blend , instead the provided scalar will be to the 1 side
        of the blend

        Args:

        :scalB: NScalar, the instance of the scalar to blend with
        :drv: NScalar, an instance of scalar used to drive the blend
        """
        
        bln = self._generate_node("blendColors", "blen")
        cmds.connectAttr(scalB.attribute_name, bln + '.color1R')
        cmds.connectAttr(self.attribute_name, bln + '.color2R')
        cmds.connectAttr(drv.attribute_name , bln + '.blender')

        return NScalar.with_generator(bln + ".outputR", self._generator)

    def __add__(self,scalB):
        """
        addition operator for NScalar class

        Args:
        
        :scalB: NScalar, the second class for the operation
        :return: NScalar instance
        """        
        pma = cmds.createNode("plusMinusAverage", n = self._generator.next()+ "_sub")
        cmds.connectAttr(scalB.attribute_name, pma + '.input1D[0]')
        cmds.connectAttr(self.attribute_name, pma + '.input1D[1]')

        return NScalar.with_generator(pma+ '.output1D', self._generator)

    def __sub__(self,scalB):
        """
        subtraciton operator for NScalar class

        Args:
        
        :scalB: NScalar, the second class for the operation
        :return: NScalar instance
        """        
        pma = cmds.createNode("plusMinusAverage", n = self._generator.next()+ "_sub")
        cmds.setAttr(pma + '.operation',2)
        cmds.connectAttr(scalB.attribute_name, pma + '.input1D[0]')
        cmds.connectAttr(self.attribute_name, pma + '.input1D[1]')

        return NScalar.with_generator(pma+ '.output1D', self._generator)

    def __mul__( self, scalB):
        """
        The mult operator for scalars

        :scalB: NScalar, the scalar to be multiplied with
        :return: NScalar instance
        """

        return self.scalar_dynamic(scalB)

    def __div__(self, scalB):
        """
        The division operator for scalars

        :scalB: NScalar, the scalar to be divided by
        :return: NScalar instance
        """

        return self.division_dynamic(scalB)

    def __rmul__(self, value):
        """
        Right multiplication operator for NScalar class

        This function gets called in the case we get a float,int multiplied
        by a NScalar instance and it performs a scalar_static operation

        Args:
        
        :value: float,int, the value to scale the vector by
        :return: NVec instance
        """   
        return self.scalar_static(value)

    def __rdiv__(self, value):
        """
        Right division operator for NScalar class

        This function gets called in the case we get a float,int divided
        by a NScalar instance and it performs a scalar_static operation

        Args:
        
        :value: float,int, the value to divide the vector by
        :return: NVec instance
        """   
        return self.division_static(value)


class NVec(NBaseVec):
    """
    This class lets easily perform vector operation as node based 
    maya operation
    """
    
    def __init__(self, attribute_name, base_name="Vector"):
        """This is the constructor

        Args:

        :attribute_name: str, the name of the attribute that will be used as \
        vector source
        :base_name: the base name used for the generated nodes
        """

        NBaseVec.__init__(self,attribute_name,base_name="Vector")


    @classmethod
    def with_generator(cls, attribute_name, generator):
        """Alternative constructor from an attribute and an existing
        generator

        Args:

        :attribute_name: str, the name of the attribute that will be used as 
                               vector source
        :generator: generator, existing generator for the names
        :return: nVec instance
        """
        instance = cls(attribute_name)
        instance.set_generator(generator)
        return instance

    
    def length(self):
        """
        This function computes the length of the vector

        :return: NVec instance
        """

        dist = cmds.createNode("distanceBetween", n= self._generator.next()+"_length")
        cmds.connectAttr(self.attribute_name, dist + '.point2')

        return NScalar.with_generator(dist+ '.distance', self._generator)

    
    def normalize(self):
        """
        This function normalize the vector

        :return: NVec instance
        """

        norm = cmds.createNode("vectorProduct", n = self._generator.next()+ "_norm")
        cmds.connectAttr(self.attribute_name, norm + '.input1')
        cmds.setAttr(norm + '.operation', 0)
        cmds.setAttr(norm + '.normalizeOutput',1)
        return NVec.with_generator(norm+ '.output', self._generator)

    def scalar_dynamic (self, scal):
        '''
        This function performs a scalar multiplication

        This function is called dynamic because it uses a connection to get
        the value for the multiplication, in short it is a NScalar instance (aka a vec1)
        that gets used three times to multiply the current vector

        :scal: NScalar, a scalar class instance
        :return: NVec instance
        ''' 

        assert type(scal) == NScalar, "__sub__ ERROR: input parameter needs to be of type NScalar"

        mult = cmds.createNode("multiplyDivide", n=  self._generator.next()+ '_scalarDyn')
        cmds.connectAttr(scal.attribute_name , mult + '.input2X')
        cmds.connectAttr(scal.attribute_name , mult + '.input2Y')
        cmds.connectAttr(scal.attribute_name , mult + '.input2Z')
        cmds.connectAttr(self.attribute_name, mult + '.input1')
        return NVec.with_generator(mult+ '.output', self._generator)

    def scalar_static(self, value):
        """
        This is a static scalar multiplication of the vector

        By static it means it multiplies by a fixed value which is not dynamic 
        like an attribute connection

        Args:

        :value: float,int, the value for which we wist to scale the vector for
        :return: NVec instance
        """

        mult = cmds.createNode("multiplyDivide", n=  self._generator.next()+ '_scalarStatic')
        cmds.setAttr(mult + '.input2X',value)
        cmds.setAttr(mult + '.input2Y',value)
        cmds.setAttr(mult + '.input2Z',value)
        cmds.connectAttr(self.attribute_name, mult + '.input1')
        return NVec.with_generator(mult+ '.output', self._generator)

    def dot(self, vecB):
        """
        This function performs a dot product

        Args:

        :vecB: NVec, the second class for the operation
        :return: NVec instance
        """ 
        assert type(vecB) == NVec, "__sub__ ERROR: input parameter needs to be of type NVec"

        vecProd = cmds.createNode("vectorProduct", n = self._generator.next()+ "_dot")
        cmds.connectAttr(self.attribute_name , vecProd + '.input1')
        cmds.connectAttr(vecB.attribute_name , vecProd + '.input2')

        return NVec.with_generator(vecProd+ '.output', self._generator)

    def cross(self,vecB):
        """
        Cross product function 

        Args:

        :vecB: NVec, the second class for the operation
        :return: NVec instance
        """
        assert type(vecB) == NVec, "__sub__ ERROR: input parameter needs to be of type NVec"

        vecProd = cmds.createNode("vectorProduct", n = self._generator.next()+ "_cross")
        cmds.setAttr(vecProd + '.operation', 2)
        cmds.connectAttr(self.attribute_name , vecProd + '.input1')
        cmds.connectAttr(vecB.attribute_name , vecProd + '.input2')

        return NVec.with_generator(vecProd+ '.output', self._generator)

    def __len__(self):
        """
        Length operator for the nVec class

        :return: NVec instance
        """

        return self.length()


    def __sub__(self,vecB):
        """
        Subtraction operator for NVec

        Args:

        :vecB: NVec, the second class for the operation
        :return: NVec instance
        """
        assert type(vecB) == NVec, "__sub__ ERROR: input parameter needs to be of type NVec"
        pma = cmds.createNode("plusMinusAverage", n = self._generator.next()+ "_sub")
        cmds.connectAttr(vecB.attribute_name, pma + '.input3D[0]')
        cmds.connectAttr(self.attribute_name, pma + '.input3D[1]')
        cmds.setAttr(pma + '.operation',2)

        return NVec.with_generator(pma+ '.output3D', self._generator)


    def __xor__(self, vecB):
        """
        Cross product operator

        Args:

        :vecB: NVec, the second class for the operation
        :return: NVec instance
        """
        return self.cross(vecB)

    def __add__(self, vecB):
        """
        addition operator for NVec class

        Args:
        
        :vecB: NVec, the second class for the operation
        :return: NVec instance
        """        
        pma = cmds.createNode("plusMinusAverage", n = self._generator.next()+ "_sub")
        cmds.connectAttr(vecB.attribute_name, pma + '.input3D[0]')
        cmds.connectAttr(self.attribute_name, pma + '.input3D[1]')

        return NVec.with_generator(pma+ '.output3D', self._generator)

    def __neg__(self):
        """
        Negation operator for the NVec class
        """
        return self.scalar_static(-1.0)

    def __mul__(self, vecB):
        """
        multiplication operator for NVec class

        Args:
        
        :vecB: NVec, the second class for the operation
        :return: NVec instance
        """        

        return self.dot(vecB)

    def __rmul__(self,value):
        """
        Right multiplication operator for NVec class

        This function gets called in the case we get a float,int multiplied
        by a NVec instance and it performs a static_scalar operation

        Args:
        
        :value: float,int, the value to scale the vector by
        :return: NVec instance
        """   
        return self.scalar_static(value)



"""
#perpendicular
import sys
sys.path.append("/user_data/WORK_IN_PROGRESS/mVec/")

import nVec
reload(nVec)

locBase = "loc_base.worldPosition"
locA = "locA_tip.worldPosition"
locB = "locB_tip.worldPosition"
target = "target_loc.t"
scalar = "scalar_loc.ty"

vecBase = nVec.NVec(locBase,"test")
vecA = nVec.NVec(locA,"test")
vecB = nVec.NVec(locB,"test")
scalV = nVec.NVec(scalar,"scal")
vec1 = vecA - vecBase
vec2 = vecB - vecBase

cross = vec1 ^ vec2
norm = cross.normalize()
final = vecBase + (2*(-norm).scalar_dynamic(scalV))
final.connect_to(target)

"""


"""
import sys
sys.path.append("/user_data/WORK_IN_PROGRESS/mVec/")
from maya import cmds

import nVec
reload(nVec)


locBase = "loc_base.worldPosition"
locA = "locA_tip.worldPosition"
locB = "locB_tip.worldPosition"
target = "target_loc.t"
scalar = "scalar_loc.ty"

vecBase = nVec.NVec(locBase,"test")
vecA = nVec.NVec(locA,"test")
vecB = nVec.NVec(locB,"test")
scalV = nVec.NScalar(scalar,"scal")
vec1 = vecA - vecBase
vec2 = vecB - vecBase

cross = vec1 ^ vec2
norm = cross.normalize()
final = vecBase + (2*(-norm).scalar_dynamic(scalV))
final.connect_to(target)
print final.as_list()

import vec
reload(vec)

locBaseL = cmds.getAttr("loc_base.worldPosition")[0]
locAL = cmds.getAttr("locA_tip.worldPosition")[0]
locBL = cmds.getAttr("locB_tip.worldPosition")[0]
scalV = cmds.getAttr("scalar_loc.ty")
vecBase = vec.Vec(locBaseL)
vecA = vec.Vec(locAL)
vecB = vec.Vec(locBL)

vec1 = vecA - vecBase
vec2 = vecB - vecBase

cross = vec1 ^ vec2
norm = cross.normalize()
final = vecBase + (scalV*2*(-norm))
print final.as_list()

"""


"""
#basic stretch IK

from maya import cmds
import nVec
reload(nVec)

#declaring initial vectors
startV = nVec.NVec("start_drv.worldPosition", "sStretch")
endV = nVec.NVec("end_drv.worldPosition", "eStretch")
poleV = nVec.NVec("poleVec_drv.worldPosition", "pStretch")
stretchV= nVec.NScalar("end_drv.stretch","stretch")
lockV= nVec.NScalar("end_drv.lock","lock")

#computing the length between the end and the start of the chain
distV = endV - startV
length = distV.length()

#getting initial chain length and converting into vectors
upLen = cmds.getAttr(chain[1] + '.tx')
lowLen = cmds.getAttr(chain[2] + '.tx')
upLenV = nVec.NScalar.from_value(upLen, "upLen")
lowLenV = nVec.NScalar.from_value(lowLen, "lowLen")

#getting total length chain (this can be easily multiplied by the global scale)
initLen = upLenV+lowLenV

#finding theratio
ratio = length /initLen

#calculating scaled length
scaledUp = upLenV * ratio
scaledlow = lowLenV * ratio

#computing final blended stretch
finalScaledUp = upLenV.blend(scaledUp, stretchV)
finalScaledLow = lowLenV.blend(scaledlow,stretchV)

#condition node (old school)
cnd = cmds.createNode("condition")
ratio.connect_to(cnd + '.firstTerm')
cmds.setAttr(cnd + '.secondTerm' ,1)
cmds.setAttr(cnd + '.operation', 3)

#connecting our final calculaded stretch node to the cnd colors
finalScaledUp.connect_to(cnd + '.colorIfTrueR')
upLenV.connect_to(cnd + '.colorIfFalseR')
finalScaledLow.connect_to(cnd + '.colorIfTrueG')
lowLenV.connect_to(cnd + '.colorIfFalseG')

cmds.connectAttr(cnd + '.outColorR', chain[1] + '.tx')
cmds.connectAttr(cnd + '.outColorG', chain[2] + '.tx')



"""
"""
#ADVANCED STRETCH
from maya import cmds
import nVec
reload(nVec)

#declaring initial vectors
startV = nVec.NVec("start_drv.worldPosition", "sStretch")
endV = nVec.NVec("end_drv.worldPosition", "eStretch")
poleV = nVec.NVec("poleVec_drv.worldPosition", "pStretch")
stretchV= nVec.NScalar("end_drv.stretch","stretch")
lockV= nVec.NScalar("end_drv.lock","lock")

#computing the length between the end and the start of the chain
distV = endV - startV
length = distV.length()

#getting initial chain length and converting into vectors
upLen = cmds.getAttr(chain[1] + '.tx')
lowLen = cmds.getAttr(chain[2] + '.tx')
upLenV = nVec.NScalar.from_value(upLen, "upLen")
lowLenV = nVec.NScalar.from_value(lowLen, "lowLen")

#getting total length chain (this can be easily multiplied by the global scale)
initLen = upLenV+lowLenV

#finding theratio
ratio = length /initLen

#calculating scaled length
scaledUp = upLenV * ratio
scaledlow = lowLenV * ratio

#computing final blended stretch
finalScaledUp = upLenV.blend(scaledUp, stretchV)
finalScaledLow = lowLenV.blend(scaledlow,stretchV)

#condition node (old school)
cnd = cmds.createNode("condition")
ratio.connect_to(cnd + '.firstTerm')
cmds.setAttr(cnd + '.secondTerm' ,1)
cmds.setAttr(cnd + '.operation', 3)

#connecting our final calculaded stretch node to the cnd colors
finalScaledUp.connect_to(cnd + '.colorIfTrueR')
upLenV.connect_to(cnd + '.colorIfFalseR')
finalScaledLow.connect_to(cnd + '.colorIfTrueG')
lowLenV.connect_to(cnd + '.colorIfFalseG')

#now compute the pole vector lock
#get polevec vectors
upPoleVec = poleV - startV
lowPoleVec = poleV - endV

#computing the length
upPoleLen = upPoleVec.length()
lowPoleLen= lowPoleVec.length()

#blending default length with poleVec vectors
upPoleBlen = upLenV.blend(upPoleLen, lockV)
lowPoleBlen = lowLenV.blend(lowPoleLen, lockV)

#connecting a NScalar to the output of the node
finalStrUp = nVec.NScalar(cnd + '.outColorR')
finalStrLow = nVec.NScalar(cnd + '.outColorG')

#blending the stretch and lock lengths
resUp = finalStrUp.blend(upPoleBlen,lockV)
resLow =finalStrLow.blend(lowPoleBlen,lockV)

#connect final result
resUp.connect_to(chain[1] + '.tx')
resLow.connect_to(chain[2] + '.tx')

"""