#!/usr/bin/python
#
# battleman.py - RPG Battle Manager
#
# A table-top RPG battle flow manager
# Tuned pretty specifically to D&D 4e for now... need a templatized system
# to do anything fancier... may develop that at some point.
#
# future features:
#  * keep a pickled file or a shelve db of the current state, and add a --resume option
#    for resuming a battle later
#  * an option for passing in multiple files that contain combatant definitions
#  * down combatants go into a separate list
#  * implement temporary hit points!

from dice import Dice
import sys


class CombatGroup():
    
    # What we're mostly getting here is a definition of the *members*
    # of the group... then we build them all and stick them in
    # the group
    @classmethod
    def from_input(cls):
        name = input_str("Name: ")
        hp = input_int('hp')
        init_mod = input_int('init mod', 0)
        ap = input_int('action points', 0)
        surges = input_int('healing surges', 0)

        recharges = []
        recharge = '-1'
        while True:
            recharge = input_str("recharge: ").split(',')
            if recharge == ['']:
                break
            else:
                recharges.append(recharge)

        count = input_int('count', 1)

        # Now make the combatants...
        members = []
        for i in range(count):
            members.append(Combatant(name, hp, pc=False, surges=surges, ap=ap, sw=0, recharges=recharges))

        if count > 1:
            name = name + 's'

        return CombatGroup(name, members, init_mod)


    def __init__(self, name, members, init_mod=0):
        self.name = name
        self.members = members
        self.init_mod = init_mod
        self.init = 0


    def roll_init(self):
        d = Dice.from_str('1d20+{}'.format(self.init_mod))
        self.set_init(d.roll()['total'])


    def set_init(self, init):
        self.init = init


    def add_member(self, c):
        self.members.append(c)


    def is_solo_group(self):
        return len(self.members) == 1


    def begin_turn(self):
        msg = None
        if self.is_solo_group():
            msg = '{} has initiative.'.format(self.name)
        else:
            msg = '{} have initiative.'.format(self.name)

        print msg


    def end_turn(self):
        for c in self.members:
            c.end_turn()


class Combatant():
    next_index = 0

    def __init__(self, name, hp, pc=False, init_mod=0, surges=0, ap=0, sw=0, recharges=[]):
        self.name = name
        self.max_hp = hp
        self.hp = self.max_hp
        self.temp_hp = 0
        self.pc = pc
        self.surges = surges
        self.ap = ap
        self.sw = sw
        self.recharges = []
        self.conditions = []
        self.index = Combatant.next_index
        Combatant.next_index += 1


    def add_condition(self, name, cond_type, duration):
        condition = {}
        condition['name'] = name
        condition['duration'] = duration
        self.conditions.append(condition)


    def end_turn(self):
        pass # fixme - need to do a lot of stuff with conditions here


    def damage(self, amount):
        was_bloodied = self.is_bloodied()

        if self.temp_hp > 0:
            self.temp_hp -= amount
            if self.temp_hp < 0:
                amount = abs(self.temp_hp)
                self.temp_hp = 0

        self.hp -= amount

        # fixme - should we change this output?
        if self.is_down():
            print('{} ({}) is down!'.format(self.name, self.index))
        elif self.is_bloodied() and not was_bloodied:
            print('{} ({}) is bloodied! Remaining hp: {}'.format(self.name, self.index, self.hp))


    def heal(self, amount):
        was_down = self.is_down()
        was_bloodied = self.is_bloodied()

        if self.hp < 0:
            self.hp = 0

        self.hp = min(self.hp + amount, self.max_hp + self.temp_hp)

        # fixme - should we change this output?
        if was_down:
            print('{} ({}) is conscious.'.format(self.name, self.index))
        if was_bloodied and not self.is_bloodied():
            print('{} ({}) is no longer bloodied.'.format(self.name, self.index))
        elif was_bloodied and self.is_bloodied():
            print('{} ({}) is still bloodied.'.format(self.name, self.index))


    def is_bloodied(self):
        return self.hp <= self.max_hp / 2


    def is_down(self):
        return self.hp <= 0


    def format_health_summary(self):
        bloodied = ''
        temp_info = ''
        if self.is_bloodied():
            bloodied = ', bloodied'
        if len(self.conditions):
            bloodied = bloodied + ', '

        if self.temp_hp > 0:
            temp_info = ', {} temp hp'.format(self.temp_hp)

        return '{} hp{}{}{}'.format(self.hp, temp_info, bloodied, ', '.join([x.name for x in self.conditions]))


    def __str__(self):
        return "{}: {} ({})".format(self.index, self.name, self.format_health_summary())


def input_str(prompt, default=None, show_default=False, prompt_str=':'):
    full_prompt = prompt
    if default != None and show_default:
        full_prompt = full_prompt + ' [{}]'.format(default)
    full_prompt = full_prompt + '{} '.format(prompt_str)
    
    data = raw_input(full_prompt)
    if not data:
        if default == None:
            print 'Error: you must provide a value!'
            return input_str(prompt, default, show_default, prompt_str)
        else:
            return default
    else:
        return data


def input_int(prompt, default=None, show_default=True, prompt_str=':'):
    return int(input_str(prompt, default, show_default))


# data about the battle - includes combatant list, etc
class Battle():
    def __init__(self):
        self.combatant_hash = {}
        self.groups = []
        self.current = None
        self.round = None


    def __str__(self):
        ret = ''
        if self.is_started():
            ret = 'Battle underway, currently on round {}\n\n'.format(self.round)
        else:
            ret = 'Battle not yet started\n\n'

        ret = ret + 'Combatants\n==========\n'
        ret = ret + self.format_combatants()

        return ret
        

    def is_started(self):
        return self.current != None


    def add_group(self, group):
        self.groups.append(group)
        for c in group.members:
            self.combatant_hash[c.index] = c


    def get_current_group(self):
        if self.current != -1:
            return self.groups[self.current]
        else:
            return None


    def begin(self):
        if self.is_started():
            print("Error: battle is already running")

        for g in self.groups:
            if g.is_solo_group() and g.members[0].pc:
                g.set_init(input_int('Initiative for {}'.format(g.name)))
            else:
                g.roll_init()

        self.groups.sort(reverse=True, key=lambda group: group.init)
        self.current = 0

        print '\nInitiative Roster:\n'
        for g in self.groups:
            print '{} ({})'.format(g.name, g.init) # fixme - should we change this output?
        print ''

        self.next_round()
        self.get_current_group().begin_turn()


    # Returns a formatted string with all of the combatants
    def format_combatants(self):
        ret = ''

        for g in self.groups:
            if g.is_solo_group():
                ret = ret + '{}\n'.format(g.members[0])
            else:
                ret = ret + '{}:\n'.format(g.name)
                for c in g.members:
                    ret = ret + '\t{}\n'.format(c)
        
        return ret.rstrip()


    # Returns a formatted string with all of the combatants
    def format_current_group(self):
        ret = ''

        g = self.groups[current]
        if g.is_solo_group():
            ret = ret + '{}\n'.format(g.members[0])
        else:
            ret = ret + '{}:\n'.format(g.name)
            for c in g.members:
                ret = ret + '\t{}\n'.format(c)
        
        return ret.rstrip()


    def next_combatant(self):
        if not self.validate_started():
            return

        g = self.get_current_group()
        g.end_turn()
        
        self.current += 1

        if self.current >= len(self.groups):
            self.current = 0
            self.next_round()

        g = self.get_current_group()
        g.begin_turn()


    def next_round(self):
        if self.round == None:
            self.round = 1
        else:
            self.round += 1
        print('Beginning round {}'.format(self.round))


    def deal_damage(self, index, amount):
        c = self.combatant_hash[index]
        c.damage(amount)


    def heal_damage(self, index, amount):
        c = self.combatant_hash[index]
        c.heal(amount)


    def validate_started(self):
        if not self.is_started():
            print('Error: you can only run this command after starting the battle')
            return False
        return True


battle = Battle()

def main():
    # hard-coding test cases for now.
    # Eventually, use a state-saving text file that's easy to edit
    battle.add_group(CombatGroup("Adele", [Combatant("Adele", hp=26, pc=True, surges=8, sw=1)], 2))
    battle.add_group(CombatGroup("Aristaire", [Combatant("Aristaire", hp=20, pc=True, surges=6, sw=1)], 0))


    battle.add_group(CombatGroup("Foobolds", [Combatant("Foobold", hp=50), Combatant("Foobold", hp=50), Combatant("Foobold", hp=50), Combatant("Foobold", hp=50), Combatant("Foobold", hp=50)], 20))
    battle.add_group(CombatGroup("Barglins", [Combatant("Barglin", hp=1), Combatant("Barglin", hp=1)], 3))
    battle.add_group(CombatGroup("Orcs of Baz", [Combatant("Orc", hp=32), Combatant("Orc", hp=32)], 1))

    # ngroups = input_int('Number of enemy groups:')
    # for i in range(1, ngroups+1):
    #     print("Adding enemy group {}".format(i))
    #     battle.add_group(CombatGroup.from_input())

    print "Welcome to 4e Battle Manager.\n"
    print battle

    while True:
        do_prompt()


def do_prompt():
    print('')
    comm = input_str('', default='?', show_default=False, prompt_str='>')

    if comm == '?':
        do_help()
    elif comm == 'a':
        print('Sorry, this is still a stub function.')
    elif comm == 'l':
        print battle.format_combatants()
    elif comm == 'l':
        print battle.format_current_group()
    elif comm == 'b':
        battle.begin()
    elif comm == 'd':
        do_damage()
    elif comm == 'h':
        do_heal()
    elif comm == 's':
        print('Sorry, this is still a stub function.')
    elif comm == 'c':
        print('Sorry, this is still a stub function.')
    elif comm == 'r':
        print('Sorry, this is still a stub function.')
    elif comm == 'n':
        battle.next_combatant()
    elif comm == 'w':
        print('Sorry, this is still a stub function.')
    elif comm == 'q':
        sys.exit(0)


def do_damage():
    print battle.format_combatants()
    index = input_int('choose combatant')
    amount = input_int('damage')
    battle.deal_damage(index, amount)


def do_heal():
    print battle.format_combatants()
    index = input_int('choose combatant')
    amount = input_int('amount')
    battle.heal_damage(index, amount)


def do_help():
    print("""Possible commands:
?  - print this help menu (yay, you already figured that one out)
a  - add more combatants (works during battle) [stub]
b  - begin the battle
l  - list combatants
p  - print info for combatant/group with initiative
d  - deal damage to someone
h  - heal someone [stub]
s  - let someone use a healing surge [stub]
sw - 
c  - apply a condition [stub]
r  - remove a condition (this can also happen automatically) [stub]
n  - next (end the current combat group's turn) [stub]
w  - wait (remove a combatant from the initiative order and into a separate pool) [stub]
q  - quit""")


if __name__ == '__main__':
    main()