# -*- coding: utf-8 -*-
from decimal import Decimal
from string import Template
import DbQuery

systemName = 'Osric'
subSystemName = 'OSRIC'
hasSeparateRacesAndClasses = True
attributes = [('STR', 'Strength', '3d6'),
              ('INT', 'Intelligence', '3d6'),
              ('WIS', 'Wisdom', '3d6'),
              ('DEX', 'Dexderity', '3d6'),
              ('CON', 'Constitution', '3d6'),
              ('CHA', 'Charisma', '3d6'),
#              ('COM','Comliness', '3d6'),
]
life = ['HP',]
alignment = ['Lawful Good', 'Neutral Good', 'Chaotic Good', 'Lawful Neutral', 'True Neutral', 'Chaotic Neutral', 'Lawful Evil', 'Neutral Evil', 'Chaotic Evil']
gender = ['Male','Female','NA']
economy = {
'gp':1,
'pp':5,
'ep':.5,
'sp':.1,
'cp':.01
}
restrictive_races = ['dwarf', 'half_orc']

bonuses = {
    'STR': [(3, '-3', '-1', '-35', '1', '0'),
            (5, '-2', '-1', '-25', '1', '0'),
            (7, '-1', '0', '-15', '1', '0'),
            (9, '0', '0', '0', '1-2', '1'),
            (11, '0', '0', '0', '1-2', '2'),
            (13, '0', '0', '+10', '1-2', '4'),
            (15, '0', '0', '+20', '1-2', '7'),
            (16, '0', '+1', '+35', '1-3', '10'),
            (17, '+1', '+1', '+50', '1-3', '13'),
            (18, '+1', '+2', '+75', '1-3', '16'),
            (18.5, '+1', '+3', '+100', '1-3', '20'),
            (18.75, '+2', '+3', '+125', '1-4', '25'),
            (18.9, '+2', '+4', '+150', '1-4', '30'),
            (18.99, '+2', '+5', '+200', '1-4(1 in 6)', '35'),
            (19, '+3', '+6', '+300', '1-5(1 in 6)', '40'),],
    'INT': [(3, '0', '-', '-'),
            (4, '0', '-', '-'),
            (5, '0', '-', '-'),
            (6, '0', '-', '-'),
            (7, '0', '-', '-'),
            (8, '1', '-', '-'),
            (9, '1', '35', '4/6'),
            (10, '2', '45', '5/7'),
            (11, '2', '45', '5/7'),
            (12, '3', '45', '5/7'),
            (13, '3', '55', '6/9'),
            (14, '4', '55', '6/9'),
            (15, '4', '65', '7/11'),
            (16, '5', '65', '7/11'),
            (17, '6', '75', '8/14'),
            (18, '7', '85', '9/18'),
            (19, '8', '90', '10/22'),],
    'WIS': [(3, '-3', '0', '45'),
            (4, '-2', '0', '40'),
            (5, '-1', '0', '35'),
            (6, '-1', '0', '30'),
            (7, '-1', '0', '25'),
            (8, '0', '0', '20'),
            (9, '0', '0', '15'),
            (10, '0', '0', '10'),
            (11, '0', '0', '5'),
            (12, '0', '0', '1'),
            (13, '0', '1', '0'),
            (14, '0', '2', '0'),
            (15, '+1', '2/1', '0'),
            (16, '+2', '2/2', '0'),
            (17, '+3', '2/2/1', '0'),
            (18, '+4', '2/2/1/1', '0'),
            (19, '+5', '3/2/1/1', '0'),],
    'DEX': [(3, '-3', '-3', '+4'),
            (4, '-2', '-2', '+3'),
            (5, '-1', '-1', '+2'),
            (6, '0', '0', '+1'),
            (7, '0', '0', '0'),
            (8, '0', '0', '0'),
            (9, '0', '0', '0'),
            (10, '0', '0', '0'),
            (11, '0', '0', '0'),
            (12, '0', '0', '0'),
            (13, '0', '0', '0'),
            (14, '0', '0', '0'),
            (15, '0', '0', '-1'),
            (16, '+1', '+1', '-2'),
            (17, '+2', '+2', '-3'),
            (18, '+3', '+3', '-4'),
            (19, '+3', '+3', '-4'),],
    'CON': [(3, '-2', '40', '35'),
            (4, '-1', '45', '40'),
            (5, '-1', '50', '45'),
            (6, '-1', '55', '50'),
            (7, '0', '60', '55'),
            (8, '0', '65', '60'),
            (9, '0', '70', '65'),
            (10, '0', '75', '70'),
            (11, '0', '80', '75'),
            (12, '0', '85', '80'),
            (13, '0', '90', '85'),
            (14, '0', '92', '88'),
            (15, '+1', '94', '91'),
            (16, '+2', '96', '95'),
            (17, '+2 (+3 for Warriors)', '98', '97'),
            (18, '+2 (+4 for Warriors)', '100', '99'),
            (19, '+2 (+5 for Warriors)', '100', '99'),],
    'CHA': [(3, '1', '-30', '-25'),
            (4, '1', '-25', '-20'),
            (5, '2', '-20', '-15'),
            (6, '2', '-15', '-10'),
            (7, '3', '-10', '-5'),
            (8, '3', '-5', '0'),
            (9, '4', '0', '0'),
            (10, '4', '0', '0'),
            (11, '4', '0', '0'),
            (12, '5', '0', '0'),
            (13, '5', '0', '+5'),
            (14, '6', '+5', '+10'),
            (15, '7', '+15', '+15'),
            (16, '8', '+20', '+25'),
            (17, '10', '+30', '+30'),
            (18, '15', '+40', '+35'),
            (19, '20', '+50', '+40'),],
}

def has_spells_at_level(level, single_class_dict):
    level = int(level)
    level_dict_list = [ row for row in single_class_dict['Classes_meta'] if row['Type'] == 'xp table' ]
    level_dict = level_dict_list[level-1]
    if level_dict['Casting_Level'] != 0 and level_dict['Casting_Level'] != '':
        return True
    return False

def get_attribute_bonuses(attr_key, score):
    score = float(score)
    return_bonus = ()
    for row in bonuses[attr_key]:
        if score > row[0]:
            continue
        return_bonus = row
        break
    return tuple(bonus for bonus in return_bonus[1:])

def get_attribute_bonus_string( attr_key, score ):
    bonuses_dict = {}
    bonuses_dict['STR'] = 'To Hit: {}     Damage: {}     Encumbrance: {}     Minor Test: {}     Major Test: {}%'
    bonuses_dict['INT'] = 'Add\'l Langs: {} Understand Spell Chance: {}% Min/Max Understood / Level: {}'
    bonuses_dict['WIS'] = 'Mental Save: {}     Bonus Cleric Spells: {}     Cleric Spell Failure: {}%'
    bonuses_dict['DEX'] = 'Surprise: {}     Missile To Hit: {}     AC Adjustment: {}'
    bonuses_dict['CON'] = 'HP Bonus: {}   Resurrect/Raise Dead: {}%   System Shock: {}%'
    bonuses_dict['CHA'] = 'Max Henchman: {}     Loyalty: {}     Reaction: {}'

    return bonuses_dict[attr_key].format( *get_attribute_bonuses( attr_key, score ) )

def calculate_ac(attr_dict, class_dict, race_dict, equipment_list):
    base_ac = 10
    dex_score = attr_dict['DEX']
    ac_bonus = get_attribute_bonuses('DEX', dex_score)[2]
    char_base_ac = base_ac + int(ac_bonus)

    armour_list = [e for e in equipment_list if e['unique_id'].startswith('armour_')]
    shield_list = [e for e in equipment_list if e['unique_id'].startswith('shield_')]

    useable_armour = []
    useable_shield = []

    if 'classes' in class_dict:
        ap_lists = []
        sp_lists = []
        for cl in class_dict['classes']:
            armour_permitted_list = [ap.strip() for ap in cl['Armour_Permitted'].split(',')]
            shield_permitted_list = [sp.strip() for sp in cl['Shield_Permitted'].split(',')]
            ap_lists.append(armour_permitted_list)
            sp_lists.append(shield_permitted_list)
        if race_dict['unique_id'] in restrictive_races:
            bucket = []
            all_any = True
            for ap_list in ap_lists:
                if 'None' in ap_list:
                    bucket = []
                    all_any = False
                    break
                elif 'Any' in ap_list:
                    continue
                else:
                    all_any = False
                    for ap in ap_list:
                        if ap not in bucket:
                            bucket.append(ap)
            if all_any:
                useable_armour = armour_list
            else:
                for a in armour_list:
                    if a['Name'] in bucket:
                        useable_armour.append(a)
            bucket = []
            all_any = True
            for sp_list in sp_lists:
                if 'None' in sp_list:
                    bucket = []
                    all_any = False
                    break
                elif 'Any' in ap_list:
                    continue
                else:
                    all_any = False
                    for sp in sp_list:
                        if sp not in sp_list:
                            bucket.append(sp)
            if all_any:
                useable_shield = shield_list
            else:
                for s in shield_list:
                    if s['Name'] in bucket:
                        useable_shield.append(s)
        else:
            bucket = []
            for ap_list in ap_lists:
                for ap in ap_list:
                    if ap not in bucket:
                        bucket.append(ap)

            if 'Any' in bucket:
                useable_armour = armour_list
            else:
                for a in armour_list:
                    if a['Name'] in bucket:
                        useable_armour.append(a)

            bucket = []
            for sp_list in sp_lists:
                for sp in sp_list:
                    if sp not in bucket:
                        bucket.append(sp)

            if 'Any' in bucket:
                useable_shield = shield_list
            else:
                for s in shield_list:
                    if s['Name'] in bucket:
                        useable_shield.append(s)
    else:
        armour_permitted_list = [ap.strip() for ap in class_dict['Armour_Permitted'].split(',')]
        shield_permitted_list = [sp.strip() for sp in class_dict['Shield_Permitted'].split(',')]
        if 'Any' in armour_permitted_list:
            useable_armour = armour_list
        elif 'None' in armour_permitted_list:
            useable_armour = []
        else:
            for a in armour_list:
                if a['Name'] in armour_permitted_list:
                    useable_armour.append(a)
        if 'Any' in shield_permitted_list:
            useable_shield = shield_list
        elif 'None' in shield_permitted_list:
            useable_shield = []
        else:
            for s in shield_list:
                if s['Name'] in shield_permitted_list:
                    useable_shield.append(s)

    best_armour = None
    for a in useable_armour:
        if not best_armour or a['AC_Effect'] < best_armour['AC_Effect']:
            best_armour = a
    best_shield = None
    for s in useable_shield:
        if not best_shield or s['AC_Effect'] < best_shield['AC_Effect']:
            best_shield = s

    if best_armour == None:
        best_armour = {'AC_Effect': 0}
    if best_shield == None:
        best_shield = {'AC_Effect': 0}
    return char_base_ac + best_armour['AC_Effect'] + best_shield['AC_Effect']

def get_saves(level, attr_dict, class_dict, race_dict):
    saves_dict = {'Aimed_Magic_Items': [],
                  'Breath_Weapons': [],
                  'Death_Paralysis_Poison': [],
                  'Petrifaction_Polymorph': [],
                  'Spells': [],}
    if 'classes' in class_dict:
        level_list = [ int(l) for l in level.split('/') ]
        for i, cl in enumerate( class_dict['classes'] ):
            for meta_row in cl['Classes_meta']:
                if meta_row['Type'] == 'xp table' and meta_row['Level'] != 'each' and int(meta_row['Level']) == level_list[i]:
                    for save in list(saves_dict.keys()):
                        saves_dict[save].append(meta_row[save])
        if race_dict['unique_id'] in restrictive_races:
            for save in list(saves_dict.keys()):
                saves_dict[save] = max(*tuple(int(l) for l in saves_dict[save]))
        else:
            for save in list(saves_dict.keys()):
                saves_dict[save] = min(*tuple(int(l) for l in saves_dict[save]))
    else:
        level = int(level)
        for meta_row in class_dict['Classes_meta']:
            if meta_row['Type'] == 'xp table' and meta_row['Level'] != 'each' and int(meta_row['Level']) == level:
                for save in list(saves_dict.keys()):
                    saves_dict[save] = meta_row[save]

    for meta_row in race_dict['Races_meta']:
        if meta_row['Type'] == 'ability' and meta_row['Subtype'] == 'save':
            modifier = meta_row['Modifier']
            modified = meta_row['Modified']
            num_modifier = ''
            for attr_name in list(attr_dict.keys()):
                if attr_name.lower() in modifier.lower():
                    num_modifier = modifier.lower().replace(attr_name.lower(), attr_dict[attr_name])
            #print num_modifier
            for mod in modified.split('/'):
                for save in list(saves_dict.keys()):
                    if mod.strip().replace(' ', '_') == save:
                        saves_dict[save] = eval(str(saves_dict[save]) + num_modifier)
                    elif mod.strip().replace(' ', '_') in save:
                        saves_dict[save] = str(saves_dict[save]) + ' (' + mod.strip() + ' ' + str(eval(str(saves_dict[save]) + num_modifier)) + ')'
                    elif save.replace('_', ' ') in mod:
                        mod_list = mod.split(':')
                        saves_dict[save] = str(saves_dict[save]) + ' (' + mod_list[1] + ' ' + str(eval(str(saves_dict[save]) + num_modifier)) + ')'
                    if len(str(saves_dict[save])) > 15:
                        saves_dict[save] = saves_dict[save].replace(' (', '<br />(')

    return saves_dict

def get_tohit_row(level, class_dict, race_dict):
    tohit_list = []
    if 'classes' in class_dict:
        level_list = [ int(l) for l in level.split('/') ]
        bucket = []
        for i, cl in enumerate( class_dict['classes'] ):
            for row in cl['Classes_meta']:
                if row['Type'] == 'xp table' and row['Level'].isdigit() and int(row['Level']) == level_list[i]:
                    tohit_tuple = (row['To_Hit_-10'],
                                   row['To_Hit_-9'],
                                   row['To_Hit_-8'],
                                   row['To_Hit_-7'],
                                   row['To_Hit_-6'],
                                   row['To_Hit_-5'],
                                   row['To_Hit_-4'],
                                   row['To_Hit_-3'],
                                   row['To_Hit_-2'],
                                   row['To_Hit_-1'],
                                   row['To_Hit_0'],
                                   row['To_Hit_1'],
                                   row['To_Hit_2'],
                                   row['To_Hit_3'],
                                   row['To_Hit_4'],
                                   row['To_Hit_5'],
                                   row['To_Hit_6'],
                                   row['To_Hit_7'],
                                   row['To_Hit_8'],
                                   row['To_Hit_9'],
                                   row['To_Hit_10'],
                                   )
                    bucket.append(tohit_tuple)
        for i in range(0, 21):
            items = (row[i] for row in bucket)
            if race_dict['unique_id'] in restrictive_races:
                tohit_list.append(str(max(*items)))
            else:
                tohit_list.append(str(min(*items)))

    else:
        level = int(level)
        for row in class_dict['Classes_meta']:
            if row['Type'] == 'xp table' and row['Level'].isdigit() and int(row['Level']) == level:
                tohit_list = [str(row['To_Hit_-10']),
                              str(row['To_Hit_-9']),
                              str(row['To_Hit_-8']),
                              str(row['To_Hit_-7']),
                              str(row['To_Hit_-6']),
                              str(row['To_Hit_-5']),
                              str(row['To_Hit_-4']),
                              str(row['To_Hit_-3']),
                              str(row['To_Hit_-2']),
                              str(row['To_Hit_-1']),
                              str(row['To_Hit_0']),
                              str(row['To_Hit_1']),
                              str(row['To_Hit_2']),
                              str(row['To_Hit_3']),
                              str(row['To_Hit_4']),
                              str(row['To_Hit_5']),
                              str(row['To_Hit_6']),
                              str(row['To_Hit_7']),
                              str(row['To_Hit_8']),
                              str(row['To_Hit_9']),
                              str(row['To_Hit_10']),
                              ]
    return tohit_list

def get_coinage_from_float(gp_decimal):
#    bucket = int(gp_decimal * 100)
#    coin_denominations = sorted(economy, key=lambda x: economy[x], reverse=True)
#    return_dict = dict.fromkeys(coin_denominations, 0)
#    for cd in coin_denominations:
#        if economy[cd] <= 1:
#            cd_bucket = 0
#            while bucket >= economy[cd] * 100:
#                bucket = bucket - int(economy[cd] * 100)
#                cd_bucket = cd_bucket + 1
#                return_dict[cd] = cd_bucket

#    return return_dict
    gp_decimal = Decimal( str( gp_decimal ) )
    coin_denominations = sorted(economy, key=lambda x: economy[x], reverse=True)
    return_dict = dict.fromkeys(coin_denominations, 0)
    for cd in coin_denominations:
        if economy[cd] <= 1:
            coin_decimal = Decimal( str( economy[cd] ) )
            coin_mod = gp_decimal // coin_decimal
            gp_decimal -= coin_mod * coin_decimal
            return_dict[cd] = int( coin_mod )

    return return_dict

def get_float_from_coinage( character_or_money_dict ):
    total = 0
    coin_denominations = list( economy.keys() )
    for cd in coin_denominations:
        total += float( economy[cd] * character_or_money_dict[cd] )
    return total

def race_is_restrictive( race ):
    if is_instance( race, dict ):
        race = race['race_id']

    if race in restrictive_races:
        return True

    return False

def race_wp( self, wp_list, race_id, item_dict_list ):
    blunt_list = [ blunt['Name'].lower() for blunt in item_dict_list if 'blunt' in blunt['Damage_Type'].split( ',' ) ]
    race_wp = []
    wp_expand = []
    for wp in wp_list:
        wp_expand.append( [ w.strip().lower() for w in wp.split( ',' ) ] )
    if race_is_restrictive( race_id ):
        bucket = wp_expand[0]
        if 'blunt' in bucket:
            bucket.remove( 'blunt' )
            bucket.extend( blunt_list )
        for i in range( 1, len( wp_expand ) ):
            if 'blunt' in wp_expand[i]:
                wp_expand[i].remove( 'blunt' )
                wp_expand[i].extend( blunt_list )
            if 'any' in bucket:
                bucket = wp_expand[i]
            elif 'any' in wp_expand[i]:
                bucket = bucket
            else:
                bucket = [ w for w in bucket if w in wp_expand[i] ]

    else:
        wp_flat = sum( wp_expand, [] )
        bucket = []
        for wp in wp_flat:
            if wp not in bucket:
                bucket.append( wp )
    if 'any' in bucket:
        bucket = [ 'any', ]
    return bucket

def calculate_movement(race_dict, class_dict, attr_dict, equipment_list):
    base_movement = race_dict['Movement_Rate']
    str_bonus = get_attribute_bonuses('STR', attr_dict['STR'])[2]
    max_movement = 120
    equipment_weight = 0
    wearable_armour = []
    for equip in equipment_list:
        if equip['Weight'] != 'N/A' and equip['Weight'] != '-':
            equip_weight = ''.join([c for c in equip['Weight'] if c.isdigit() or c == '/'])
            if equip_weight.find('/') != -1:
                equip_weight_tuple = equip_weight.split('/')
                numerator = int(equip_weight_tuple[0])
                denominator = int(equip_weight_tuple[1])
                equipment_weight += numerator / float(denominator)
            else:
                equipment_weight += float(equipment_weight)

        if equip['Max_Move_Rate'] != '' and equip['Max_Move_Rate'] != 'N/A':
            base_ac = calculate_ac(attr_dict, class_dict, race_dict, [])
            adj_ac = calculate_ac(attr_dict, class_dict, race_dict, [equip])
            if adj_ac < base_ac:
                wearable_armour.append(equip)
    best_armour = None
    for wa in wearable_armour:
        if not best_armour or wa['AC_Effect'] < best_armour['AC_Effect']:
            best_armour = wa
    if best_armour:
        max_move = ''.join([c for c in best_armour['Max_Move_Rate'] if c.isdigit()])
        if int(max_move) < max_movement:
            max_movement = int(max_move)

    if equipment_weight <= 35 + int(str_bonus):
        movement = (min(base_movement, max_movement), '+1 (for armour lighter than chain mail only)')
    elif equipment_weight <= 70 + int(str_bonus):
        movement = (min(base_movement - 30, max_movement), 'Normal bonuses apply')
    elif equipment_weight <= 105 + int(str_bonus):
        movement = (min(base_movement - 60, max_movement), 'No normal bonuses apply (but penalties do)')
    elif equipment_weight <= 150 + int(str_bonus):
        movement = (min(30, max_movement), 'No normal bonuses apply (but penalties do); -1 extra penalty')
    else:
        movement = (0, 'No movement possible')

    return movement

def get_spells_by_level(level, attr_dict, single_class_dict):
    level = int(level)
    primary_spell_string = 'Level_{}_Spells'
    secondary_spell_string = 'Level_{}_Spells_Secondary'
    primary = ''
    secondary = ''
    for row in single_class_dict['Classes_meta']:
        if row['Type'] == 'xp table' and row['Level'].isdigit() and int(row['Level']) == level and row['Casting_Level'] > 0:
            for i in range(1, 10):
                primary_key = primary_spell_string.format(i)
                secondary_key = secondary_spell_string.format(i)
                if row[primary_key] > 0:
                    primary_int = row[primary_key]
                    if i > 1:
                        primary += '/'
                    if single_class_dict['Category'] == 'priest':
                        bonus_spells = get_attribute_bonuses('WIS', attr_dict['WIS'])[1]
                        bonus_spells_list = bonus_spells.split('/')
                        if i - 1 < len(bonus_spells_list):
                            primary_int += int(bonus_spells_list[i - 1])
                    primary += str(primary_int)
                if row[secondary_key] > 0:
                    if i > 1:
                        secondary += '/'
                    secondary += str(row[secondary_key])
    return ( primary, secondary )

def get_turn_undead_row( level, single_class_dict ):
    level = int(level)
    tu_list = []
    col_string = 'Turn_Undead_Type_{}'
    for row in single_class_dict['Classes_meta']:
        if row['Type'] == 'xp table' and row['Level'].isdigit() and int( row['Level'] ) == level:
            for i in range( 1, 14 ):
                col = col_string.format(i)
                tu_list.append( row[col] )
    return tu_list

ta_col_list = ['Climb_Walls', 'Find_Traps', 'Hear_Noise', 'Hide_in_Shadows',
               'Move_Quietly', 'Open_Locks', 'Pick_Pockets', 'Read_Languages']
def get_thief_abilities_row( level, single_class_dict ):
    level = int(level)
    ta_list = []
    for row in single_class_dict['Classes_meta']:
        if row['Type'] == 'xp table' and row['Level'].isdigit() and int( row['Level'] ) == level:
            for ta_col in ta_col_list:
                ta_list.append( '{}%'.format( row[ta_col] ) )
    return ta_list

def get_class_abilities( level, attr_dict, single_class_dict ):
    level = int(level)
    spells_by_level = get_spells_by_level( level, attr_dict, single_class_dict )
    cl_ab = []
    if spells_by_level[0]:
        primary_spells = single_class_dict['Primary_Spell_List'].replace('_', ' ').title()
        cl_ab.append(('{} Spells by Level'.format(primary_spells), spells_by_level[0]))
        if spells_by_level[1]:
            secondary_spells = class_dict['Secondary_Spell_List'].replace('_', ' ').title()
            cl_ab.append(('{} Spells by Level'.format(secondary_spells), spells_by_level[1]))
    tu_list = get_turn_undead_row( level, single_class_dict )
    ta_list = get_thief_abilities_row( level, single_class_dict )
    for row in single_class_dict['Classes_meta']:
        if row['Type'] == 'ability' and level >= row['Level_Gained']:
            cl_ab.append((row['Ability'], row['Ability_Description']))
            if tu_list and 'turn' in row['Ability'].lower() and 'undead' in row['Ability'].lower():
                headers = [ 'Type {}'.format( i ) for i in range( 1, 14 ) ]
                cl_ab.append( ( '', ( headers, tu_list ) ) )
            if ta_list and 'thief abilities' in row['Ability'].lower():
                headers = [ h.replace( '_', ' ' ) for h in ta_col_list ]
                cl_ab.append( ( '', ( headers, ta_list ) ) )

    return cl_ab

def get_race_abilities( race_dict ):
    return_list = []
    subtype_list = [ 'combat', 'starting languages', 'infravision', 'misc' ]
    for row in race_dict['Races_meta']:
        if row['Type'] == 'ability' and row['Subtype'] in subtype_list:
            if row['Subtype'] == 'infravision':
                first_item = 'Infravision {}'.format( row['Modifier'] )
            elif row['Subtype'] == 'starting languages':
                first_item = 'Starting Languages: '
            else:
                first_item = row['Modifier']
            return_list.append( ( first_item, row['Modified'], row['Notes'] ) )
    return return_list

def get_spell_book( spell_list ):
    output_string = ''
    for spell in spell_list:
        spell['Name']

def get_non_proficiency_penalty( class_dict, race_dict ):
    race_id = race_dict['unique_id']
    if 'classes' in class_dict:
        bucket = []
        for cl in class_dict['classes']:
            bucket.append( cl['Non-Proficiency_Penalty'] )
        if race_id in restrictive_races:
            return min( bucket )
        else:
            return max( bucket )

    return class_dict['Non-Proficiency_Penalty']

def get_character_pdf_markup( character_dict ):
    class_table = DbQuery.getTable( 'Classes' )
    race_table = DbQuery.getTable( 'Races' )
    items_table = DbQuery.getTable( 'Items' )
    spells_table = DbQuery.getTable( 'Spells' )

    class_dict = { 'Name' : '', 'classes' : [] }
    classes_list = character_dict['Classes'].split( '/' )
    for class_id in classes_list:
        for cl in class_table:
            if class_id == cl['unique_id']:
                if class_dict['Name'] == '':
                    if len( classes_list ) == 1:
                        class_dict = cl
                        break
                    class_dict['Name'] = cl['Name']
                else:
                    class_dict['Name'] += '/{}'.format( cl['Name'] )
                class_dict['classes'].append(cl)

    level = character_dict['Level']
    class_name = class_dict['Name']
    class_font_size = '14px'
    class_padding = '0px'
    if len( class_name ) > 15:
        class_font_size = '8px'
        class_padding = '4px'

    for race in race_table:
        if race['unique_id'] == character_dict['Race']:
            race_dict = race

    portrait = character_dict['Portrait']
    ext = character_dict['Portrait_Image_Type']

    attr_dict = {
        'STR' : character_dict['STR'],
        'INT' : character_dict['INT'],
        'WIS' : character_dict['WIS'],
        'DEX' : character_dict['DEX'],
        'CON' : character_dict['CON'],
        'CHA' : character_dict['CHA'],
    }

    equip_id_list = []
    spellbook_id_list = []
    daily_spells_id_list = []
    daily_spells2_id_list = []
    proficiency_id_dict = {}
    for meta_row in character_dict['Characters_meta']:
        if meta_row['Type'] == 'Equipment':
            equip_id_list.append( meta_row['Entry_ID'] )
        elif meta_row['Type'] == 'Treasure':
            if meta_row['Entry_ID'] == 'gp':
                gp = meta_row['Data']

            elif meta_row['Entry_ID'] == 'pp':
                pp = meta_row['Data']

            elif meta_row['Entry_ID'] == 'ep':
                ep = meta_row['Data']

            elif meta_row['Entry_ID'] == 'sp':
                sp = meta_row['Data']

            elif meta_row['Entry_ID'] == 'cp':
                cp = meta_row['Data']
        elif meta_row['Type'] == 'Spellbook':
            spellbook_id_list.append( meta_row['Entry_ID'] )
        elif meta_row['Type'] == 'DailySpells':
            daily_spells_id_list.append( meta_row['Entry_ID'] )
        elif meta_row['Type'] == 'DailySpells2':
            daily_spells2_id_list.append( meta_row['Entry_ID'] )
        elif meta_row['Type'] == 'Proficiency':
            proficiency_id_dict[ meta_row['Entry_ID'] ] = meta_row['Data']

    proficiency_list = []
    specialised_list = []
    double_specialised_list = []
    for prof in items_table:
        if prof['Is_Proficiency'].lower() == 'yes' and prof['unique_id'] in list( proficiency_id_dict.keys() ):
            prof_name = prof['Name']
            prof_level = proficiency_id_dict[  prof['unique_id'] ]
            if prof_level == 'P':
                proficiency_list.append( prof )
            elif prof_level == 'S':
                specialised_list.append( prof )
            elif prof_level == '2XS':
                double_specialised_list.append( prof )

    equipment_list = []
    for equip in items_table:
        if equip['unique_id'] in equip_id_list:
            equipment_list.append( equip )

    class_abilities = {}
    if 'classes' in class_dict:
        level_list = [ int(l) for l in level.split('/') ]
        for i, cl in enumerate( class_dict['classes'] ):
            class_abilities[ cl['Name'] ] = get_class_abilities( level_list[i], attr_dict, cl )
    else:
        class_abilities[ class_dict['Name'] ] = get_class_abilities( level, attr_dict, class_dict )
    race_abilities = get_race_abilities( race_dict )

    spellbook = []
    daily_spells = []
    daily_spells2 = []
    for spell in spells_table:
        if spell['spell_id'] in spellbook_id_list:
            spellbook.append( spell )
        if spell['spell_id'] in daily_spells_id_list:
            daily_spells.append( spell )
        if spell['spell_id'] in daily_spells2_id_list:
            daily_spells2.append( spell )

    #print equipment_list
    saves_dict = get_saves( level, attr_dict, class_dict, race_dict )
    movement_tuple = calculate_movement( race_dict, class_dict, attr_dict, equipment_list )
    markup_template_dict = {
        'class_font_size' : class_font_size,
        'class_padding' : class_padding,
        'name' : character_dict['Name'],
        'gender' : character_dict['Gender'],
        'class' : class_dict['Name'],
        'alignment' : character_dict['Alignment'],
        'race' : race_dict['Name'],
        'xp' : character_dict['XP'],
        'hp' : character_dict['HP'],
        'ac' : calculate_ac( attr_dict, class_dict, race_dict, equipment_list ),
        'level' : level,
        'age' : character_dict['Age'],
        'height': character_dict['Height'],
        'weight': character_dict['Weight'],
        'portrait': portrait,
        'image_type': ext,
        'tohit_row': '<td align=center>' + '</td><td align=center>'.join( get_tohit_row( level, class_dict, race_dict ) ) + '</td>',
        'gp' : gp,
        'pp' : pp,
        'ep' : ep,
        'sp' : sp,
        'cp' : cp,
        'movement_rate' : movement_tuple[0],
        'movement_desc' : movement_tuple[1],
        'nonproficiency_penalty' : get_non_proficiency_penalty( class_dict, race_dict ),
        }
    for attr_name in list( attr_dict.keys() ):
        markup_template_dict[attr_name] = attr_dict[attr_name]
        markup_template_dict[ attr_name + '_bonus' ] = get_attribute_bonus_string( attr_name, attr_dict[attr_name] )
    for save in list( saves_dict.keys() ):
        markup_template_dict[save] = saves_dict[save]

    markup = '''\
<style type=text/css>
.border {
color: red;
border-style: solid;
border-color: purple;
margin-right: 5px;
}

.bigger-font {
font-size: 15px;
}

.smaller-font {
font-size: 10px;
}

.pad-cell {
padding-left: 5px;
padding-right: 5px;
}

.pad-bottom {
padding-bottom: 5px;
}

.pad-top-large {
padding-top: 10px;
}

.pad-all {
padding: 5px;
}

.no-pad {
padding: 0px;
}

.lpad {
padding-left: 15px;
}

.float-right {
float: right;
}

.class-font {
font-size: $class_font_size;
padding-top: $class_padding;
}

.alignment-font {
font-size: 10px;
padding-top: 3px;
}

.attr-bonuses {
font-size: 8px;
vertical-align: middle;
white-space: pre;
}

.tohit-table {
border-style: solid;
}

.tohit-table > tr > td {
padding: 2px;
vertical-align: middle;
}

.equipment-table > tr > th {
padding: 4px;
align: center;
font-size: 10px;
}

.equipment-table > tr > td {
padding: 4px;
align: center;
font-size: 10px;
}

.equip-legend {
font-size: 8px;
}

table.ability {
font-size: 12px;
}

table.ability > tr > th {
padding: 2px;
}

.pre {
white-space: pre;
}

p.page-break {
page-break-after:always;
}
</style>
<h1 align=center>$name</h1>

<table width=100%>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td class=lpad align=center rowspan=5><img height=140 src=data:image;base64,$portrait /></td></tr>
<tr><td class=pad-bottom><b>Name: </b></td><td align=right>$name</td><td class=lpad><b>XP: </b></td><td align=right>$xp</td><td class=lpad><b>Age: </b></td align=right><td align=right>$age</td></tr>
<tr><td class=pad-bottom><b>Class: </b></td><td align=right class=class-font>$class</td><td class=lpad><b>HP: </b></td><td align=right>$hp</td><td class=lpad><b>Height: </b></td><td align=right>$height</td></tr>
<tr><td class=pad-bottom><b>Alignment: </b></td><td align=right class=alignment-font>$alignment</td><td class=lpad><b>AC: </b></td><td align=right>$ac</td><td class=lpad><b>Weight: </b></td><td align=right>$weight</td></tr>
<tr><td class=pad-bottom><b>Race: </b></td><td align=right>$race</td><td class=lpad><b>Level: </b></td><td align=right>$level</td><td class=lpad><b>Gender: </b></td><td align=right>$gender</td></tr>
</table>

<hr />

<table align=center><tr>

<td>
<table class='border bigger-font' border=2 ><tr><td>
<table class=pad-cell>
<tr><td align=right class=pad-cell>Str:</td><td align=right class=pad-cell>$STR</td><td class=attr-bonuses> $STR_bonus </td></tr>
<tr><td align=right class=pad-cell>Int:</td><td align=right class=pad-cell>$INT</td><td class=attr-bonuses> $INT_bonus </td></tr>
<tr><td align=right class=pad-cell>Wis:</td><td align=right class=pad-cell>$WIS</td><td class=attr-bonuses> $WIS_bonus </td></tr>
<tr><td align=right class=pad-cell>Dex:</td><td align=right class=pad-cell>$DEX</td><td class=attr-bonuses> $DEX_bonus </td></tr>
<tr><td align=right class=pad-cell>Con:</td><td align=right class=pad-cell>$CON</td><td class=attr-bonuses> $CON_bonus  </td></tr>
<tr><td align=right class=pad-cell>Cha:</td><td align=right class=pad-cell>$CHA</td><td class=attr-bonuses> $CHA_bonus </td></tr>
</table>
</td></tr></table>
</td>

<td>
<table class=smaller-font align=center border=1>
<tr><td colspan=2><h3 align=center>Saving Throws</h3></td></tr>
<tr><td class=pad-cell>Aimed Magic Items</td><td class=pad-cell align=right>$Aimed_Magic_Items </td></tr>
<tr><td class=pad-cell>Breath Weapon</td><td class=pad-cell align=right>$Breath_Weapons </td></tr>
<tr><td class=pad-cell>Death, Paralysis, Poison</td><td class=pad-cell align=right>$Death_Paralysis_Poison </td></tr>
<tr><td class=pad-cell>Petrifaction, Polymorph</td><td class=pad-cell align=right>$Petrifaction_Polymorph </td></tr>
<tr><td class=pad-cell>Spells</td><td class=pad-cell align=right>$Spells </td></tr>
</tr></table>
</td>

</tr></table>

<hr />

<table class=tohit-table border=1 align=center>
<tr><td><b>Enemy AC</b></td><td align=center> -10 </td><td align=center> -9 </td><td align=center> -8 </td><td align=center> -7 </td><td align=center> -6 </td><td align=center> -5 </td>
<td align=center> -4 </td><td align=center> -3 </td><td align=center> -2 </td><td align=center> -1 </td><td align=center> 0 </td><td align=center> 1 </td>
<td align=center> 2 </td><td align=center> 3 </td><td align=center> 4 </td><td align=center> 5 </td><td align=center> 6 </td><td align=center> 7 </td>
<td align=center> 8 </td><td align=center> 9 </td><td align=center> 10 </td></tr>
<tr><td><b>To Hit</b></td>$tohit_row</tr>
</table>

<hr />

<div class=pre align=center>GP: $gp     PP: $pp     EP: $ep     SP: $sp     CP: $cp</div>

<hr />

<table align=center>
<tr><th><h4>Equipment</h4></th></tr>
<tr><td><table border=1 class=equipment-table>
<tr><th>Name</th><th>Damage Vs S or M</th><th>Damage Vs L</th><th>Damage Type</th><th>RoF</th><th>Range</th><th>Max Move</th><th>AC Effect</th><th>Notes</th></tr>
'''

#    proficiency_page = self.pages['ProficiencyPage']
#    specialised_list = proficiency_page.specialised_list
#    double_specialised_list = proficiency_page.double_specialised_list
    for equip in equipment_list:
        equip_name = equip['Name']
        if equip in double_specialised_list:
            equip_name = equip_name + '<sup>‡</sup>'
        elif equip in specialised_list:
            equip_name = equip_name + '<sup>†</sup>'
        elif equip in proficiency_list:
            equip_name = equip_name + '*'
        equip_list = [ equip_name, equip['Damage_Vs_S_or_M'], equip['Damage_Vs_L'], equip['Damage_Type'],
                       equip['Rate_of_Fire'], equip['Range'], equip['Max_Move_Rate'], str( equip['AC_Effect'] ), equip['Notes'] ]
        markup += '<tr><td align=center>' + '</td><td align=center>'.join( equip_list ) + '</td></tr>'

    markup += '''
</table></td></tr></table>
<div align=center class="equip-legend pre">*=Proficient     †=Specialised     ‡=Double Specialised</div>
<div><b>Movement Rate: </b>$movement_rate ft/round<br />
<b>Surprise: </b>$movement_desc<br />
<b>Non-Proficiency Penalty: </b>$nonproficiency_penalty</div>

<p class=page-break></p>
<h2>Ablities</h2>
'''

    if class_abilities:
        for cl in list( class_abilities.keys() ):
            markup += '\n<h5>{} Abilities</h5>\n'.format( cl )
            for i, a in enumerate( class_abilities[cl] ):
                if a[0]:
                    if i > 0:
                        markup += '<br />'
                    markup += '<b>{}: </b>{}\n'.format( *a )
                else:
                    markup += '<table class=ability align=center border=1>\n<tr>'
                    for h in a[1][0]:
                        markup += '<th align=center>{}</th>'.format(h)
                    markup += '</tr>\n<tr>'
                    for d in a[1][1]:
                        markup += '<td align=center>{}</td>'.format(d)
                    markup += '</tr>\n</table>\n'

    if race_abilities:
        markup += '\n<h5>{} Abilites</h5>\n'.format( race_dict['Name'] )
        markup += '<ul>\n'
        for a in race_abilities:
            markup += '<li>'
            markup += a[0]
            if a[1]:
                markup += ' {}'.format( a[1] )
            if a[2]:
                markup += ' {}'.format( a[2] )
            markup += '</li>\n'
        markup += '</ul>\n'

    spellcaster = False
    if 'classes' in class_dict:
        level_list = [ int(l) for l in level.split( '/' ) ]
        for i, cl in enumerate( class_dict['classes'] ):
            if has_spells_at_level( level_list[i], cl ):
                spellcaster = True
    else:
        if has_spells_at_level( level, class_dict ):
            spellcaster = True

    if spellcaster:
        spell_item_string = '''
<h3>{Name}</h3>
<b>Reversible: </b>{Reversible}<br/>
<b>Level: </b>{Level}<br />
<b>Damage: </b>{Damage}<br />
<b>Range: </b>{Range}<br />
<b>Duration: </b>{Duration}<br />
<b>Area of Effect: </b>{Area_of_Effect}<br />
<b>Components: </b>{Components}<br />
<b>Casting Time: </b>{Casting_Time}<br />
<b>Saving Throw: </b>{Saving_Throw}<br />
<b>Description: </b><span class=pre>{Description}</span><br /><br />
'''
        markup += '<p class=page-break></p>\n<h2>Spells</h2>\n'
        if spellbook:
            markup += '<h5>Spellbook</h5>\n<hr />'
            for spell in spellbook:
                markup += spell_item_string.format( **spell )
            markup += '<hr />\n'
        if daily_spells:
            markup += '<h5>{} Daily Spells</h5>\n<hr />'.format( daily_spells[0]['Type'].title().replace( '_', ' ' ) )
            for spell in daily_spells:
                markup += spell_item_string.format( **spell )
            markup += '<hr />\n'
        if daily_spells2:
            markup += '<h5>{} Daily Spells</h5>\n<hr />'.format( daily_spells2[0]['Type'].title().replace( '_', ' ' ) )
            for spell in daily_spells2:
                markup += spell_item_string.format( **spell )
            markup += '<hr />\n'

    t = Template(markup)
    final_markup = t.safe_substitute( markup_template_dict )

    return ( '{}.pdf'.format( character_dict['Name'] ), final_markup )


def Translate_Tensorflow_MLP(file_out, input_names, output_names, model, input_min=[], input_max=[], output_min=[], output_max=[]):
    # This function writes the MLP to a format which can be read by the SU2 MLP import tool
    # Inputs:
    # - file_out: output file name without extension
    # - input_names: list of strings with the variable names of the MLP input(s)
    # - output names: list of strings with the variable names of the MLP output(s)
    # - model: tensorflow.keras.model; the trained model
    # - input_min: lower normalization values for the input
    # - input_max: upper normalization values for the input
    # - output_min: lower normalization values for the output
    # - output_max: upper normalization values for the output

    # MLP config
    model_config = model.get_config()

    # Number of input variables in the model
    n_inputs = model_config['layers'][0]['config']['batch_input_shape'][1]
    # Number of output variables in the model
    n_outputs = model_config['layers'][-1]['config']['units']

    # Checking if number of provided input and output names are equal to those in the model
    if not n_inputs == len(input_names):
        raise Exception("Number of provided input names unequal to the number of inputs in the model")
    if not n_outputs == len(output_names):
        raise Exception("Number of provided output names unequal to the number of outputs in the model")

    if len(input_max) != len(input_min):
        raise Exception("Upper and lower input normalizations should have the same length")
    if len(output_max) != len(output_min):
        raise Exception("Upper and lower output normalizations should have the same length")

    if len(input_max) > 0 and len(input_min) != n_inputs:
        raise Exception("Input normalization not provided for all inputs")

    if len(output_max) > 0 and len(output_min) != n_outputs:
        raise Exception("Output normalization not provided for all outputs")

    # Creating output file
    fid = open(file_out+'.mlp', 'w+')
    fid.write("<header>\n\n")
    n_layers = len(model_config['layers'])

    # Writing number of neurons per layer
    fid.write('[number of layers]\n%i\n\n' % n_layers)
    fid.write('[neurons per layer]\n')
    activation_functions = []

    for iLayer in range(n_layers-1):
        layer_class = model_config['layers'][iLayer]['class_name']
        if layer_class == 'InputLayer':
            # In case of the input layer, the input shape is written instead of the number of units
            activation_functions.append('linear')
            n_neurons = model_config['layers'][iLayer]['config']['batch_input_shape'][1]
        else:
            activation_functions.append(model_config['layers'][iLayer]['config']['activation'])
            n_neurons = model_config['layers'][iLayer]['config']['units']

        fid.write('%i\n' % n_neurons)
    fid.write('%i\n' % n_outputs)

    activation_functions.append('linear')

    # Writing the activation function for each layer
    fid.write('\n[activation function]\n')
    for iLayer in range(n_layers):
        fid.write(activation_functions[iLayer] + '\n')

    # Writing the input and output names
    fid.write('\n[input names]\n')
    for input in input_names:
        fid.write(input + '\n')

    if len(input_min) > 0:
        fid.write('\n[input normalization]\n')
        for i in range(len(input_names)):
            fid.write('%+.16e\t%+.16e\n' % (input_min[i], input_max[i]))

    fid.write('\n[output names]\n')
    for output in output_names:
        fid.write(output+'\n')

    if len(output_min) > 0:
        fid.write('\n[output normalization]\n')
        for i in range(len(output_names)):
            fid.write('%+.16e\t%+.16e\n' % (output_min[i], output_max[i]))

    fid.write("\n</header>\n")
    # Writing the weights of each layer
    fid.write('\n[weights per layer]\n')
    for layer in model.layers:
        fid.write('<layer>\n')
        weights = layer.get_weights()[0]
        for row in weights:
            fid.write("\t".join(f'{w:+.16e}' for w in row) + "\n")
        fid.write('</layer>\n')

    # Writing the biases of each layer
    fid.write('\n[biases per layer]\n')

    # Input layer biases are set to zero
    fid.write('%+.16e\t%+.16e\t%+.16e\n' % (0.0, 0.0, 0.0))

    for layer in model.layers:
        biases = layer.get_weights()[1]
        fid.write("\t".join([f'{b:+.16e}' for b in biases]) + "\n")

    fid.close()


#!/usr/bin/env python3

import distutils.spawn
import glob
import os
import subprocess
import sys

import utils

from typing import Callable, List, Tuple

DIR_CHAPTERS = os.path.join(os.getcwd(), 'Kapitel')
DOKU_REPO = '../wiki'
DOKU_DIR = os.path.join(DOKU_REPO, 'doku')
PROGRAMS = (
    ('pandoc', 'pandoc', 'pandoc'),
    ('rsvg-convert', 'librsvg', 'librsvg2-bin'),
)

def main():
    if not os.path.exists(DOKU_REPO):
        print(f'No such file or directory: {DOKU_REPO}')
        print(f'git clone https://github.com/kkmsc17/wiki {DOKU_REPO}')
        sys.exit(1)

    install_missing_programs(PROGRAMS)
    make_chapters()
    make_figures()

def install_missing_programs(programs: Tuple[Tuple[str, str, str]]):
    for name, brew_pkg, apt_pkg in programs:
        if not distutils.spawn.find_executable(name):
            subprocess.call(
                f'brew install {brew_pkg} &> /dev/null || sudo apt-get install {apt_pkg}',
                shell=True
            )

def make_chapters():
    make(
        glob.glob(f'{DOKU_DIR}/*-*.md'),
        lambda source: '{}.tex'.format(os.path.join(DIR_CHAPTERS, os.path.basename(source))).strip(),
        'pandoc --normalize --smart --top-level-division=chapter {source} -f markdown -t latex -o "{target}"',
    )

def make_figures():
    files = subprocess.check_output(f'find {DOKU_DIR} -name "*.svg"', shell=True)
    files = [svg.decode('utf-8') for svg in files.strip().split(bytes(os.linesep, 'utf-8'))]
    make(
        files,
        lambda source: str(source).replace(DOKU_DIR + os.sep, '').replace('.svg', '.pdf').strip(),
        'rsvg-convert -f pdf -o {target} {source}',
    )

def make(sources: List[str], targetfn: Callable[[str], str], cmd_tpl: str):
    with utils.ModDB('.prepare.cache') as mdb:
        sources = filter(mdb.has_changed, filter(lambda x: len(x) > 0, sources))
        for source in sources:
            target = targetfn(source)

            if (os.path.exists(target) and
                    os.stat(target).st_mtime > os.stat(source).st_mtime):
                print('CONFLICT: Source file was changed since last run, but target file is newer.')
                print(f'Source: {source}')
                print(f'Target: {target}')
                print('Will not override target... skipping.')
                continue

            target_dir = os.path.dirname(target)
            if len(target_dir) > 0 and not os.path.exists(target_dir):
                print(target_dir)
                os.makedirs(target_dir)

            target_rel = os.path.relpath(target, start=os.getcwd())
            print(f'Generating {target_rel}...')

            cmd = cmd_tpl.format(source=source, target=target)
            subprocess.call(cmd, shell=True)

if __name__ == '__main__':
    main()


#lab03.py, Yi Xiao Yue(Bella)
import turtle
import math
Lucia = turtle.Turtle()
Lucia.shape("turtle")
Lucia.speed(-10)
Lucia.width(4)
def drawRectangle_1():
    """
    draw a rectangle with width 50 and height 100. Use a turtle called Lucia to create the drawing
    """
    Lucia.color("green","yellow")  # Sets the pen color to green and fill color to yellow
    Lucia.seth(90) # Set the initial orientation of the turtle to 0 degrees
    Lucia.begin_fill()
    Lucia.forward(50)    # Move the turtle forward by 50 units in the direction that it was pointing
    Lucia.left(90)       # Turn the turtle left by 90 degrees relative to the direction it was pointing
    Lucia.forward(100)   # Move the turtle forward by 100 units
    Lucia.left(90)
    Lucia.forward(50)
    Lucia.left(90)
    Lucia.forward(100)
    Lucia.left(90)       # Make sure the turtle is oriented back to its initial orientation
    Lucia.end_fill()
def drawRectangle_2():
    """
    draw a rectangle with width 50, height 100, tilt 0, pen color green and fill color yellow . Use a turtle called t to create the drawing
    """

    # Calculate the coordinates for the four corners of the rectangle
    x1 = Lucia.xcor()
    y1 = Lucia.ycor()
    x2 = x1 + 50
    y2 = y1
    x3 = x2
    y3 = y2 + 100
    x4 = x1
    y4 = y1 + 100
    Lucia.color("green","yellow")   # set the pen and fill colors
    Lucia.begin_fill()
    # Command the turtle to visit the four corners in order
    Lucia.goto(x2, y2)
    Lucia.goto(x3, y3)
    Lucia.goto(x4, y4)
    Lucia.goto(x1, y1)

    Lucia.end_fill()

def drawRectangle_3():
    """
    draw a rectangle with width 50, height 100, tilt 0, pen color green and fill color yellow . Use a turtle called t to create the drawing
    """

    # Calculate the coordinates for the four corners of the rectangle

    x1 = Lucia.xcor()
    y1 = Lucia.ycor()

    fourCorners = [(x1 + 50, y1), (x1 + 50, y1 + 100), (x1, y1 + 100), (x1, y1)]

    Lucia.color("green", "yellow")
    Lucia.begin_fill()

    Lucia.goto(fourCorners[0][0], fourCorners[0][1])
    Lucia.goto(fourCorners[1][0], fourCorners[1][1])
    Lucia.goto(fourCorners[2][0], fourCorners[2][1])
    Lucia.goto(fourCorners[3][0], fourCorners[3][1])

    Lucia.end_fill()

def drawRectangle(width, height, tilt, penColor, fillColor):
    """
    draw a rectangle with a given width, height, penColor and fillColor,
    with the current location of the turtle being the
    lower left corner, and the bottom side tilted by an angle tilt (specified in degrees)
    relative to the horizontal axis. After the rectangle is drawn, the turtle should return to its original position with an orientation of 0 degrees with respect to the x-axis.
    Use a turtle called t to create the drawing
    """
    Lucia.color(penColor,fillColor)
    Lucia.seth(tilt)
    Lucia.begin_fill()
    for i in range(2):
        Lucia.forward(width)
        Lucia.left(90)
        Lucia.forward(height)
        Lucia.left(90)
    Lucia.end_fill()

def drawTwoRectangles():
    Lucia.left(180)
    Lucia.up()
    Lucia.forward(500)
    Lucia.down()
    drawRectangle( 50, 100, 0, "red", "")

    # Move the turtle right by 100 units without leaving a trail

    Lucia.up()
    Lucia.forward(100)
    Lucia.down()
    drawRectangle( 100, 150, 22, "green", "yellow")

def drawTriangle(base, height, penColor, fillColor):
    """
    draw a triangle with a given base, height, penColor and fillColor,
    with the current location of the turtle being the
    lower left corner. The base of the triangle should be at 0 degrees with respect to the x-axis.
    Do not assume anything about the initial orientation of the turtle.
    The final orientation of the turtle should be 0 degrees with respect to the x-axis.
    Use a turtle called Lucia to create the drawing
    """

    # Insert code to draw the triangle

    Lucia.color(penColor,fillColor)
    Lucia.begin_fill()
    Lucia.forward(base)
    angle1AsRadian = math.atan(2*height/base)
    angle1AsDegree = math.degrees(angle1AsRadian)
    Lucia.left(180 - angle1AsDegree)
    Lucia.forward(math.sqrt(height**2 + (.5*base)**2))
    Lucia.left(2*angle1AsDegree)
    Lucia.forward(math.sqrt(height**2 + (.5*base)**2))

    Lucia.end_fill()

def drawTwoTriangles():
    """ Draws two non overlapping triangles with different sizes and colors
    """

    drawTriangle(200,100,"blue","pink")
    Lucia.up()
    Lucia.forward(220)
    Lucia.down()
    drawTriangle(100,200,"grey","blue")
    Lucia.seth(0)

if __name__=="__main__":
    drawTwoTriangles()

    drawTwoRectangles()


import pandas as pd

import numpy as np

import matplotlib.pyplot as plt
df_oof = pd.read_csv('../input/rnaoutputanalysis/holdouts.csv')
df_oof
df_oof['seq'] = df_oof.id_seqpos.apply(lambda v: v.split('_')[-2])

df_oof['i'] = df_oof.id_seqpos.apply(lambda v: int(v.split('_')[-1]))

df_oof = df_oof.sort_values('i')
df_oof = df_oof[df_oof.SN_filter == 1]
df_oof_react = df_oof[['seq', 'reactivity']].groupby('seq').reactivity.apply(list).reset_index()

df_oof_react
np.stack(df_oof_react.reactivity.values).shape
fig, ax = plt.subplots(1, 1, figsize=(30, 10))

oof_labels_filtered = np.stack(df_oof_react.reactivity.values)

for i in range(oof_labels_filtered.shape[0]):

    ax.plot(oof_labels_filtered[i, :], alpha=0.008, c='blue');

    ax.set_title('ractivity (holdout)')

ax.plot(np.mean(oof_labels_filtered, axis=0), alpha=1, c='darkblue', linewidth=2);

ax.plot(np.median(oof_labels_filtered, axis=0), alpha=1, c='darkblue', linewidth=2, linestyle='dashed');

ax.set_ylim(0, 4)
# EDA

df_train = pd.read_json('../input/stanford-covid-vaccine/train.json', lines=True)
fig, ax = plt.subplots(1, 1, figsize=(30, 10))

train_labels_filtered = np.stack(df_train[df_train.SN_filter.values == 1].reactivity.values)

for i in range(train_labels_filtered.shape[0]):

    ax.plot(train_labels_filtered[i, :], alpha=0.008, c='green');

    ax.set_title('reactivity (train)')

ax.plot(np.mean(train_labels_filtered[:, :], axis=0), alpha=1, c='darkgreen', linewidth=2);

ax.plot(np.median(train_labels_filtered[:, :], axis=0), alpha=1, c='darkgreen', linewidth=2, linestyle='dashed');

ax.set_ylim(0, 4)
from __future__ import print_function

from ipywidgets import interact, interactive, fixed, interact_manual

import ipywidgets as widgets
@interact(i=widgets.IntSlider(min=0, max=train_labels_filtered.shape[0]-1, step=1, value=0))

def show(i):

    plt.figure(figsize=(30, 3))

    plt.plot(train_labels_filtered[i, :], c='blue');

    plt.plot(oof_labels_filtered[i, :], c='green');

    plt.xlim(0, 107)

    plt.gca().set_xticks(np.arange(107))

    plt.gca().set_xticklabels(df_train.iloc[i].sequence)

    a = plt.gca().twiny()

    a.set_xticks(np.arange(107))

    a.set_xticklabels(df_train.iloc[i].structure)

    a.get_yaxis().set_visible(False)

    plt.grid()
# show residuals

fig, ax = plt.subplots(1, 1, figsize=(30, 10))

residuals = oof_labels_filtered - train_labels_filtered

for i in range(residuals.shape[0]):

    ax.plot(residuals[i, :], alpha=0.01, c='red');

    ax.set_title('residuals')

ax.plot(np.mean(residuals[:, :], axis=0), alpha=1, c='darkred', linewidth=2);

ax.plot(np.median(residuals[:, :], axis=0), alpha=1, c='darkred', linewidth=2, linestyle='dashed');

ax.set_ylim(-1, 1)
np.mean(residuals[:, :].flatten())
np.median(residuals[:, :].flatten())


from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys

import time

##########################################################

cookie_id = "cookie"
cursor_id = "buyCursor"
cursor_xpath = '//*[@id="buyCursor"]/b/text()[2]'
grandma_id = "buyGrandma"
grandma_xpath = '//*[@id="buyGrandma"]/b/text()[2]'
factory_id = "buyFactory"
factory_xpath = '//*[@id="buyFactory"]/b/text()[2]'
mine_id = "buyMine"
mine_xpath = '//*[@id="buyMine"]/b/text()[2]'
shipment_id = "buyShipment"
shipment_xpath = '//*[@id="buyShipment"]/b/text()[2]'
alchemy_id = "buyAlchemy lab"
alchemy_path = '//*[@id="buyAlchemy lab"]/b/text()[2]'
portal_id = "buyPortal"
portal_xpath = '//*[@id="buyPortal"]/b/text()[2]'
time_machine_id = "buyTime machine"
time_machine_xpath = '//*[@id="buyTime machine"]/b/text()[2]'
cookie_amount_id = "money"
cps_id = "cps"

##########################################################

options = webdriver.ChromeOptions()
options.add_experimental_option("detach", True)

CHROME_DRIVER_PATH = "/Users/bruce/Dev/chromedriver"
s=Service(CHROME_DRIVER_PATH)
driver = webdriver.Chrome(service=s, options=options)
driver.get("https://orteil.dashnet.org/experiments/cookie/")

##########################################################

cookie = driver.find_element(By.ID, cookie_id)

cursor= driver.find_element(By.ID, cursor_id)

grandma = driver.find_element(By.ID, grandma_id)

factory = driver.find_element(By.ID, factory_id)

mine = driver.find_element(By.ID, mine_id)

shipment = driver.find_element(By.ID, shipment_id)

alchemy = driver.find_element(By.ID, alchemy_id)

portal = driver.find_element(By.ID, portal_id)

time_machine = driver.find_element(By.ID, time_machine_id)

# print(time_machine_price)

##########################################################

#LOOP

# timeout variable can be omitted, if you use specific value in the while condition
timeout = time.time() + 5   # [seconds]
one_min= time.time() + 60

timeout_start = time.time()
play = True
times = 0

cps = driver.find_element(By.ID, cps_id)
item_ids = ["buyCursor", "buyGrandma", "buyFactory", "buyMine", "buyShipment", "buyAlchemy lab",
            "buyPortal", "buyTime machine"]

#############################################
while True:
    cookie.click()

    if time.time() > timeout:

        all_prices = driver.find_elements(By.CSS_SELECTOR, "#store b")
        item_prices = []

        for price in all_prices:
            element_text = price.text
            print(element_text)
            if element_text != "":
                cost = int(element_text.split("-")[1].strip().replace(",", ""))
                item_prices.append(cost)

        cookie_upgrades = {}
        for n in range(len(item_prices)):
            cookie_upgrades[item_prices[n]] = item_ids[n]
        print(cookie_upgrades)

        cookie_money = driver.find_element(By.ID, cookie_amount_id).text
        if "," in cookie_money:
            cookie_money = cookie_money.replace(",", "")
        cookie_count = int(cookie_money)
        print(cookie_count)

        affordable_upgrades = {}
        for key, value in cookie_upgrades.items():
            if cookie_count > key:
                affordable_upgrades[key] = value
        print(cookie_upgrades.items())
        print(affordable_upgrades)

        highest_price_affordable_upgrade = max(affordable_upgrades)

        to_purchase_id = affordable_upgrades[highest_price_affordable_upgrade]

        driver.find_element(By.ID, to_purchase_id).click()

        timeout = time.time() + 5

    if time.time() >one_min:
        print(cps)
        break

        # if cookie_money > time_machine_price:
        #     time_machine.click()
        # elif cookie_money > portal_price:
        #     portal.click()
        # elif cookie_money > alchemy_price:
        #     alchemy.click()
        # elif cookie_money > shipment_price:
        #     shipment.click()
        # elif cookie_money > mine_price:
        #     mine.click()
        # elif cookie_money > factory_price:
        #     factory.click()
        # elif cookie_money > grandma_price:
        #     grandma.click()
        # elif cookie_money > cursor_price:
        #     cursor.click()
        # else:
        #     cookie.click()

##########################################################
##########################################################

# search1 = driver.find_element(By.NAME, "fName")
# search1.send_keys("Bruce")
# search4 = driver.find_element(By.CSS_SELECTOR, "form button")
# search4.click()

# time.sleep(5)


import torch.nn as nn
import torch
from transformers import CLIPModel
from torchvision import transforms

class VisionEncoder(nn.Module):
    def __init__(self, pretrained_model: str = "flaviagiammarino/pubmed-clip-vit-base-patch32"):
        super(VisionEncoder, self).__init__()

        # Load the CLIP model and tokenizer
        self.model = CLIPModel.from_pretrained(pretrained_model)
        self.transform = self.get_image_transforms()

    def get_image_transforms(self):
        """Defines the image preprocessing pipeline."""
        return transforms.Compose([
            transforms.Resize((224, 224)),
            transforms.ToTensor()])

    def forward(self, images):
        """Extract features from images."""
        images = [self.transform(image) for image in images]
        images = torch.stack(images, dim=0)
        features = self.model.encode_image(images)
        return features

if __name__ == '__main__':
    encoder = VisionEncoder()
    from PIL import Image
    sample_image = Image.open("./data/img/synpic676.jpg")
    features = encoder([sample_image])
    print(features.shape)  # Expected: torch.Size([1, 512])


import os

from ctypes import CDLL, c_double, POINTER, c_int, c_char_p, c_char

from .numpy_utils import CPtrToDouble, CPtrToInt, CPtrToChar
from .numpy_utils import Allocator as alloc

################################################################################

# load lib
_lib = CDLL(os.path.join(os.path.dirname(__file__), "_defects.so"))

################################################################################

# findDefects prototype
_lib.findDefects.restype = c_int
_lib.findDefects.argtypes = [c_int, c_int, c_int, POINTER(c_int), POINTER(c_int), POINTER(c_int), POINTER(c_int), POINTER(c_int),
                                    POINTER(c_int), c_int, POINTER(c_int), c_int, POINTER(c_int), c_int, POINTER(c_int), c_int, POINTER(c_char),
                                    POINTER(c_int), POINTER(c_double), c_int, POINTER(c_char), POINTER(c_int), POINTER(c_double), POINTER(c_double),
                                    POINTER(c_int), c_double, c_double, POINTER(c_double), POINTER(c_double), c_int, c_int]

# findDefects
def findDefects(includeVacs, includeInts, includeAnts, defectList, NDefectsByType, vacancies, interstitials, antisites, onAntisites,
                       inclSpec, exclSpecInput, exclSpecRef, NAtoms, specieList, specie, pos, refNAtoms, specieListRef, specieRef, refPos,
                       cellDims, PBC, vacancyRadius, inclusionRadius, minPos, maxPos, verboseLevel, debugDefects):
    """
    findDefects

    """
    return _lib.findDefects(includeVacs, includeInts, includeAnts, CPtrToInt(defectList), CPtrToInt(NDefectsByType), CPtrToInt(vacancies),
                            CPtrToInt(interstitials), CPtrToInt(antisites), CPtrToInt(onAntisites), len(inclSpec), CPtrToInt(inclSpec),
                            len(exclSpecInput), CPtrToInt(exclSpecInput), len(exclSpecRef), CPtrToInt(exclSpecRef), NAtoms, CPtrToChar(specieList),
                            CPtrToInt(specie), CPtrToDouble(pos), refNAtoms, CPtrToChar(specieListRef), CPtrToInt(specieRef), CPtrToDouble(refPos),
                            CPtrToDouble(cellDims), CPtrToInt(PBC), vacancyRadius, inclusionRadius, CPtrToDouble(minPos), CPtrToDouble(maxPos),
                            verboseLevel, debugDefects)

################################################################################

# atomicSeparation2 prototype
_lib.atomicSeparation2.restype = c_double
_lib.atomicSeparation2.argtypes = [c_double, c_double, c_double, c_double, c_double, c_double, c_double, c_double, c_double, c_int, c_int, c_int]

# atomicSeparation2
def atomicSeparation2(ax, ay, az, bx, by, bz, xdim, ydim, zdim, pbcx, pbcy, pbcz):
    """
    Calculate atomic separation squared.

    """
    return _lib.atomicSeparation2(ax, ay, az, bx, by, bz, xdim, ydim, zdim, pbcx, pbcy, pbcz)

################################################################################


class Reine:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def est_en_conflit(self, autre_reine):
        return (self.x == autre_reine.x or
                self.y == autre_reine.y or
                self.x + self.y == autre_reine.x + autre_reine.y or
                self.x - self.y == autre_reine.x - autre_reine.y)

class Echiquier:
    def __init__(self):
        self.taille = 8
        self.reines = []

    def placer_reine(self, reine):
        self.reines.append(reine)

    def echiquier_valide(self):
        for i in range(len(self.reines)):
            for j in range(i + 1, len(self.reines)):
                if self.reines[i].est_en_conflit(self.reines[j]):
                    return False
        return True

def dfs_solve(echiquier):
    pile = [echiquier]
    while pile:
        etat_actuel = pile.pop()
        if len(etat_actuel.reines) == etat_actuel.taille:
            return etat_actuel
        for y in range(etat_actuel.taille):
            nouvelle_reine = Reine(len(etat_actuel.reines), y)
            nouveau_echiquier = Echiquier()
            nouveau_echiquier.reines = list(etat_actuel.reines)
            nouveau_echiquier.placer_reine(nouvelle_reine)
            if nouveau_echiquier.echiquier_valide():
                pile.append(nouveau_echiquier)

def bfs_solve(echiquier):
    file = [echiquier]
    while file:
        etat_actuel = file.pop(0)
        if len(etat_actuel.reines) == etat_actuel.taille:
            return etat_actuel
        for y in range(etat_actuel.taille):
            nouvelle_reine = Reine(len(etat_actuel.reines), y)
            nouveau_echiquier = Echiquier()
            nouveau_echiquier.reines = list(etat_actuel.reines)
            nouveau_echiquier.placer_reine(nouvelle_reine)
            if nouveau_echiquier.echiquier_valide():
                file.append(nouveau_echiquier)
    return None

def main():
    echiquier_initial = Echiquier()
    solution_dfs = dfs_solve(echiquier_initial)
    solution_bfs = bfs_solve(echiquier_initial)

    if solution_dfs is not None:
        print("Solution trouvée en utilisant l'algorithme de recherche en profondeur:")
        for reine in solution_dfs.reines:
            print("({}, {})".format(reine.x, reine.y))
    else:
        print("Pas de solution trouvée en utilisant l'algorithme de recherche en profondeur.")

    if solution_bfs is not None:
        print("Solution trouvée en utilisant l'algorithme de recherche en largeur:")
        for reine in solution_bfs.reines:
            print("({}, {})".format(reine.x, reine.y))
    else:
        print("Pas de solution trouvée en utilisant l'algorithme de recherche en largeur.")

if __name__ == '__main__':
    main()


import matplotlib.pyplot as plt
from rightUp import move_right_slant
from moveRight import move_right

def polyline_detection(incision, incision_original, image):
    x_len = len(incision)
    y_len = len(incision[1])

    incision_points = list()  # for the final detection

    y_position = 0  # on which column is the pixel true
    x_position = 0
    possibilities = list()  # if there is another possible way in the net
    tolerance = 6  # the tolerance band
    incision_continue = True
    steps_up = 3  # how many steps am I able to make UP
    steps_down = 3  # how many steps am I able to make DOWN
    up_counter = 0
    down_counter = 0
    up_down = False
    up_down_steps = 0
    x_diff_right_up = 0
    y_diff_right_up = 0
    x_diff_right_down = 0
    y_diff_right_down = 0

    for y in range(y_position, y_len):
        if incision_continue:
            for x in range(x_position, x_len):
                # print(str(x) + ", " + str(y))
                # direction: right
                if incision[x][y]:
                    outs = move_right(x, y, tolerance, incision_points, possibilities, x_len, incision,
                                      incision_continue, image)
                    x_position = outs[0]
                    incision_points = outs[1]
                    possibilities = outs[2]
                    incision_continue = outs[3]
                    break

                # direction: right up
                elif incision[x-1][y]:
                    outs = move_right_slant(x, y, 0, tolerance, incision_points, possibilities, "up", x_len, incision,
                                            incision_continue, image)
                    x_position = outs[0]
                    incision_points = outs[1]
                    possibilities = outs[2]
                    incision_continue = outs[3]
                    break

                # direction: right up - higher tolerance band
                elif incision[x-2][y]:
                    outs = move_right_slant(x, y, 1, tolerance, incision_points, possibilities, "up", x_len, incision,
                                            incision_continue, image)
                    x_position = outs[0]
                    incision_points = outs[1]
                    possibilities = outs[2]
                    incision_continue = outs[3]
                    break

                # direction: right down
                elif x+1 != x_len and incision[x+1][y]:
                    outs = move_right_slant(x, y, 0, tolerance, incision_points, possibilities, "down", x_len, incision,
                                            incision_continue, image)
                    x_position = outs[0]
                    incision_points = outs[1]
                    possibilities = outs[2]
                    incision_continue = outs[3]
                    break
        else:
            break

    x_values = list()
    y_values = list()
    for i in range(0, len(incision_points)):
        x_values.append(incision_points[i][0])
        y_values.append(incision_points[i][1])
    plt.subplot(211)
    plt.plot(y_values, x_values)
    plt.xlim([0, y_len])
    plt.ylim([x_len, 0])
    plt.subplot(212)
    plt.imshow(incision_original, cmap="gray")
    plt.show()

    return incision_points


import string, tweepy, json, re
from dotenv import dotenv_values
from sentiment import analyze_sentiment, translate_text
from nltk.tokenize import WordPunctTokenizer

config = dotenv_values(".env")
auth = tweepy.OAuthHandler(config["CONSUMER_KEY"], config["CONSUMER_KEY_SECRET"])
auth.set_access_token(config["ACCESS_TOKEN"], config["ACCESS_TOKEN_SECRET"])
api = tweepy.API(auth)

query = "vuurwerk"
data = []

with open("names/dutch_names.json", 'r') as file:
    NAMES = json.load(file)

def has_numbers(twitter_handle):
    return any(char.isdigit() for char in twitter_handle)

def has_letters(twitter_handle):
    return any(char.isalpha() for char in twitter_handle)

def starts_with_numb(twitter_handle): #Had a bug where strings that started with an int broke the query
    return twitter_handle[0].isdigit()

def clean_tweets(tweet):
    user_removed = re.sub(r'@[A-Za-z0-9]+','',tweet)
    link_removed = re.sub('https?://[A-Za-z0-9./]+','',user_removed)
    number_removed = re.sub('[^a-zA-Z]', ' ', link_removed)
    lower_case_tweet= number_removed.lower()
    tok = WordPunctTokenizer()
    words = tok.tokenize(lower_case_tweet)
    clean_tweet = (' '.join(words)).strip()
    return clean_tweet

def nameCheck(twitter_handle, amount_of_numbers):  # returns bool
    if starts_with_numb(twitter_handle) == False:
        if has_numbers(twitter_handle) == True:
            if has_letters(twitter_handle) == True:
                temp = re.compile("([a-zA-Z]+)([0-9]+)")    # Using re.compile() + re.match() + re.groups()
                res = temp.match(twitter_handle).groups()         # Splitting text and number in string
                print(res)
                numbercheck = res[1]
                numbercheck = len(numbercheck)
                if numbercheck >= amount_of_numbers:
                    bunch_of_numbers = True
                else:
                    bunch_of_numbers = False
                    return False, 0
                if bunch_of_numbers == True:
                    first_name = res[0]
                    for name in NAMES:
                        if first_name == name["name"]:
                            gender = name["gender"]
                            return True, gender
                    else:
                        return False, 0
                else:
                    return False, 0
            else:
                return False, 0
        else:
            return False, 0
    else:
        return False, 0

for tweets in tweepy.Cursor(api.search_tweets, q=query, count=100, lang="nl", tweet_mode='extended', result_type="latest").pages(5):
    total = 0
    total_found = 0
    for tweet in tweets:
        tweet = tweet._json
        twitter_handle = tweet["user"]["screen_name"]
        twitter_handle = ''.join(filter(str.isalnum, twitter_handle))
        print(twitter_handle)
        name_check_pass, gender = nameCheck(twitter_handle, 2)
        total = total + 1
        if name_check_pass == True:
            print("We caught one!")
            text = tweet["full_text"]
            rt = text.startswith('RT')
            print("")
            if rt == True:
                text = tweet["retweeted_status"]["full_text"]
                translation = translate_text('en', text)
                clean_tweet = clean_tweets(translation)
                sentiment = analyze_sentiment(clean_tweet)
                print(sentiment)
                total_found = total_found + 1
                data.append({
                    'query': query,
                    'date' : tweet["created_at"],
                    'screen_name' : tweet["user"]["screen_name"],
                    'gender' : gender,
                    'text' : tweet["retweeted_status"]["full_text"],
                    "followers" : tweet["user"]["followers_count"],
                    "retweet" : True,
                    "original_author" : tweet["retweeted_status"]["user"]["screen_name"],
                    "sentiment" : sentiment[0],
                    "magnitude" : sentiment[1]
                })
                print("Retweet is trueee")
            else:
                text = tweet["full_text"]
                translation = translate_text('en', text)
                clean_tweet = clean_tweets(translation)
                sentiment = analyze_sentiment(clean_tweet)
                total_found = total_found + 1
                data.append({
                    'query': query,
                    'date' : tweet["created_at"],
                    'screen_name' : tweet["user"]["screen_name"],
                    'gender' : gender,
                    'text' : tweet["full_text"],
                    "followers" : tweet["user"]["followers_count"],
                    "retweet" : False,
                    "original_author" : tweet["user"]["screen_name"],
                    "sentiment" : sentiment[0],
                    "magnitude" : sentiment[1]
                })
        else:
            print("No Firstnames Bunchofnumbers found.")

print(total)
filename = 'data/tweets_' + query +'.json'
with open (filename, 'w', encoding='utf8') as outfile:
    json.dump(data, outfile)


import os
import fnmatch
import yaml
from log import log

def get_node_info(node_dir, node_name):
    """
    Recurses down the directory trying to find filename wildcards that
    match the given filename. Returns the node info for the closest
    match.

    If there are two wildcards that match, the one that is more
    specific will win.

    Example:
        node_name = 'dev-sea1.api.example.com'

        files = os.listdir(node_dir)
        # Returns ['dev%','dev%.api.example.com', 'staging%']
        get_node_info(node_dir, node_name) = node info of 'dev%.api.example.com'

    Keyword arguments:
        node_dir -- the directory to start
        node_name -- the name of the host to match against
    """
    log.debug('entering node.get_node_info')
    result = _descend(node_dir, node_name)

    # sort by length with only the basename and with all hashes removed
    result.sort(lambda x,y: cmp(
        len(os.path.basename(x).translate(None, '%*')), # remove all percents
        len(os.path.basename(y).translate(None, '%*'))))# and asterisks

    ret = {}
    for r in result:
        with open(r) as node_file:
            ret.update(yaml.load(node_file.read()))

    log.debug('leaving node.get_node_info: %s', ret)
    return ret

def _descend(base_dir, node_name):
    """
    Recurses down the directory trying to find filename wildcards that
    match the given filename. Returns a list of all wildcards that match.

    Keyword arguments:
        base_dir -- the directory to start
        node_name -- the name of the host to match against
    """
    files = os.listdir(base_dir)
    result = []
    for f in files:
        srcfile = os.path.join(base_dir, f)
        if os.path.isdir(srcfile):
            result.extend(_descend(srcfile, node_name))
        else:
            if fnmatch.filter([node_name], f.replace('%','*')):
                result.append(srcfile)
    return result