# -*- coding: utf-8 -*-

from ..Hurricane  import DbU, DataBase
from ..helpers    import trace
from ..helpers.io import ErrorMessage as Error
            

class Rules ( object ):
    """
    The Rules object provides an easier access to the design rules stored
    in the Technology databse. Instead of having to perform a function call
    like:

    .. code:: Python

       tech  = DataBase.getDB().getTechnology()
       value = tech.getPhysicalRule( 'minEnclosure', 'pImplant', 'active' )

    We can write access the rule as an attribute of the ``rule`` object.

    .. code:: Python
   
       import oroshi

       value = oroshi.rules.minEnclosure_pImplant_active

    Only the rules defined in the Rules.ruleSet list will be loaded.
    """

    ruleSet = [ 'minSpacing_nWell'
              , 'minWidth_pImplant'
              , 'minSpacing_pImplant'
              , 'minSpacing_rpolyh_pImplant'
              , 'minEnclosure_pImplant_poly2con'
              , 'minEnclosure_nImplant_active'
              , 'minEnclosure_pImplant_active'
              , 'minSpacing_nImplant_pImplant'
              , 'minSpacing_cut0'
              , 'minWidth_cut0'
              , 'minWidth_active'
              , 'minEnclosure_active_cut0'
              , 'transistorMinL'
              , 'transistorMinW'
              , 'minSpacing_poly'
              , 'minGateSpacing_poly'
              , 'minSpacing_poly_active'
              , 'minExtension_active_poly'
              , 'minExtension_poly_active'
              , 'minEnclosure_poly_cut0'
              , 'minSpacing_cut0_poly'
              , 'minWidth_cut0'
              , 'minSpacing_cut0_active'
              , 'minWidth_metal1'
              , 'minSpacing_metal1'
              , 'minEnclosure_metal1_cut0'
              , 'minEnclosure_metal1_cut1'
              , 'minWidth_cut1'
              , 'minSpacing_cut1'
              , 'minWidth_metal2'
              , 'minSpacing_metal2'
              , 'minEnclosure_metal2_cut1'
              , 'minEnclosure_metal2_cut2'
              , 'minWidth_cut2'
              , 'minSpacing_cut2'
              , 'minWidth_cut1'
              , 'minSpacing_cut1'
              , 'minWidth_metal3'
              , 'minSpacing_metal3'
              , 'minSpacingWide1_metal3'
              , 'minEnclosure_metal3_cut2'
              , 'minSpacingOnMetBot_cut2'
              , 'minSpacingOnMetCap_cut2'
              , 'maxWidth_metcap'
              , 'minSpacing_metbot'
              , 'minSpacing_cut1_metcap'
              , 'minSpacing_cut2_metcap'
              , 'minEnclosure_metbot_metcap'
              , 'minEnclosure_metbot_cut1'
              , 'minEnclosure_metbot_cut2'
              , 'minEnclosure_metcap_cut2'
              , 'minWidth_metcap'
              , 'minWidth_metcapdum'
              , 'minWidth_cpoly'
              , 'minWidth_poly2'
              , 'minWidth_rpolyh'
              , 'minWidthHighPrec_rpolyh'
              , 'minSpacing_cpoly'
              , 'minSpacing_poly2'
              , 'minSpacing_rpolyh'
              , 'minSpacing_cut0_cpoly'
              , 'minSpacing_diff_poly2'
              , 'minSpacing_poly_poly2'
              , 'minEnclosure_poly_cpoly' 
              , 'minEnclosure_cpoly_cut0'
              , 'minEnclosure_poly2_cut0'
              , 'MIMCap'
              , 'PIPCap'
              , 'MIMPerimeterCap'
              , 'PIPPerimeterCap'
              , 'RPOLYHSheetRes'
              , 'RPOLY2PHSheetRes'
              , 'MET1RPOLYHContRes'
              , 'minWidth_hres'
              , 'minSpacing_hres'
              , 'minEnclosure_hres_poly2'
              , 'minSpacing_hres_poly1'
              , 'minSpacing_hres_poly2'
              , 'minSpacing_hres_active'
              , 'corrFactor90'
              , 'corrFactor135'
              , 'minRpolyhSquares'
              ]

    def __init__ ( self ):
        """
        Create an empty rule set. The rules must be loaded afterwards by calling
        ``Rules.load()``.
        """
        trace( 100, '\tRules.__init__()\n' )
        self.dtr = None

    def isLoaded ( self ):
        """Tells if the rules have already been loaded."""
        return self.dtr is not None

    def load ( self, dtr ):
        """
        Load the rule set from the technology into the Rules object.

        .. note:: The ``dtr`` parameter is just another name for the currently
                  used Hurricane::Technology.
        """
        trace( 100, '\tRules.load()\n' )
        if self.dtr:
            print( Error( 1, 'Rules.load(): Attempt to load rules from DTR a second time (ignored).' ))
            return
        self.dtr = dtr
        for rule in Rules.ruleSet: self.addAttr(rule)

    def getRealLayer ( self, stdName ):
        """
        Get a Layer object by it's name. The alias translation from generic
        names is used to return the real technology name.

        For example:

        ==================  ===================
        Generic Layer Name  SkyWater 130nm Name
        ==================  ===================
        nWell               nwm
        active              difftap
        pImplant            psdm
        cut0                licon
        metal1              li
        cut1                via
        metal2              metal1
        ==================  ===================
        """
        return self.dtr.getLayer( stdName )

    def attrTranslate ( self, attribute ):
        """
        Translate a rule complete name, given in ``attribute``, using the *generic*
        layer names into another string, using the target technology layer names.

        For example, for SkyWater 130nm: ::

            minEnclosure_pImplant_active => minSpacing_psdm_difftap
        """
        words       = attribute.split( '_' )
        translateds = [ words[0] ]
        for word in words[1:]:
            realLayer = self.getRealLayer( word )
            if realLayer is None:
                print( Error( 1, 'rules.attrTranslate(): Unable to translate generic layer "{}".' \
                                 .format( word )))
                realLayerName = word
            else:
                realLayerName = realLayer.getName()
            translateds.append( realLayerName )
        return '_'.join( translateds )
    
    def addAttr ( self, attribute ):
        """
        Add a new attribute into the dictionnary of rule set. The attribute fields,
        separated by '_' are broken down to get the corresponding rule in the
        technology and be set as value of said attribute.

        The attribute is the concatenation of the rule name and the various layers
        it applies on. For example: ::

            (minEnclosure, pImplant, active) => 'minEnclosure_pImplant_active'
        """
        techAttribute = self.attrTranslate( attribute )
        if attribute in self.__dict__: return
        
       #print( 'Rules.addAttr(): {} -> {}'.format( attribute, techAttribute ))
        value = None
        words = techAttribute.split( '_' )
        try:
            if len(words) == 1:
                if   words[0].endswith('Cap'     ): value = self.dtr.getUnitRule( words[0] ).getDoubleValue()
                elif words[0].endswith('ContRes' ): value = self.dtr.getUnitRule( words[0] ).getDoubleValue()
                elif words[0].endswith('Res'     ): value = self.dtr.getUnitRule( words[0] ).getDoubleValue()
                elif words[0].endswith('ctor90'  ): value = self.dtr.getUnitRule( words[0] ).getDoubleValue()
                elif words[0].endswith('ctor135' ): value = self.dtr.getUnitRule( words[0] ).getDoubleValue()
                elif words[0].endswith('quares'  ): value = self.dtr.getUnitRule( words[0] ).getDoubleValue()
            if (value is None) and len(words) < 4:
                rule = self.dtr.getPhysicalRule( *tuple(words) )
                if rule.isDouble():
                    value = rule.getDoubleValue()
                   #print( 'Accessed value (Unit):{}'.format(value) )
                else:
                    value = rule.getValue()
                   #print( 'Accessed value (DbU):{}'.format(DbU.getValueString(value)) )
        except Exception as e:
            print( e )
        
        if not value is None:
            self.__dict__[attribute] = value
        
        return