# Note: Each subclass's attr field is given in canonical/hierarchical
# adjective order. See
# https://dictionary.cambridge.org/us/grammar/british-grammar/adjectives-order

# At the 50% threshold, only color attributes have multiple
# matches. Since there are no guarantees this exclusion of other
# attributes would hold at lower thresholds, the code applies the
# possibility to all attributes. It inserts "and" at the conclusion of
# such a multi-match sequence, even if there are no commas to
# distinguish/group as a sequence. Most of the time this will work
# because color is last in the ordering (except for left/right
# directions).

class Part():
    cub = None # Singleton object, shared by all derivative parts

    def describe(self,nab_id):
        # Get all the adjectives

        descr= '' 
         
        cub_id = self.cub.get_id(nab_id)

        if cub_id is None: # Without a matching CUB id, there are no part  
            return descr
        
        for name in self.attrs: # i.e., length, shape, color
            vals = self.cub.attribute_values(cub_id,name) # i.e., ['blue', 'red']
            if len(vals) == 0:
                continue # No matching attribute annotation found
            if len(vals) == 1:
                descr += ' ' + vals[0] # Append description of single value
            else:
                # Append descriptions of all values with an "and" before the last
                descr += ' ' + ' '.join(vals[0:-1])
                descr += ' and ' + vals[-1]
                
        return descr.strip() # Leading space


    def description(self,nab_id):
    # Get all the adjectives plus the noun
        descr = self.describe(nab_id)
        if len(descr)>0:
            return descr + ' ' + self.name
        else:
            return self.name


class OrientedPart(Part):
    def __init__(self,sagittal_dir):
        super().__init__()
        if not (sagittal_dir in ['left','right']):
            raise ValueError('sagittal_dir must be "left" or "right"')
        self.sagittal_dir = sagittal_dir

    def describe(self,nab_id):
        # Appends direction to description
        
        descr = super().describe(nab_id)
        if len(descr)>0: # suggestions on cleaner/idiomatic ways to do 
            descr += ' ' # conditional space insertion welcome
        descr += self.sagittal_dir
        return descr
    
    
class Bill(Part):
    attrs = ['has_bill_length','has_bill_shape','has_bill_color'] # 2,4,6
    name = 'bill'
    
class Crown(Part):
    attrs = ['has_crown_color']
    name = 'crown'
    
class Nape(Part):
    attrs = ['has_nape_color']
    name = 'nape'

class Eye(OrientedPart):
    attrs = ['has_eye_color']
    name = 'eye'
    

class Belly(Part):
    attrs = ['has_belly_pattern', 'has_belly_color'] # 3,6
    name = 'belly'
    
class Breast(Part):
    attrs = ['has_breast_pattern', 'has_breast_color'] # 3,6
    name = 'breast'
    
class Back(Part):
    attrs = ['has_back_pattern', 'has_back_color'] # 3,6
    name = 'back'
    
class Tail(Part):
    attrs = ['has_tail_pattern', 'has_tail_shape', 'has_upper_tail_color',
                 'has_under_tail_color'] # 3, 4, 6, 6
    name = 'tail'
    
class Wing(OrientedPart):
    attrs = ['has_wing_pattern','has_wing_shape','has_wing_color'] # 3, 4, 6
    name = 'wing'
    
        
class NABParts():
    # Parts listed in NABird part_id order-- [0,10] -- cf. NABirds/parts/parts.txt
    parts = [ Bill(),
              Crown(),
              Nape(),
              Eye('left'),
              Eye('right'),
              Belly(),
              Breast(),
              Back(),
              Tail(),
              Wing('left'),
              Wing('right') ]
