import pygame
from pygame.locals import *

import os
import random

from locals import *

import pathfind
from obj import Object

class Character (Object):
    def __init__(self, name, pos):
        Object.__init__(self, name, pos)

        self.target = pos
        try:
            self.portrait = pygame.image.load(os.path.join("ui", name + ".jpg")).convert_alpha()
            self.portrait_hilight = pygame.image.load(os.path.join("ui", name + "_hilight.jpg")).convert_alpha()
        except:
            # This is probably a zombie then
            pass
        self.path = []
        self.objtype = TYPE_CHARACTER
        self.attacktarget = None
        self.last_action = pygame.time.get_ticks()
        self.last_path_check = pygame.time.get_ticks()
        self.initialattackdist = 0
        self.iteminuse = None
        self.targetobj = None
        self.stunned = 0
        self.idle = True
        for item in self.inventory:
            if item.is_weapon():
               self.iteminuse = item
        self.soundcount = 0 # Hackety hack hack.

    def move_to(self, map_, pos):
        #self.target = pos
        self.idle = False
        self.path = pathfind.find_path(map_, self.pos, pos, self, None)
        if self.path == None:
            self.path = []
            # If there's no path, don't try to go there
            self.stop()
        else:
            #print self.path
            self.target = self.path[0]
            #self.path = self.path[1:]
        self.update_direction()

    def draw(self, screen, offset):
        if self.dead and self.death_time == -1:
            return
        else:
            Object.draw(self,screen,offset)

    #Moving without pathfinding. Zombies use this for their random movement.
    def move_to_dumb(self, pos):
        #print "dumb"
        self.idle = False
        self.target = pos
        self.update_direction()

    def move_to_obj(self, map_, obj):
        #print "move to " + str(obj)
        self.idle = False
        self.targetobj = obj
        self.path = pathfind.find_path(map_, self.pos, obj.pos, self, obj)
        #print self.path
        #print self.pos, obj.pos
        if self.path == None:
            self.path = []
            self.stop()
        else:
            self.target = self.path[0]
        self.update_direction()

    def update_direction(self):
        if not self.target:
            return
        diff = (self.target[0] - self.pos[0],
                self.target[1] - self.pos[1])
        self.set_animation(self.get_direction(diff))

    def get_direction(self, diff):
        if abs(diff[0]) > abs(diff[1]):
            if diff[0] > 0:
                return "west"
            else:
                return "east"
        else:
            if diff[1] > 0:
                return "north"
            else:
                return "south"

    def update(self, map_):
        if self.idle and not self.dead:
            self.choose_action(map_)
        if self.current_animation.name.startswith("dying") and self.current_animation.frame == self.current_animation.frames - 1:
            self.set_animation("dead")
        if self.dead:
            Object.update(self, map_)
            return

        if self.stunned > 0:
            self.stunned -= 1
            if not self.current_animation.name.startswith("stun_"):
               self.idle = True
               self.set_animation("stun_" + self.direction_from_anim())
            Object.update(self, map_)
            return

        changed = [False, False]
        if self.dir[0] < 0:
            if self.target[0] - self.pos[0] > 0:
                changed[0] = True
        elif self.dir[0] > 0:
            if self.target[0] - self.pos[0] < 0:
                changed[0] = True
        if self.dir[1] < 0:
            if self.target[1] - self.pos[1] > 0:
                changed[1] = True
        if self.dir[1] > 0:
            if self.target[1] - self.pos[1] < 0:
                changed[1] = True

        if changed[0] or changed[1]:
            self.update_direction()

        # The commented code on the next line checks the exact position of the character, and will probably be reused when the pathfinding algorithm is revised.
        if (self.dir[0] != 0 or self.dir[1] != 0) and abs(self.target[0] - self.pos[0]) + abs(self.target[1] - self.pos[1]) <= 0.2: #2 * (abs(self.dir[0]) + abs(self.dir[1])):
            #if self.targetobj:
# this is a bit of a resource hog
                #self.move_to_obj(map_, self.targetobj)
            #    pass
            if len(self.path) != 0:
                self.target = self.path[0]
                self.path = self.path[1:]
                self.update_direction()
                if self.targetobj and (self.last_path_check - pygame.time.get_ticks()) > PATH_CHECK_DELAY:
                    #This still generates a whole lot of pathfinding calls (pretty much most of them). Some of the paths are a bit buggy.
                    self.move_to_obj(map_, self.targetobj)
                    # This should fix some of that:
                    self.last_path_check = pygame.time.get_ticks()
            else:
                self.stop()

        if self.attacktarget:
            if self.attacktarget.dead:
                self.stop()
            else:
                dx = self.attacktarget.pos[0] - self.pos[0]
                dy = self.attacktarget.pos[1] - self.pos[1]
                if pathfind.line_of_sight(map_, self.pos, self.attacktarget.pos, self, self.attacktarget):
# This range measurement is not very good, because if the range is smaller than
# the objects' sizes summed, you can never actually attack. Might not become
# a problem though as long as the players and enemies are roughly the same
# size.
                    action = self.iteminuse.char_animation
                    attackdist = self.distance(self.attacktarget)
                    if attackdist <= self.iteminuse.range:

                        if not self.current_animation.name.startswith(action):
                            if self.iteminuse.ammo and self.getammo(self.iteminuse.ammo) == 0:
                                 self.switch_item()
                                 self.attack(map_, self.attacktarget)
                            else:
                                 self.set_animation(action + "_" + self.get_direction((dx,dy)))

# A bit of a hack here, but should work. Damage is given at the last frame.
                        if self.current_animation.frame == self.current_animation.frames - 1:
                            if self.iteminuse.ammo and self.getammo(self.iteminuse.ammo) == 0:
                                self.switch_item()
                                self.attack(map_, self.attacktarget)
                            else:
                                try:
                                    sound = pygame.mixer.Sound("sounds/" + action + ".wav")
                                    sound.set_volume(0.5)
                                    if action != "shooting" or self.soundcount <= 0:
                                        sound.play()
                                        self.soundcount = 3
                                    self.soundcount -= 1
                                except:
                                    pass
    
                                if self.iteminuse.ammo:
                                    self.spendammo(self.iteminuse.ammo, 1)
    
                                #The shots get more inaccurate as the distance increases
                                #if self.iteminuse.accrange < attackdist:
                                    #hitodds = self.iteminuse.accuracy * (1 - (attackdist - self.iteminuse.accrange) / (self.iteminuse.range - self.iteminuse.accrange))
                                #else:
    
                                hitodds = self.iteminuse.accuracy
                                if random.random() < hitodds:
                                    self.attacktarget.take_damage(self.iteminuse.damage)
                                    self.attacktarget.get_stunned(self.iteminuse.stun)
                                    if self.attacktarget.dead:
                                       self.stop()
                                else:
                                    print "missed"
                                    #pass
                    elif self.current_animation.name.startswith(action):
                        self.attack(map_, self.attacktarget)

        Object.update(self, map_)

    def stop(self):
        self.attacktarget = None
        self.targetobj = None
        self.target = None
        self.idle = True
        self.soundcount = 0
        self.path = []
        Object.stop(self)

    def look_at(self, target):
        diff = (target.pos[0] - self.pos[0], target.pos[1] - self.pos[1])
        self.set_animation("idle_" + self.get_direction(diff))

    def attack(self, map_, target):
        attackdist = self.distance(target)
        #To avoid unnecessary pathfinding (if for example the player clicks the target several times).
        if target == self.attacktarget and (not self.idle) and (attackdist <= self.initialattackdist):
           self.initialattackdist = attackdist
           return
        self.idle = False
        self.initialattackdist = attackdist
        self.attacktarget = target
        self.move_to_obj(map_, target)

    def getammo(self, ammotype):
        for item in self.inventory:
            if item.name == "ammo_" + ammotype:
               return item.amount
        return 0

    def spendammo(self, ammotype, amount):
        for item in self.inventory:
            if item.name == "ammo_" + ammotype:
               item.amount -= amount
               if item.amount < 0:
                  print "negative ammo. this shouldn't happen!"
                  item.amount = 0

    def switch_item(self):
        choose_next = False
        first_usable_found = None
        itemtouse = None
        for item in self.inventory:
            if item.is_weapon():
               if not first_usable_found:
                  first_usable_found = item
               if choose_next:
                  itemtouse = item
                  choose_next = False
               if self.iteminuse == item:
                  choose_next = True
        if itemtouse:
            self.iteminuse = itemtouse
        else:
            self.iteminuse = first_usable_found

    def take_damage(self, damage):
        print self.name + " taking damage"
        self.health -= damage
        if (self.health <= 0):
            print "dead"
            self.dead = True
            self.death_time = pygame.time.get_ticks()
            self.set_animation("dying_south")
            if (self.objtype == TYPE_CHARACTER):
                self.portrait = pygame.image.load(os.path.join("ui", "chardead.png")).convert_alpha()
                self.portrait_hilight = self.portrait
            if (self.objtype == TYPE_ENEMY):
                Score.zombies_dead += 1        

    def get_stunned(self, stuntime):
        if self.stunned < stuntime:
           self.stunned = stuntime

    def choose_action(self, map_):
       if pygame.time.get_ticks() - self.last_action < ACTION_CHECK_DELAY:
           return
       self.last_action  = pygame.time.get_ticks()
       atarget = None
       atargetdist = CHAR_AUTOLOOK_DISTANCE
       for o in map_.objects:
           if o.objtype == TYPE_ENEMY and (not o.dead) and self.distance(o) < atargetdist and pathfind.line_of_sight(map_, self.pos, o.pos, self, o):
                  atarget = o
                  atargetdist = self.distance(o)

       if atarget:
           if atargetdist < CHAR_AUTOATTACK_DISTANCE:
               self.attack(map_, atarget)
           else:
               self.look_at(atarget)
       #Running away might be implemented too? Perhaps one of the characters is too afraid of the zombies to attack them automatically?


