from __future__ import division
import vect, random, pygame, ui, math, constants, resources

class State(object):
    def __init__(self, name):
        # initialises a state of with a name
        self.name = name
    
    def action(self):
        pass
    
    def check_conditions(self):
        pass

    def entry_actions(self):
        pass

    def exit_actions(self):
        pass

class StateMachine(object):
    
    def __init__(self):
        
        # dictionary of states
        self.states = {}
        self.active_state = None
        
    def add_state(self, state):
        
        self.states[state.name] = state
        
    def set_state(self, new_state):
        
        if self.active_state is not None:
            self.active_state.exit_actions()
            
        self.active_state = self.states[new_state]
        self.active_state.entry_actions()
        
    def think(self):
        
        if self.active_state is None:
            pass
        
        self.active_state.action()
        
        new_state = self.active_state.check_conditions()
        if new_state is not None:
            self.set_state(new_state)
            
class StateZombie(State):

    
    def search(self):
        check_dist = vect.distance_to(self.object.destination,self.object.location) - constants.AT_DESTINATION
        if check_dist < 0 or self.object.timer_ai.check():
            # we must be at destination, so pick a new destination
            visible_tiles = list(self.object.visible_tiles)
            if len(visible_tiles):
                # here we make sure we only get an unblocked square
                while True:
                    n = random.randrange(0,len(visible_tiles))
                    dest_grid = visible_tiles[n]
                    square = self.object.world.grid.squares[dest_grid]
                    if not square.blocked:
                        break
                    #break
                self.object.destination = square.location
                self.object.timer_ai.set()

    def react_to_being_shot(self):
        if self.object.hit:
            if self.object.hit_direction:
                dir = self.object.hit_direction
                self.object.timer_ai.set()
                self.object.hit = False
                dist = 10
                delta = dist*math.cos(dir), dist*math.sin(dir)
                self.object.destination = vect.add(1, self.object.location, 1,delta)
    
    def panic_run(self):
        if vect.distance_to(self.object.destination,self.object.location) < constants.AT_DESTINATION:
            # we must be at destination, so pick a new destination
            square_list = list(self.object.world.grid.squares[self.object.grid_location].adjacent_squares)
            if len(square_list):
                # here we make sure we only get an unblocked square
                while True:
                    n = random.randrange(0,len(square_list)-1)
                    dest_grid = square_list[n]
                    square = self.object.world.grid.squares[dest_grid]
                    if not square.blocked:
                        break
                    #break
                self.object.destination = square.location
        
    def look_for_food(self):
        '''makes the zombies hunt the closest food object'''
        # makes a dict of all zombie_food objects
        if self.object.health < self.object.max_health:
            dist_dict = {}
            for object2 in self.object.visible_sims:
                if object2.zombie_food > 0 and object2.alive == False:
                    dist = vect.distance_to(self.object.location, object2.location)
                    if dist < self.object.sight_radius:
                        dist_dict[dist/object2.zombie_food] = object2      
            if dist_dict.keys() == []:
                self.object.target = None
            else:
                # chooses the closest object in dict 
                closest_object = dist_dict[min(dist_dict.keys())]
                self.object.destination = closest_object.location
                self.object.target = closest_object
        else:
            self.object.target = None

    def look_for_prey(self):
        '''makes the zombies hunt the closest alive prey object'''
        # makes a dict of all zombie_food objects
        dist_dict = {}
        for object2 in self.object.visible_sims:
            if object2.zombie_food > 0 and object2.alive == True:
                dist = vect.distance_to(self.object.location, object2.location)
                if dist < self.object.sight_radius:
                    dist_dict[dist/object2.zombie_food] = object2      
        if dist_dict.keys() == []:
            self.look_for_food()
        else:
            # chooses the closest object in dict 
            closest_object = dist_dict[min(dist_dict.keys())]
            self.object.destination = closest_object.location
            self.object.target = closest_object
        
        
class StateZombieExploring(StateZombie):
    
    def __init__(self, object):
        State.__init__(self, "zombie_exploring")
        self.object = object
        
    def entry_actions(self):
        self.object.ai_colour = (0,0,255)
        self.object.acc_mod = 0.75 + random.uniform(-0.25,0.25)

    def action(self):
        self.react_to_being_shot()
        self.search()
        

    def check_conditions(self):
        if self.object.onfire:
            return "zombie_panicking"  
        else:
            self.look_for_prey()
            if self.object.target == None:
                if self.object.health < self.object.max_health and random.randint(1,500)==1:
                    return "zombie_resting"
                else:
                    return None
            else:
                if self.object.target.alive == True:
                    return "zombie_chasing"
                elif self.object.target.alive == False:
                    return "zombie_collecting"

class StateZombiePanicking(StateZombie):
    
    def __init__(self, object):
        State.__init__(self, "zombie_panicking")
        self.object = object
        
    def entry_actions(self):
        self.object.ai_colour = (0,0,255)
        self.search()
        self.object.destination = self.object.location
        self.object.acc_mod = 2
    
    def exit_actions(self):
        pass

    def action(self):
        
        self.panic_run()

    def check_conditions(self):
        if not self.object.onfire:
            return "zombie_exploring"  
            
class StateZombieChasing(StateZombie):
    
    def __init__(self, object):
        State.__init__(self, "zombie_chasing")
        self.object = object
        
    def entry_actions(self):
        self.object.ai_colour = (255,0,0)
        self.object.acc_mod = 2
        if random.random()<0.5:
            resources.play_sound(self.object.sound_groan, resources.attenuate_sound(self.object))

    def action(self):
        self.look_for_prey()
        self.object.attack(self.object.target)
        
    def check_conditions(self):
        if self.object.onfire:
            return "zombie_panicking"  
        else:
            if self.object.target == None:
                self.search()
                return "zombie_exploring"
            else:
                if self.object.target.alive == False:
                    return "zombie_collecting"
                else:
                    return None

        
class StateZombieCollecting(StateZombie):
    
    def __init__(self, object):
        State.__init__(self, "zombie_collecting")
        self.object = object
        
    def entry_actions(self):
        self.object.ai_colour = (0,255,125)
        self.object.acc_mod = 1

    def action(self):
        self.look_for_prey()
        self.object.attack(self.object.target)
        self.react_to_being_shot()
        
    def check_conditions(self):
        if self.object.onfire:
            return "zombie_panicking"  
        else:
            if not self.object.target:
                self.search()
                return "zombie_exploring"
            else:
                if self.object.target.alive:
                    return "zombie_chasing"
                else:
                    return None
                  
class StateZombieResting(StateZombie):
    
    def __init__(self, object):
        State.__init__(self, "zombie_resting")
        self.object = object
        
    def entry_actions(self):
        self.object.ai_colour = (0,0,0)
        self.object.acc_mod = 0
        self.old_health = self.object.health

    def action(self):
        if self.object.health < self.old_health:
            self.taking_damage = True
        else:
            self.taking_damage = False
        self.object.destination = self.object.location
        self.object.health +=self.object.world.dt*self.object.max_health/60
        self.old_health = self.object.health
        
    def check_conditions(self):
        if random.randint(1,1000) == 1 or self.object.health == self.object.max_health or self.taking_damage == True:
            return "zombie_exploring"
        
#================================================================================
# 
#================================================================================


class StateSoldier(State):

    def search(self):
        check_dist = vect.distance_to(self.object.destination,self.object.location) - constants.AT_DESTINATION
        if check_dist < 0 or self.object.timer_ai.check():
            # we must be at destination, so pick a new destination
            visible_tiles = list(self.object.visible_tiles)
            if len(visible_tiles):
                # here we make sure we only get an unblocked square
                while True:
                    n = random.randrange(0,len(visible_tiles))
                    dest_grid = visible_tiles[n]
                    square = self.object.world.grid.squares[dest_grid]
                    if not square.blocked:
                        break
                    #break
                self.object.destination = square.location
                self.object.timer_ai.set()
    
    def panic_run(self):
        if vect.distance_to(self.object.destination,self.object.location) < constants.AT_DESTINATION:
            # we must be at destination, so pick a new destination
            square_list = list(self.object.world.grid.squares[self.object.grid_location].adjacent_squares)
            if len(square_list):
                # here we make sure we only get an unblocked square
                while True:
                    n = random.randrange(0,len(square_list)-1)
                    dest_grid = square_list[n]
                    square = self.object.world.grid.squares[dest_grid]
                    if not square.blocked:
                        break
                    #break
                self.object.destination = square.location
                
    def voluntary_reload(self):
        weapon = self.object.weapon
        
        if weapon:
            if self.object.ammo>0:
                # we force the weapon to be updated
                weapon.reload()
                # clip is not full
                if weapon.clip_ammo < weapon.clip_size*0.8:
                    #lots of spare_ammo
                    if weapon.clip_ammo < weapon.ammo:
                        weapon.reload_weapon()

    def retreat(self):
        zombie_list = []
        for object2 in self.object.visible_sims:
            if object2.faction == "horde":
                dist = vect.distance_to(self.object.location, object2.location)
                if dist < 15:
                    zombie_list.append(object2)
        heading_vect = (0,0)
        for object in zombie_list:
            dist = vect.distance_to(self.object.location, object.location)
            correction_vect = vect.add(1,self.object.location,-1,object.location)
            heading_vect = vect.add(1,heading_vect,1/(dist+0.001)**4,correction_vect)
        heading_vect = vect.normalise(heading_vect,2)
        if random.randint(1,100) == 1:
            self.rand = random.randint(-1,1)
        random_vect = vect.rotate(self.rand*math.pi/2.0,heading_vect)
        heading_vect = vect.add(1,heading_vect,2.0,random_vect)
        vect.normalise(heading_vect)
        self.object.destination = vect.add(1,self.object.location,1,heading_vect)


    def find_pickups(self):
        dist_dict = {}
        for item in self.object.visible_sims:
            dist = vect.distance_to(self.object.location, item.location)
            
            if item.sim_type == "ammo":
                if self.object.ammo < 0.8*self.object.max_ammo:
                    """ the +1 stops frac from being zero which causes ties in dist*frac"""
                    frac = (self.object.ammo+1.0)/self.object.max_ammo
                    dist_dict[dist*frac] = item
                    
            elif item.sim_type == "medkit":
                if self.object.health < 0.8*self.object.max_health:
                    frac = self.object.health/self.object.max_health
                    dist_dict[dist*frac] = item
                    
            elif item.sim_cat == "weapon" and item.mobile:
                if item.sim_type in self.object.weapons.keys():
                    pass
                    #count = self.object.weapons[item.sim_type][0]
                else:
                    #count = 0
                    dist_dict[dist] = item
        if dist_dict.keys() == []:
            return None
        else:
            closest_object = dist_dict[min(dist_dict.keys())]
            self.object.destination = closest_object.location
            return "survivor_collecting"

    def pick_target(self):
        if self.object.weapon:
            if self.object.ammo > 0 or self.object.weapon.sim_type == "Pistol":
                dist_dict = {}
                for object2 in self.object.visible_sims:
                    if object2.faction == "horde":
                        dist = vect.distance_to(self.object.location, object2.location)
                        dist_dict[dist] = object2
                if dist_dict.keys() == []:
                    self.object.target = None
                else:
                    closest_object = dist_dict[min(dist_dict.keys())]
                    self.object.target = closest_object
            else:
                self.object.target = None
        else:
            self.object.target = None
            
    def check_for_zombies(self):
        return_state = None
        for object2 in self.object.visible_sims:
            if object2.faction == "horde":
                dist = vect.distance_to(self.object.location, object2.location)
                if dist < 10:
                    return_state = "survivor_retreating"
        return return_state

class StateSoldierPanicking(StateSoldier):
    
    def __init__(self, object):
        State.__init__(self, "survivor_panicking")
        self.object = object
        
    def entry_actions(self):
        self.object.ai_colour = (0,0,255)
        self.object.destination = self.object.location
        self.object.acc_mod = 2
    
    def exit_actions(self):
        pass

    def action(self):
        
        self.panic_run()

    def check_conditions(self):
        if not self.object.onfire:
            return "survivor_exploring"  
            
            
class StateSoldierResting(StateSoldier):
    
    def __init__(self, object):
        State.__init__(self, "survivor_resting")
        self.object = object
        
    def entry_actions(self):
        self.object.ai_colour = (0,0,0)
        self.object.acc_mod = 0
        
    def exit_actions(self):
        pass
 
    def action(self):
        self.object.destination = self.object.location
        
    def check_conditions(self):
        if self.object.onfire:
            return "survivor_panicking"  
        else:
            if random.randint(1,100) == 1:
                return "survivor_exploring"
        
class StateSoldierExploring(StateSoldier):
    
    def __init__(self, object):
        State.__init__(self, "survivor_exploring")
        self.object = object
        
    def entry_actions(self):
        self.object.ai_colour = (0,0,255)
        self.object.acc_mod = 0.8
        self.object.target = None
        
    def exit_actions(self):
        pass

    def action(self):
        # we only voluntarily reload while the player is safe
        self.voluntary_reload()
        self.search()

    def check_conditions(self):
        if self.object.onfire:
            return "survivor_panicking"  
        else:
            if self.find_pickups() is "survivor_collecting":
                return "survivor_collecting"
            if self.check_for_zombies() is "survivor_retreating":
                return "survivor_retreating"
       
class StateSoldierCollecting(StateSoldier):
    
    def __init__(self, object):
        State.__init__(self, "survivor_collecting")
        self.object = object
                
    def entry_actions(self):
        self.object.ai_colour = (0,255,0)
        self.object.acc_mod = 1
        
    def exit_actions(self):
        pass
 
    def action(self):
        self.pick_target()

    def check_conditions(self):
        if self.object.onfire:
            return "survivor_panicking"  
        else:
            if self.find_pickups() == None:
                self.search()
                return "survivor_exploring"
            if self.check_for_zombies() == "survivor_retreating":
                return "survivor_retreating"

class StateSoldierRetreating(StateSoldier):
    
    def __init__(self, object):
        State.__init__(self, "survivor_retreating")
        self.object = object
        self.rand = 0
                
    def entry_actions(self):
        self.object.ai_colour = (255,0,0)
        
    def exit_actions(self):
        # stops the sim trying to use "bad" retreat locations
        self.object.destination = self.object.location
 
    def action(self):
        self.retreat()
        self.pick_target()

    def check_conditions(self):
        if self.object.onfire:
            return "survivor_panicking"  
        else:
            if not self.check_for_zombies():
                return "survivor_exploring"
            else:
                return None

#===============================================================================
# #               
#===============================================================================
        
class StateSoldierWeapon(State):
    def __init__(self):
        # initialises a state of with a name
        pass

    def entry_actions(self):
        pass
        
    def action(self):
        pass
        
    def check_conditions(self):
        return self.pick_new_weapon()
        
    def exit_actions(self):
        pass



    def check_ammo_count(self):
        ammo = self.object.weapons[self.name][2]
        if not ammo:
            return "Pistol"
        
    def pick_new_weapon(self):
        """ Returns the best weapon
        
        Picks the next best weapon the sim has, unless the sim has no ammo
        in which case it picks a pistol
        to be developed further once i have separate ammo types
        or make the sims know about weapon length
        """
        if self.object.weapon:
            current_num = self.object.weapon.weapon_num
        else:
            current_num = 0
        new_weapon = None
        if len(self.object.inv.list_types())>0:
            for item in self.object.inv.list_items():
                if item.sim_cat is "weapon":
                    if item.weapon_num >= current_num:
                        # fix this!!! we need an ammo pool for the weapon, not just its clip_ammo
                        item.update_ammo()
                        if item.ammo > 0:
                            new_weapon =  item.sim_type
        if new_weapon is None:
            new_weapon = "Pistol"
        return new_weapon
        
class StateSoldierWeaponNone(StateSoldierWeapon):
    
    def __init__(self, object):
        State.__init__(self, "None")
        self.object = object
        
##    def entry_actions(self):
##        pass
##    
##    def action(self):
##        pass
##    
##    def check_conditions(self):
##        return self.pick_new_weapon()
####        if len(self.object.weapons.keys())>0:
####            for key in self.object.weapons.keys():
####                if self.object.weapons[key][0] > 0:
####                    return key      
    
class StateSoldierWeaponPistol(StateSoldierWeapon):
    
    def __init__(self, object):
        State.__init__(self, "Pistol")
        self.object = object
        
    def entry_actions(self):
        self.object.switch_weapon_to(self.name)
##    
##    def action(self):
##        pass
##    
##    def check_conditions(self):
##        return self.pick_new_weapon()
####        ammo = self.object.ammo
####        for key in self.object.weapons.keys():
####            if self.object.weapons[key][0] > 0:
####                if self.object.weapons[key][2] > 0:
####                    if key is not "Pistol":
####                        return key
##                
##    def exit_actions(self):
##        pass
    
class StateSoldierWeaponSMG(StateSoldierWeapon):
    
    def __init__(self, object):
        State.__init__(self, "SMG")
        self.object = object
        
    def entry_actions(self):
        self.object.switch_weapon_to(self.name)
##    
##    def action(self):
##        pass
##    
##    def check_conditions(self):
##        return self.pick_new_weapon()
##                
##    def exit_actions(self):
##        pass

class StateSoldierWeaponAR(StateSoldierWeapon):
    
    def __init__(self, object):
        State.__init__(self, "AssaultR")
        self.object = object
        
    def entry_actions(self):
        self.object.switch_weapon_to(self.name)
##    
##    def action(self):
##        pass
##    
##    def check_conditions(self):
##        return self.pick_new_weapon()
##                
##    def exit_actions(self):
##        pass
    
class StateSoldierWeaponSR(StateSoldierWeapon):
    
    def __init__(self, object):
        State.__init__(self, "SniperR")
        self.object = object
        
    def entry_actions(self):
        self.object.switch_weapon_to(self.name)
##    
##    def action(self):
##        pass
##    
##    def check_conditions(self):
##        return self.pick_new_weapon()
##                
##    def exit_actions(self):
##        pass
##    
class StateSoldierWeaponLR(StateSoldierWeapon):
    
    def __init__(self, object):
        State.__init__(self, "LaserR")
        self.object = object
        
    def entry_actions(self):
        self.object.switch_weapon_to(self.name)
##    
##    def action(self):
##        pass
##    
##    def check_conditions(self):
##        return self.pick_new_weapon()
##                
    def exit_actions(self):
        pass
class StateSoldierWeaponGR(StateSoldierWeapon):
    
    def __init__(self, object):
        State.__init__(self, "GaussR")
        self.object = object
        
    def entry_actions(self):
        self.object.switch_weapon_to(self.name)
##    
##    def action(self):
##        pass
##    
##    def check_conditions(self):
##        return self.pick_new_weapon()
##                
##    def exit_actions(self):
##        pass
class StateSoldierWeaponMG(StateSoldierWeapon):
    
    def __init__(self, object):
        State.__init__(self, "MiniGun")
        self.object = object
        
    def entry_actions(self):
        self.object.switch_weapon_to(self.name)

class StateSoldierWeaponSG(StateSoldierWeapon):
    
    def __init__(self, object):
        State.__init__(self, "Shotgun")
        self.object = object
        
    def entry_actions(self):
        self.object.switch_weapon_to(self.name)
