''' 
    Copyright (c) 2012, Steve Peck aka gr3yh47 <gr3yh47@gmail.com>
    See COPYING.txt for full license text


    sprites.py
    This file is part of Bright Flame Tactics.

    Bright Flame Tactics is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Bright Flame Tactics is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Bright Flame Tactics.  If not, see <http://www.gnu.org/licenses/>.
    
    some code derived from qq, Copyright (c) 2008, 2009 Radomir Dopieralski <qq@sheep.art.pl>. see qq.rar for source and license
'''
import ConfigParser
import pygame
import pygame.locals as pg
import random
from collections import deque, defaultdict
from map import *
from status import *
from items import *

'''A Sprite group that sorts them by depth.'''
class SortedUpdates(pygame.sprite.RenderUpdates):
    def sprites(self):
        return sorted(self.spritedict.keys(), key=lambda sprite: sprite.depth)
   

'''Base class for any Sprite including NPCs that just talk'''
class Sprite(pygame.sprite.Sprite):
    """Sprite for animated items and base class for Player."""

    is_active = False #will be used to set the active character the player is controlling in battle

    def __init__(self, pos=(0, 0), frames = None, multi = None):
        super(Sprite, self).__init__()
        if multi:
            if frames:
                self.frames = {}
                for action in multi:
                    self.frames[action] = frames[action]
            self.image = self.frames['walk'][0][0]
        else:
            if frames:
                self.frames = frames
            self.image = self.frames[0][0]
        self.rect = self.image.get_rect()
        self.animation = self.stand_animation()
        self.pos = pos
        self.scale = False
        

    def _get_pos(self):
        """Check the current position of the sprite on the map."""
        return (self.rect.center[0]-16)/32, (self.rect.center[1])/32

    def _set_pos(self, pos):
        """Set the position and depth of the sprite on the map."""

        self.rect.center = pos[0]*32+16, pos[1]*32
        self.depth = self.rect.center[1]

    pos = property(_get_pos, _set_pos)

    def _get_abs(self):
        '''check the absolute pixel position of the sprite on the map by topleft'''
        return self.rect.topleft[0], self.rect.topleft[1]
        
    def _set_abs(self, abs_pos):
        '''set the absolute pixel position and depth of the sprite on the map'''
        self.rect.topleft = abs_pos[0], abs_pos[1],
        
    abs = property(_get_abs, _set_abs)
    
    def move(self, dx, dy):
        """Change the position of the sprite on screen."""

        self.rect.move_ip(dx, dy)
        self.depth = self.rect.midbottom[1]

    def stand_animation(self):
        """The default animation."""

        while True:
            # Change to next frame every two ticks
            for frame in self.frames[0]:
                self.image = frame
                yield None
                yield None

    def update(self, *args):
        """Run the current animation."""
        try:
            self.animation.next()
        except StopIteration:
            pass
   
    def resize(self, image = None, multiplier = 2):
    
        if not image:
            image = self.image
        width = image.get_width()
        height = image.get_height()
        new_width = int(width * multiplier)
        new_height = int(height * multiplier)
        return pygame.transform.scale(image, (new_width, new_height))    

   
class Selection(Sprite):
    def __init__(self, filename = 'selection3.png', size = 32):
        self.tc = TileCache(filename, size)
        self.depth = 0
        self.i = 0
        super(Selection, self).__init__((0,0), self.tc.cache)
        
    def _get_pos(self):
        """Check the current position of the sprite on the map."""
        return (self.rect.midbottom[0]-16)/32, (self.rect.midbottom[1]-32)/32
        
    def _set_pos(self, pos):
        """Set the position and depth of the sprite on the map."""
        
        self.rect.midbottom = pos[0]*32+16, pos[1]*32+32, 
        
    pos = property(_get_pos, _set_pos)
     
    
class Numbers(Sprite):
    def __init__(self, color = 'white', digit = 'ones'):
        self.tc = TileCache(color + digit + '.png')
        super(Numbers, self).__init__((0, 0), self.tc.cache)
        self.depth = 10000
    
    def num_animation(self, num):
        for frame in [0, 1, 2, 3]:
            self.image = self.resize(self.frames[num][frame], 4)
            yield None
            yield None
            yield None
        
    def _get_pos(self):
        """Check the current position of the sprite on the map."""
        return (self.rect.midbottom[0]-16)/32, (self.rect.midbottom[1]-32)/32
        
    def _set_pos(self, pos):
        """Set the position and depth of the sprite on the map."""
        
        self.rect.midbottom = pos[0]*32+16, pos[1]*32+32, 
        
    pos = property(_get_pos, _set_pos)
        
class BattleMessage(Sprite):
    def __init__(self, name = 'crit', size = 128):
        self.tc = TileCache(name + '.png', size)
        super(BattleMessage, self).__init__((0, 0), self.tc.cache)
        self.depth = 10000
        self.image = self.frames[0][0]
        
    def message_animation(self):
        for frame in xrange(3):
            self.image = self.frames[0][frame]
            yield None
   
'''base class for battle characters'''
class Character(Sprite):  
    def __init__(self, cls, team = 'a', sp = 0, dodge = None, pos = (1, 1)):
    
        self.cls = cls #character class, as in fighter mage etc
        
        parser = ConfigParser.ConfigParser()
        parser.read(os.path.join('.', 'configs', 'classes.stat'))
        
        if dodge:
            actions.append('dodge')
            
        self.img_dir = os.path.join('tilesets', 'characters')
        base_dir = os.path.join(self.img_dir, 'base')
        self.tc = TileCache("male_", 64, 64, self.actions, base_dir, True)
        super(Character, self).__init__(pos, self.tc.caches, self.actions)
        #self.naked_frames = TileCache("male_", 64, 64, self.actions, base_dir, True).caches
        #self.haircut(6)
        self.stats = {}
        self.direction = 2
        self.animation = None
        self.image = self.frames['walk'][0][self.direction]     

        self.name = None        
        
        
        #primary and secondary weapon
        self.p_weapon = None
        self.s_weapon = None
        #currently equipped weapon will be changed by Weapon.equip()
        self.e_weapon = None
        
        #stats are stored in a dict so that they can be iterated through for diplaying
        for opt, val in  parser.items(cls):
            if len(opt) == 3:
                self.stats[opt] = int(val)
            
        self.sp = sp #stat points to upgrade stats at char creation and level up 
        if self.sp: #starting sp only granted to captain, who starts at lvl 2
            self.stats['lvl'] = 2
            self.stats['xp'] = 10
            self.stats['nxt'] = 20
        else:
            self.stats['lvl'] = 1
            self.stats['xp'] = 0 #experience points start at 0...maybe 
            self.stats['nxt'] = 10 #xp required for next level
        self.xpstep = 10 #used to calculate how much xp needed till next level after a level up. increases by 1.5 each level.
        self.stats['hp'] = self.stats['max']  #hitpoints
        self.mounted = 0 #might be used to allow mounted units to move again after attacking
        self.armor = 0 #will be modified by equipment (armor), equip method will encompass all weapons/armor stat changes.
        self.team = team #will be used to ID friend vs foe.
        self.bonuses = defaultdict(lambda:0) #bonuses from equipment.        
        
        self.dress_up()
        
        self.x, self.y = 0, 0
        self.valid_moves = []
        self.pos = (self.x, self.y)
     
        self.collection = Collection(self)
        
        self.inventory = Inventory(self) #list of Items
    
    # def _get_pos(self):
        # """Check the current position of the sprite on the map."""
        # print self.rect.midbottom[1], self.rect.midbottom[0]
        # print (self.rect.midbottom[1]-32)/32, (self.rect.midbottom[0]-16)/32
        # return (self.rect.midbottom[1]-32)/32, (self.rect.midbottom[0]-16)/32

    # def _set_pos(self, pos):
        # """Set the position and depth of the sprite on the map."""

        # self.rect.midbottom = pos[1]*32+16, pos[0]*32+32, 
        # self.depth = self.rect.midbottom[0]

    # pos = property(_get_pos, _set_pos) 
    
    def draw_weapons(self):
        if self.p_weapon:
            self.p_weapon.draw_weapon(self.frames)
        if self.s_weapon:
            self.s_weapon.draw_weapon(self.frames)
        
    def dress_up(self, bottom = False, size = 64):
        clothes_dir = os.path.join(self.img_dir, 'outfits', self.team)
        self.clothes_frames = TileCache(self.cls.lower() + '_', size, size, self.actions, clothes_dir).caches
        if bottom:
            pos = ((320-size)/2, (320-size)/2 + 64)
        else:
            if size != 320:
                pos = ((320-size)/2, (320-size)/2)
            else:
                pos = (0, 0)
       
        for action in self.clothes_frames:
            i = 0
            for row in self.frames[action]:
                j = 0
                for image in row:
                    try:
                        clothes = self.clothes_frames[action][i][j]
                        self.frames[action][i][j].blit(clothes, pos)
                    except IndexError:
                        quit()
                    j += 1
                i += 1
    
    def haircut(self, number = 0, color = 'orange'):
        hair_dir = os.path.join(self.img_dir, 'hair')
        hair_types = TileCache(color + 'hairmale.png', 64, 40, None, hair_dir).cache
        hair = []
        for i in xrange(len(hair_types)):
            for j in xrange(len(hair_types[i])):
                if j == number:
                    hair.append(hair_types[i][j])
        sword = [[(0,0), (0,0), (0,0), (0,0), (0,0), (0,0)], [(0,0), (0,0), (0,0), (-1,0), (-3,0), (-1,0)], 
                [(0,0), (0,0), (0,0), (0,0), (0,0), (0,0)], [(0,0), (0,0), (0,0), (1,0), (3,0), (1,0)]]
        hurt = [[(0,0), (0,2), (0,10), (0,12), (0,24), (0,36)]]
        offset = {'sword': sword, 'hurt': hurt}
        
        for action in self.frames:
            i = 0
            for row in self.frames[action]:
                j = 0
                for image in row:
                    if action in offset:
                        pos = (offset[action][j][i][0] + 128, offset[action][j][i][1] + 128)
                    else:
                        pos = (128,128)
                    self.frames[action][i][j].blit(hair[j], pos)
                    j += 1
                i += 1
    
    def confirm(self):
        return self.collection.image
    
    def stat_change(self, stat, diff):
        self.stats[stat] += diff
    
    def get_stat(self, stat):
        return self.stats[stat] + self.bonuses[stat]
      
    def armor_rating(self, piercing): #takes the opponent's piercing value\
        '''return an armor rating for the character to be used in calculations'''
        return max(self.armor - piercing, 0) #return a minimum of 0
        
    def get_luck(self, modifier = 0):
        modifier = max(modifier, 0)
        random.seed()
        test = random.randint(0, 50) 
        if test >= (50 - (self.get_stat('lck') + modifier)):
            #print 'LUCKY!'
            self.stats['xp'] += 1 #lucky rolls give the character an xp point
            return 1 #lucky
        elif test == 0:
            #print 'FAIL!'
            return -1 #natural fail
        else:
            #print 'NOT LUCKY'
            return 0 #not lucky
    
    def walk_animation(self):
        '''character walk animation'''
        # This animation is hardcoded for 4 frames and 32x32 map tiles
        for frame in xrange(1,9):
            self.image = self.frames['walk'][frame][self.direction]
            num = 4
            self.move(num*DX[self.direction], num*DY[self.direction])
            yield None
     
    def pre_atk_animation(self):
        self.image = self.resize(self.frames['walk'][0][self.direction], 4)
        for i in [15, 15, 15, 15, 10, 10, 10, 10, 8, 8, 6, 6]:
            self.move(DX[self.direction]*i, DY[self.direction] * i)
            yield None
    
    def atk_animation(self, dir):#, apos, tpos):
        '''character attack animation'''
        x = DX[dir] * -1
        y = DY[dir] * -1
        #ax, ay = apos
        #tx, ty = tpos
        #x, y = ax - tx, ay - ty
        #print x, y
        frame = 0
        num_frames = len(self.frames[self.action])
        num2 = num_frames /2
        num1 = num_frames - num2
        l = [-2] * (num1) + [2] * (num2)
        px_per = 120 / num_frames
        for i in l:                 
            self.move(x * px_per * i , y * px_per * i)
            self.image = self.resize(self.frames[self.action][frame][self.direction], 4)
            frame += 1
            # if num2 > num1 and frame == num_frames:
                # self.move(x * px_per, y * px_per)
            yield None
        
    def ranged_atk_animation(self):
        for frame in xrange(len(self.frames[self.action])):
            if frame == 0:
                self.play_sound = True
            else:
                self.play_sound = False
            self.image = self.resize(self.frames[self.action][frame][self.direction], 4)
            yield None
         
    def hurt_animation(self, num_frames):
        
        for px in ([0] * (num_frames - 4) * 2 + [-4, 8, -8, 12, -8, 12, -12, 0, 0]):
            if px <> 0:
                self.image = self.resize(self.frames['spell'][2][self.direction], 4)
            else:
                self.image = self.resize(self.frames['walk'][0][self.direction], 4)
            if px == -4:
                self.play_sound = True
            else:
                self.play_sound = False
            self.move(DY[self.direction] * px, DX[self.direction] * px)
            yield None

    def dodge_animation(self, num_frames):
        #dodges are perpendicular to strikes
        x = DY[self.direction]
        y = DX[self.direction]
        num_half = (num_frames + 1) / 2
        px_num = 240/num_frames
        l = [-px_num] * num_half + [px_num] * num_half
        i = 0
        for px_per in l:
            if i == 2:
                self.play_sound = True
            else:
                self.play_sound = False
            self.move(x*px_per, y*px_per)
            i += 1
            yield None
            
    def update(self, *args):
        '''move to the next frame of animation'''
        if self.animation is None:
            if self.scale == True:
                self.image = self.resize(self.frames['walk'][0][self.direction], 4)
            else:
                self.image = self.frames['walk'][0][self.direction]
        else:
            try: 
                self.animation.next()
            except StopIteration:
                self.animation = None
        
    def in_range(self, op_x, op_y):
        '''determines if self has a weapon within range of attacker (for counterattack)'''
        range = abs(self.x - op_x) + abs(self.y - op_y)
        if self.p_weapon.d_range >= range or range in self.p_weapon.i_range:
            return self.p_weapon
        elif self.s_weapon:
            if self.s_weapon.d_range >= range or range in self.p_weapon.i_range:
                return self.s_weapon
            else: 
                return None
        else:
            return None
            
    
    # def choose_weapon(self):
        # '''DEPRECATED: choose weapon to attack with via command line'''
        # if not self.s_weapon:
            # return self.p_weapon
        # while True:
            # print 'p =', self.p_weapon.name
            # print 's =', self.s_weapon.name
            # choice = raw_input('choose weapon: ')
            # if choice == 'p':
                # self.p_weapon.equip()
                # return self.p_weapon
            # elif choice == 's':
                # self.s_weapon.equip()
                # return self.s_weapon
            # else:
                # print 'invalid choice\n'
        
    def check_lvl(self, screen):
        '''checks for level up condition'''
        while self.stats['xp'] >= self.stats['nxt']:
            self.level_up(screen)
    
    def level_up(self, screen):
        '''levels up the character'''
        self.display_stats(screen)
        self.stats['lvl'] += 1
        self.xpstep = int(round(self.xpstep * 1.5))
        self.stats['nxt'] += self.xpstep
        self.upgrade_stats()
        self.collection.level_up()
        self.show_upgrades(screen)
        
    def display_stats(self, screen):
        lvl_display = pygame.image.load(os.path.join('.', 'graphics', 'levelbg.png')).convert_alpha()
        lvl_display.blit(self.collection.image, (50, 80))
        img_x = (screen.get_width() - lvl_display.get_width() ) / 2
        img_y = (screen.get_height() - lvl_display.get_height() ) / 2
        screen.blit(lvl_display, (img_x, img_y))
        pygame.display.flip()
        clock = pygame.time.Clock()
        for i in xrange(30):
            pygame.event.pump()
            clock.tick(15)
        pass #TBI
        
    def upgrade_stats(self):
        old_max = self.stats['max']
        for stat in self.stats.iterkeys():
            if stat == 'lvl' or stat == 'nxt' or stat == 'hp' or stat == 'xp':
                pass
            elif stat == 'max':
                 self.stats[stat] +=  self.stats[stat]/2
            else:
                 self.stats[stat] += 1
        self.stats['hp'] += self.stats['max'] - old_max
        
    
    def show_upgrades(self, screen):
        up_sound = pygame.mixer.Sound(os.path.join('.', 'sounds', 'lvlup.wav'))
        clock = pygame.time.Clock()
        for i in xrange(6):
            up_sound.play()
            upgrade_img = pygame.image.load(os.path.join('.', 'graphics', 'levelup.png')).convert_alpha()
            self.collection.refresh()
            lvl_display = pygame.image.load(os.path.join('.', 'graphics', 'levelbg.png')).convert_alpha()
            lvl_display.blit(self.collection.image, (50, 80))
            lvl_display.blit(upgrade_img, (50, 80 - i))
            img_x = (screen.get_width() - lvl_display.get_width() ) / 2
            img_y = (screen.get_height() - lvl_display.get_height() ) / 2
            screen.blit(lvl_display, (img_x, img_y))
            pygame.display.flip()
            pygame.event.pump()
            clock.tick(15)
        for i in xrange(26):
            upgrade_img = pygame.image.load(os.path.join('.', 'graphics', 'levelup.png')).convert_alpha()
            self.collection.refresh()
            lvl_display = pygame.image.load(os.path.join('.', 'graphics', 'levelbg.png')).convert_alpha()
            lvl_display.blit(self.collection.image, (50, 80))
            img_x = (screen.get_width() - lvl_display.get_width() ) / 2
            img_y = (screen.get_height() - lvl_display.get_height() ) / 2
            screen.blit(lvl_display, (img_x, img_y))
            pygame.display.flip()
            pygame.event.pump()
            clock.tick(15)
    
class Swordsman(Character):
    def __init__(self, team, sp = 0):
        self.actions = ['sword', 'spear', 'bow', 'walk', 'spell']
        super(Swordsman, self).__init__('Swordsman', team, sp)
        self.armor = 1
        
        
class Spearman(Character):
    def __init__(self, team, sp = 0):
        self.actions = ['spear', 'sword', 'walk', 'spell']
        super(Spearman, self).__init__('Spearman', team, sp)
        self.armor = 2
     
class Mage(Character):
    def __init__(self, team, sp = 0):
        self.actions = ['spell', 'walk', 'sword']
        super(Mage, self).__init__('Mage', team, sp)
        #copy sword frames into a new dodge animation
        self.frames['dodge'] = []
        for images in self.tc.caches['sword'][:4] + self.tc.caches['sword'][3::-1]:
            #need two of each to fit the full 
            fl_1 = []
            for dir in xrange(4):
                img = pygame.Surface((320, 320))
                img = img.convert_alpha()
                img.fill((255, 255, 255, 0))
                img.blit(images[dir], (0,0))
                fl_1.append(img)
            self.frames['dodge'].append(fl_1)
        
        #build turtleshell tilecache
        turtle_file = os.path.join('tilesets', 'weapons', 'turtleshell.png')
        turtle_frames = TileCache(turtle_file, 128).cache
        #loop to blit turtle shell on dodge frames
        for p_row, t_row in zip(self.frames['dodge'], turtle_frames):
            dir = 0
            for p_img, t_img in zip(p_row, t_row):
                if dir == 0:
                    img = pygame.Surface((320, 320))
                    img = img.convert_alpha()
                    img.fill((255, 255, 255, 0))
                    img.blit(t_img, (96, 96))
                    img.blit(p_img, (0, 0))
                    p_img.fill((255, 255, 255, 0))
                    p_img.blit(img, (0, 0))
                else:
                    p_img.blit(t_img, (96, 96))
                dir += 1
        
        long_spell = [self.frames['spell'][0]]
        for frame_col in self.frames['spell'][1:]:
            long_spell.extend([frame_col] * 3)
        self.frames['spell'] = long_spell
        
        #test for forcing drawing of frames during init
        # screen = pygame.display.set_mode((320, 320))
        # clock = pygame.time.Clock()
        
        # print len(self.frames['dodge'])
        # print len(self.frames['dodge'][0])
        # for j in xrange(4):
            # for i in xrange(8):
                # pygame.event.pump()
                # print i, j
                # screen.fill((0, 0, 0))
                # screen.blit(self.frames['dodge'][i][j], (0, 0))
                # pygame.display.flip()
                # clock.tick(15)
            
        
    def dodge_animation(self, num_frames):
        if num_frames > 8:
            for i in xrange(num_frames - 8):
                yield None
        for frame in xrange(8):
            self.image = self.frames['dodge'][frame][self.direction]
            yield None
        
class Archer(Character):
    def __init__(self, team, sp = 0):
        self.actions = ['bow', 'sword', 'spear', 'walk', 'spell']
        super(Archer, self).__init__('Archer', team, sp)
        self.armor = 1
    
class Cavalry(Character):
    def __init__(self, team, sp = 0):
        self.actions = ['bow', 'sword', 'spear', 'walk', 'spell']
        super(Cavalry, self).__init__('Cavalry', team, sp)
        self.armor = 1
        #the mounted value will also contain the spaces left in the mvt stat
        self.mounted = self.get_stat('mvt')     
        
        horse_file = os.path.join('tilesets', 'characters', 'base', 'horse_' + self.team + '.png')
        horse_frames = TileCache(horse_file, 32).cache
        #self.frames['walk'] = [self.frames['walk'][0]] * len(self.frames['walk'])
        
        # clock = pygame.time.Clock()
        # screen = pygame.display.set_mode((320, 320))
        for action in self.actions:
            new_frames = []
            toggle = 0
            for i in xrange(len(self.frames[action])):
                new_row = []
                dir = 0
                for j in xrange(len(self.frames[action][i])):
                    t_num = min(max((toggle % 4) - 1, 0), 1)                    
                    h_tile = horse_frames[t_num][j]
                    pygame.event.pump()
                    h_pos = (144, 160)
                    img = pygame.Surface((320, 320))
                    img = img.convert_alpha()
                    img.fill((255, 255, 255, 0))
                    #p_img = self.resize(self.frames[action][i][j], .5)
                    if action == 'walk':
                        if j % 2:
                            t_frame = self.frames['walk'][3][j]
                            img.blit(h_tile, h_pos)  
                            img.blit(t_frame, (0, 0))
                        else:
                            t_frame = self.frames['walk'][0][j]
                            img.blit(t_frame, (0, 0))
                            img.blit(h_tile, h_pos)  
                    else:
                        t_frame = self.frames[action][i][j]
                        img.blit(t_frame, (0, 0))
                        img.blit(h_tile, h_pos)  
                    new_row.append(img)
                new_frames.append(new_row)
                toggle += 1
                
            self.frames[action] = new_frames
                        
                    # if action == 'walk' and j == 1:
                        # screen.fill((0, 0, 0))
                        # screen.blit(img, (0, 0))
                        # pygame.display.flip()
                        # clock.tick(5)
        
    # def draw_weapons(self):
        # super(Cavalry, self).draw_weapons()

                    
                
            
 
 