"""
In this code sample, we'll convert the functions we created earlier to make a gear, into a class.
A class is a python object that lets you contain functions that relate to a specific object easily

It will be a little different then the one in the video, because I've added a few more methods, but it should be easy to follow along.
"""
import maya.cmds as cmds

# The class keyword creates a class, ours will be called Gear
# We will base our class on the python 'object'
# Object is the base for everything in python, and by basing off of the python 'object' we get all of its
#       attributes for free
class Gear(object):
    """
    Classes can have docstrings too that will describe how to use it

    In this case you would use it like this

    # This creates an instance of the class.
    # Classes descrive an object, instances are the objects that they describe
    # For example an Animal class describes an animal, but a dog on the street would be an instance of an Animal
    gearA = Gear()

    gearA.create(teeth=20, length=0.2)
    gearA.changeTeeth(teeth=10, length=0.5)
    """
    # The __init__ function is something that classes use a lot
    # They get run whenever you initialize a new instance of a class
    # For example when you do this gearA = Gear() it will run the __init__
    # You can think of them as the entryway to a class that tells it how to set up
    #
    # Most functions inside a class will take a first parameter called self that tells it to refer to itself.
    # Kind of like how you need to know you are yourself, the self parameter tells an instance it is itself
    def __init__(self):
        # We will just use this __init__ to create placeholder variables on the class
        # Variables that start with self are set on the instance and can be accessed outside this function
        self.shape = None
        self.transform = None
        self.constructor = None
        self.extrude = None

    # Another thing, functions inside a class are called methods
    def create(self, teeth=10, length=0.3):
        # The logic here is the same as in the functional version of this
        spans = teeth * 2

        # We refer to the createPipe method with self because we want to know to call the method that is inside this class
        # Notice we aren't getting a variable back from this method
        # Because we will store the variables on the class, we don't have to pass around them from a return (though we can if we choose to)
        self.createPipe(spans)

        # Similarly we call self.makeTeeth which is a method inside this class
        self.makeTeeth(teeth=teeth, length=length)

    def createPipe(self, spans):
        # We set the transform and shape to the class variables
        self.transform, self.shape = cmds.polyPipe(subdivisionsAxis=spans)

        # I didn't like having to find the constructor from the extrude node
        # Lets just find it now and save it to the class because it won't change
        for node in cmds.listConnections('%s.inMesh' % self.transform):
            if cmds.objectType(node) == 'polyPipe':
                self.constructor = node
                break

    def makeTeeth(self, teeth=10, length=0.3):
        # The logic here is exactly the same as in the makeTeeth function we created
        cmds.select(clear=True)
        faces = self.getTeethFaces(teeth)
        for face in faces:
            cmds.select('%s.%s' % (self.transform, face), add=True)

        # Instead of returning a value, lets just store the extrude node onto the class as a class variable
        self.extrude = cmds.polyExtrudeFacet(localTranslateZ=length)[0]
        cmds.select(clear=True)

    def changeLength(self, length=0.3):
        # Because we stored the extrude node on the class, we can just get it directly
        # This way we don't need to be told what extrude node to change
        cmds.polyExtrudeFacet(self.extrude, edit=True, ltz=length)

    def changeTeeth(self, teeth=10, length=0.3):
        # we know what node the constructor is, so we can refer to it directly
        cmds.polyPipe(self.constructor, edit=True, sa=teeth * 2)
        # Then we can just call the makeTeeth directly
        self.modifyExtrude(teeth=teeth, length=length)

    def getTeethFaces(self, teeth):
        spans = teeth * 2
        sideFaces = range(spans * 2, spans * 3, 2)

        faces = []
        for face in sideFaces:
            # Similar to what we did earlier, but using %d instead of %s
            # In reality it doesn't matter, but here it means it will only accept a number
            faces.append('f[%d]' % face)
        return faces


    def modifyExtrude(self, teeth=10, length=0.3):
        faces = self.getTeethFaces(teeth)

        # The extrude node has an attribute called inputComponents
        # To change it we can use a simple setAttr call instead of recreating the extrude which can be expensive
        # The arguments to changing a list of components is slightly different than a simple setAttr
        # it is:
        #   cmds.setAttr('extrudeNode.inputComponents', numberOfItems, item1, item2, item3, type='componentList')
        cmds.setAttr('%s.inputComponents' % self.extrude, len(faces), *faces, type='componentList')

        # The *faces will be new to you.
        # It basically means to expand a list in place for arguments
        # so if the list has ['f[1]', 'f[2]'] etc, it will be expanded in the arguments to be like this
        # cmds.setAttr('extrudeNode.inputComponents', 2, 'f[1]', 'f[2]', type='componentList'

        # Finally we modify the length
        self.changeLength(length)