from mudwyrm_users.admin.achaea import ScriptState
from mudwyrm_users.admin.achaea.action import Action, Outcome, EventOutcome
from mudwyrm_users.admin.achaea.trigger import Trigger, Alias, OnEvent, TriggerPack
from mudwyrm_users.admin.achaea.common import not_, traverse_scripts, AttrDict, partition_action
from mudwyrm_users.admin.achaea.database import Base

from mudwyrm_users.admin.achaea.scripts import char
from mudwyrm_users.admin.achaea.scripts.actions import all_actions as actions

import sqlalchemy as sa

p = None
s = ScriptState()

def init(processor):
    assert processor is not None
    global p
    p = processor
    
    s.loot = []
    s.info_here = {}
    s.state = 'inactive'
    s.target = None

def think():
    if s.state == 'inactive':
        return
    if not s.target:
        return combat_echo("No target to fight.")
    if isinstance(s.target, int) and s.target not in room['objects']:
        s.state = 'inactive'
        return combat_echo("Target has been lost. Given up on fighting.")
    cure()
    if s.state == 'attacking':
        attack()
    elif s.state == 'defending':
        defend()
    elif s.state == 'looting':
        loot()
    
##################

def combat_echo(text):
    p.echo("[Combat] %s" % text)
    
def choose_offensive_action(target):
    if char.race == 'Dragon':
        return (actions.gut, target)
    elif char.class_ == 'Sylvan':
        return (actions.thornrend if char.status('viridian')
                else actions.firelash, target)
    elif char.class_ == 'Serpent':
        if char.skill_available('garrote'):
            return (actions.garrote, target)
        else:
            assert char.skill_available('bite')
            venom = 'sumac' if not char.skill_available('camus', 'venom') else 'camus'
            return (actions.venom_bite, venom, target)
    elif char.class_ == 'Shaman':
        return (actions.curse, 'bleed', target)
    elif char.class_ == 'Blademaster':
        return (actions.drawslash, target)
    elif char.class_ == 'Alchemist':
        return (actions.educe_iron, target)
    return None

def choose_defensive_action():
    if char.skill_available('reflection'):
        return (actions.reflection, 'me')
    return None
    
def offensive_mode():
    if s.state == 'defending':
        s.state = 'attacking'
        combat_echo("Switched to offensive mode.")
        
def defensive_mode():
    if s.state == 'attacking':
        s.state = 'defending'
        combat_echo("Switched to defensive mode.")
    
def attack():
    if char.health < char.defensive_health_level:
        defensive_mode()
        return
    action, args = partition_action(choose_offensive_action(s.target))
    if not action:
        return combat_echo("No offensive action was set for this character, not attacking.")
    if action.possible(*args) and not p.action_already_active(action, *args):
        p.act(action, *args)

def defend():
    action, args = partition_action(choose_defensive_action())
    if not action:
        offensive_mode()
        return combat_echo("No defensive action was set for this character, not defending.")
    if action.possible(*args) and not p.action_already_active(action, *args):
        p.act(action, *args)
    if char.health > char.offensive_health_level:
        offensive_mode()

def loot():
    if char.balance('balance') and char.balance('equilibrium'):
        for item in s.loot:
            p.send("get %s" % item)
        s.loot = []
        s.state = 'inactive'
        combat_echo("Finished fighting.")
        
def cure():
    if char.status('loki'):
        if actions.diagnose.possible():
            p.act(actions.diagnose)
        
##########################

        
@Alias(r'^(?:kill|k) (.+)$')
def combat_start(match):
    target = match.group(1)
    if s.state not in ['inactive', 'looting']:
        return combat_echo("Already fighting someone.")
    s.target = target
    s.state = 'attacking'
    combat_echo("Fighting %s" % s.target)
    think()

@Alias(r'^(?:autokill|ak|k)$')
def autotarget_combat_start(match):
    if s.state not in ['inactive', 'looting']:
        return combat_echo("Already fighting someone.")
    def find_target():
        target_list = p.db.query(Target).all()
        for obj in char.room_objects.itervalues():
            for t in target_list:
                if obj['name'].find(t.name) >= 0:
                    return obj
        return None
    target = find_target()
    if not target:
        return combat_echo("No target found.")
    s.target = target['id']
    s.state = 'attacking'
    combat_echo("Target found: %s" % target['name'])
    think()
    
@Alias(r'^(?:stopkill|sk)$')
def combat_stop(match):
    if s.state not in ['inactive', 'looting']:
        s.state = 'inactive'
        combat_echo("Given up on fighting.")
    else:
        combat_echo("Already not fighting")
        
########################

@OnEvent('TargetNotFound')
def on_target_not_found():
    if s.state in ['attacking', 'defending']:
        s.state = 'inactive'
        combat_echo("Target has been lost. Given up on fighting.")
        p.notification("Combat", "Target has been lost.")
        
@OnEvent('CreatureSlain')
def on_creature_slain(name):
    # TODO: check if a creature was the target.
    if s.state in ['attacking', 'defending']:
        combat_echo("Target has been slain.")
        s.state = 'looting'
        p.notification("Combat", "Target has been slain.")
        
@OnEvent('LootDropped')
def on_loot_dropped(name):
    s.loot.append(name)
    
        
    
@Trigger(r'^You have slain (.+), retrieving the corpse\.$')
def creature_slain(match):
    p.raise_event('CreatureSlain', name=match.group(1))
        
@Trigger(r'^A few golden sovereigns spill from the corpse\.$',
         r'^A small quantity of sovereigns spills from the corpse\.$',
         r'^A (?:tiny|small|large) pile of sovereigns spills from the corpse\.$',
         r'^(?:Many|Numerous) golden sovereigns spill from the corpse\.$')
def gold_loot(match):
    p.raise_event('LootDropped', name='money')
    
@Trigger(r'^A glistening iridescent pearl tumbles out of the corpse of a barnacle encrusted oyster\.$',
         r'^A gleaming black pearl tumbles out of the corpse of a marsh ooze\.$')
def pearl_loot(match):
    p.raise_event('LootDropped', name='pearl')
    
@Trigger(r'^A (?:chipped|jagged|smooth) iconic shard appears and clatters to the ground\.$')
def shard_loot(match):
    p.raise_event('LootDropped', name='shard')
    
@Trigger(r'^The Mask of the Beast tumbles out of the corpse of a mysterious cloaked figure\.$')
def mask_of_the_beast_loot(match):
    p.raise_event('LootDropped', name='mask')
    

class info_here(Action):
    def start(action):
        s.info_here.clear()
        p.send("info here")
        
    @Alias(r'^(ih|info here)$')
    def aliases(match):
        p.act(info_here)
    
    @Trigger(r'^(\D+\d+)\s+(.*)$')
    def info_here_line(match, action):
        s.info_here[match.group(1)] = match.group(2)
        
    @Outcome(r'^Number of objects: (\d+)$')
    def info_here_end_line(match, action):
        n = int(match.group(1))
        p.raise_event('InfoHereUpdated')
        if len(s.info_here) != n:
            raise ScriptError("Warning: Number of objects captured from "
                    "'info here' doesn't match the actual number of objects.")
        
@OnEvent('InfoHereUpdated')
def ih_updated():
    pass

class Target(Base):
    __tablename__ = 'targets'
    id = sa.Column(sa.Integer, primary_key=True)
    name = sa.Column(sa.String, nullable=False, unique=True)
    
    def __init__(self, name):
        self.name = name

@Alias(r'^target_list$')
def target_list(match):
    targets = p.db.query(Target).all()
    if not targets:
        p.echo("Target list is empty.")
    else:
        p.echo("Target list: %s." % ", ".join(t.name for t in targets))
        
@Alias(r'^target_add (.*)$')
def target_add(match):
    target = Target(match.group(1))
    p.db.add(target)
    p.db.commit()
    p.echo("%s has been added to the target list." % target.name)

@Alias(r'^target_remove (.*)$')
def target_remove(match):
    name = match.group(1)
    target = p.db.query(Target).filter(Target.name == name).first()
    if not target:
        return p.echo("Target list doesn't contain %s." % name)
    p.db.delete(target)
    p.db.commit()
    p.echo("%s has been removed from the target list." % name)
        