import csv
import requests

from math import floor, ceil
from random import randint, choice, shuffle

from .models import (
    CharacterClass,
    EquipmentArmour,
    EquipmentRangedWeapon,
    EquipmentMeleeWeapon,
    CharacterNPC,
    Spell
)


def number_encountered():
    return (randint(1, 4) + 2)

def npc_class(excluded_buckets):
    if excluded_buckets:
        classes = [c for cls in CharacterClass.query.filter(CharacterClass.bucket.notin_(excluded_buckets)).all() for c in ([cls] * cls.frequency_modifier)]
    else:
        classes = [c for cls in CharacterClass.query.all() for c in ([cls] * cls.frequency_modifier)]
    return choice(classes)

def npc_alignment():
    roll = randint(1, 6)
    if roll == 1 or roll == 2:
        return 'Lawful'
    elif roll == 3 or roll == 4 or roll == 5:
        return 'Neutral'
    elif roll == 6:
        return 'Chaotic'

def npc_baselevel(base_level):
    roll = randint(1, 6)
    mod = 0
    if roll == 1:
        mod = -2
    elif roll == 2:
        mod = -1
    elif roll == 3 or roll == 4:
        mod = 0
    elif roll == 5:
        mod = 1
    elif roll == 6:
        mod = 2

    base_level += mod
    if base_level < 1:
        base_level = 1

    return base_level

def npc_abilities():
    ability_list = ['strength', 'intelligence', 'wisdom', 'dexterity', 'constitution', 'charisma']
    abilities = [(randint(1, 6) + randint(1, 6) + randint(1, 6)) for x in range(0,6)]
    return zip(ability_list, abilities)

def attribute_mod(atr):
    mod = 0

    if atr <= 3: mod = -3
    if atr >= 4 and atr <= 5: mod = -2
    if atr >= 6 and atr <= 8: mod = -1
    if atr >= 9 and atr <= 12: mod = 0
    if atr >= 13 and atr <= 15: mod = 1
    if atr >= 16 and atr <= 17: mod = 2
    if atr >= 18: mod = 3

    return mod

def select_melee_weapon(guild, data):
    weapons = []
    for x in range(0, guild.melee_heavy):
        weapons.extend(data['melee']['heavy'])
    for x in range(0, guild.melee_medium):
        weapons.extend(data['melee']['medium'])
    for x in range(0, guild.melee_light):
        weapons.extend(data['melee']['light'])
    return choice(weapons)

def select_ranged_weapon(guild, data):
    weapons = []
    for x in range(0, guild.ranged_light):
        weapons.extend(data['ranged']['light'])
    for x in range(0, guild.ranged_heavy):
        weapons.extend(data['ranged']['heavy'])

    if not weapons:
        return None
    return choice(weapons)

def select_spell_list(npc, data):
    npc_spell_list = []
    overall_spell_list = []
    if npc.is_arcane_spellcaster:
        overall_spell_list = data['spells']['arcane']
    if npc.is_divine_spellcaster:
        overall_spell_list = data['spells']['divine']
    
    for level, known in enumerate(npc.spells_known(), start=1):
        level_set = set()
        while len(level_set) < known:
            level_set.add(choice(overall_spell_list[level]).id)
        npc_spell_list.extend(level_set)

    return ','.join(str(sid) for sid in npc_spell_list)

def calc_hp(conmod, hit_die_size, level):
    hp = 0
    hitdice = [randint(1, hit_die_size) for x in range(0, level)]
    for die in hitdice:
        die += conmod
        if die < 1:
            die = 1
        hp += die

    return hp

def calc_armour(armour_mod, armours):
    armourval = randint(0, len(armours) - 1) + armour_mod
    if armourval < 0:
        armourval = 0
    if armourval > len(armours) - 1:
        armourval = len(armours) - 1
    return armours[armourval]

def generate_npc(base_level, data, guild_id=False):
    npc = CharacterNPC()

    if not guild_id:
        npc.guild = npc_class(['Demi-Human'])
    else:
        npc.guild = CharacterClass.query.filter_by(id=guild_id).first()

    npc.level = npc_baselevel(base_level)
    if npc.level > npc.guild.maximum_level:
        npc.level = npc.guild.maximum_level
    npc.alignment = npc_alignment()

    npc.update(npc_abilities())
    npc.hit_points = calc_hp(attribute_mod(npc.constitution), npc.guild.hit_die_size, npc.level)

    npc.armour = calc_armour(npc.guild.armour_modifier, data['armours'])
    npc.melee = select_melee_weapon(npc.guild, data)
    npc.ranged = select_ranged_weapon(npc.guild, data)
    npc.spells = select_spell_list(npc, data)

    return npc

def name_party_api(party):
    male_names = requests.get(
        'http://names.drycodes.com/{}'.format(ceil(len(party))),
        params={'nameOptions': 'boy_names', 'separator': 'space'}
    ).json()

    female_names = requests.get(
        'http://names.drycodes.com/{}'.format(floor(len(party))),
        params={'nameOptions': 'girl_names', 'separator': 'space'}
    ).json()
    
    names = female_names + male_names
    shuffle(names)

    for i in range(0, len(party)):
        party[i].name = names[i]

    return party

def load_name_data():
    names = []
    surnames = []
    with open('acks/npc/data/fantasy_names.csv', newline='') as data:
        reader = csv.DictReader(data)
        for row in reader:
            for k,v in row.items():
                if v == '1':
                    row[k]  = True
                elif v == '':
                    row[k] = False
            if row['Family Name']:
                surnames.append(row['Name'])
            else:
                names.append(row['Name'])
    return (names, surnames)

def name_party(party):
    names, surnames  = load_name_data()
    shuffle(names)
    shuffle(surnames)

    for i in range(0, len(party)):
        party[i].name = '{} {}'.format(choice(names), choice(surnames))

    return party

def load_db_data():
    data = {
        'armours': EquipmentArmour.query.all(),
        'ranged': {
            'light': EquipmentRangedWeapon.query.filter_by(bucket='Light').all(),
            'heavy': EquipmentRangedWeapon.query.filter_by(bucket='Heavy').all()
        },
        'melee': {
            'light': EquipmentMeleeWeapon.query.filter_by(bucket='Light').all(),
            'medium': EquipmentMeleeWeapon.query.filter_by(bucket='Medium').all(),
            'heavy': EquipmentMeleeWeapon.query.filter_by(bucket='Heavy').all()
        },
        'spells': {
            'divine': {},
            'arcane': {},
        }
    }

    for spell in Spell.query.all():
        if spell.is_arcane:
            spells = [spell]
            if data['spells']['arcane'] and spell.arcane in data['spells']['arcane']:
                spells.extend(data['spells']['arcane'][spell.arcane])
            data['spells']['arcane'][spell.arcane] = spells
        if spell.is_divine:
            spells = [spell]
            if data['spells']['divine'] and spell.divine in data['spells']['divine']:
                spells.extend(data['spells']['divine'][spell.divine])
            data['spells']['divine'][spell.divine] = spells

    return data

def create_npc(base_level, guild_id):
    data = load_db_data()
    if guild_id:
        return name_party([generate_npc(base_level, data, guild_id=guild_id)])[0]
    return name_party([generate_npc(base_level, data)])[0]

def create_party(base_level):
    data = load_db_data()
    return name_party([generate_npc(base_level, data) for x in range(0, number_encountered())])

def print_party(party):
    def print_npc(npc):
        print('Level {0} NPC, {1}, {2} HP'.format(npc.level, npc.guild, npc.hp))
        print('{0} Str, {1} Int, {2} Wis, {3} Dex, {4} Con, {5} Chr'.format(
            npc.str, npc.int, npc.wis,
            npc.dex, npc.con, npc.chr
        ))
        print('Armour Class: {0} - {1}, {2}gp'.format(npc.armour.name, npc.armour.ac_mod, npc.armour.gp_value))
        print('{:^16} - {:^10} - {:^10} - {:^10}'.format('Weapon', 'Gold', 'Throw Mod', 'Damage'))
        print('-------------------------------------------------------')
        print('{:^16} | {:^10} | {:^10} | {:^10}'.format(npc.melee.name, 0, npc.melee.damage_die))
        if npc['ranged']:
            print('{:^16} | {:^10} | {:^10} | {:^10}'.format(npc.ranged.name, 0, npc.melee.damage_die))
        print('\n')

    for npc in party:
        print_npc(npc)


if __name__ == '__main__':
    party = create_party(2)
    print_party(party)
