text: ################################################################
  #       DNA_part: a component-like intermediate class necessary for DNA_construct
  #       Author: Andrey Shur
  #       Latest update: 6/4/2020
  #
  #
  ################################################################
  
  
  # Copyright (c) 2020, Build-A-Cell. All rights reserved.
  # See LICENSE file in the project root directory for details.
  
  from .component import Component
  from .polymer import OrderedMonomer, OrderedPolymer
  from .species import Species
  
  
  class DNA_part(Component, OrderedMonomer):
      def __init__(self,name,mechanisms=None,parameters=None,**keywords):
          """this represents a modular component sequence. These get compiled into working components"""
          Component.__init__(self=self, name = name, mechanisms = mechanisms,
                             parameters = parameters, **keywords)
          
          self.name = name
          #self.assembly = property(self._get_assembly,self._set_assembly)
          assembly = None #this is already covered in self.assembly. Is this needed?
          direction = None #orientation
          self.color = None #this will be taken from higher up
          self.color2 = None #this will be taken from higher up and only defined for attL/R sites
          pos = None #position in the dna_construct
          self.sequence = None #nucleotide sequence
          self.no_stop_codons = [] #some parts will set this, others won't. Default is that it has stop codons
          
          for key, value in keywords.items():
              #goes through any extra parameters and sets them!
              if(key=="assembly"):
                  assembly = value
              elif(key=="direction"):
                  direction = value
              elif(key=="color"):
                  self.color = value
              elif(key=="color2"):
                  self.color2 = value
              elif(key=="pos"):
                  pos = value
              elif(key=="sequence"):
                  self.sequence = value
              elif(key=="no_stop_codons"):
                  if(value is not None):
                      self.no_stop_codons = value
          if(isinstance(assembly,OrderedPolymer)):
              OrderedMonomer.__init__(self,position=pos,parent=assembly,direction=direction)
          else:
              self.assembly = assembly
              OrderedMonomer.__init__(self)
      @property
      def dna_species(self):
          return Species(self.name, material_type="dna")
      def __repr__(self):
          myname = self.name
          if(self.position is not None):
              myname+="_"+str(self.position)
          if(self.direction =="reverse"):
              myname += "_r"
          return myname
      def __hash__(self):
          hval = 0
          if(hasattr(self,"assembly")):
              if(self.assembly is None):
                  hval += hash(None)
              else:
                  hval += hash(str(self.assembly))
          hval += hash(self.name)
          if(self.parent is not None):
              hval+= hash(str(self.parent))
          hval+= hash(self.position)
          hval += hash(self.direction)
          return hval
      def __eq__(self,other):
          if(type(other)==type(self)):
              if(self.name==other.name):
                  if(self.assembly is not None and other.assembly is not None):
                      if(str(self.assembly)==str(other.assembly)):
                          return True
                  elif((self.assembly is not None) or (other.assembly is not None)):
                      #if one has an assembly and the other doesn't, then they aren't the same!!
                      return False
                  elif(((self.parent is None) and (other.parent is None)) or \
                      ((self.parent is not None) and (other.parent is not None) and (str(self.parent)==str(other.parent)))):
                      #this is for when we are using the OrderedMonomer for its intended function
                      if(self.direction==other.direction and self.position==other.position):
                          return True
          return False
  
      def clone(self,position,direction,parent_dna):
          """this defines where the part is in what piece of DNA"""
          #TODO add warning if DNA_part is not cloned
          self.insert(parent_dna,position,direction)
          #if(self.assembly is not None):
          #    warn(str(self) + " already belongs to "+str(self.assembly.name)+"! It will now be part of the new assembly")
          #    self.unclone()
  
          #self.pos = position
          #self.direction = direction
          #self.assembly = parent_dna
          return self
      def unclone(self):
          """removes the current part from anything"""
          self.remove()
          #if(self.assembly is not None):
          #    rightparts = self.assembly.parts_list[self.pos+1:]
          #    for part in rightparts:
          #        part.pos -= 1
          #    self.assembly.parts_list = self.assembly.parts_list[:self.pos]+rightparts
          #self.pos = None
          #self.direction = None
          #self.assembly = None
          return self
      def reverse(self):
          OrderedMonomer.reverse(self)
          #if(self.direction=="forward"):
          #    self.direction = "reverse"
          #elif(self.direction=="reverse"):
          #    self.direction = "forward"
          #elif(self.direction==None):
          #    warn(str(self)+" has no direction. Perhaps it wasn't cloned?")
          #else:
          #    raise ValueError("direction is not forward or reverse! It's "+self.direction)
          return self
  