#
# BigBrotherBot(B3) (www.bigbrotherbot.net)
# Copyright (C) 2005 Michael "ThorN" Thornton
# 
# This program 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 2 of the License, or
# (at your option) any later version.
#
# This program 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 this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
#
# CHANGELOG
#    04/14/2011 - 1.3.7 Courgette
#    * fix bug with !topstats and  !topxp
#    04/13/2011 - 1.3.6 Courgette
#    * import missing string module
#    10/30/2010 - 1.3.5 GrosBedo
#    * added show_awards and show_awards_xp
#    10/20/2010 - 1.3.4 GrosBedo
#    * clientKill and clientDamage separated from clientKillTeam and clientDamageTeam
#    10/20/2010 - 1.3.3 GrosBedo
#    * No more teamKills if team is unknown (eg: parser can't detect the team)
#    8/15/2010 - 1.3.2 GrosBedo
#    * Fixed disabling reset xp option
#    8/14/2010 - 1.3.1 Courgette
#    * move commands in the commands section of config
#    * allow to define aliases in config
#    * add automated tests
#    8/14/2010 - 1.3.0 GrosBedo
#    * Stats are now cleared at the beginning of next round (so they are still available at scoreboard)
#    * Moved the parameters to the xml config file (and added more)
#    * Added XP score and !topxp
#    * Setting to enable/disable score reset at round start
#    9/5/2005 - 1.2.0 - ThorN
#    * Added !topstats command
#    8/29/2005 - 1.1.0 - ThorN
#    * Converted to use new event handlers
 
__author__ = 'ThorN, GrosBedo'
__version__ = '1.3.7'
 
 
import string
import b3
import b3.events
import b3.plugin
 
#--------------------------------------------------------------------------------------------------
class StatsPlugin(b3.plugin.Plugin):
    _adminPlugin = None
 
    def onLoadConfig(self):
        try:
            self.mapstatslevel = self.config.getint('commands', 'mapstats')
        except:
            self.mapstatslevel = 0
            self.debug('Using default value (%i) for commands::mapstats', self.mapstatslevel)
 
        try:
            self.testscorelevel = self.config.getint('commands', 'testscore')
        except:
            self.testscorelevel = 0
            self.debug('Using default value (%i) for commands::testscore', self.testscorelevel)
 
        try:
            self.topstatslevel = self.config.getint('commands', 'topstats')
        except:
            self.topstatslevel = 2
            self.debug('Using default value (%i) for commands::topstats', self.topstatslevel)
 
        try:
            self.topxplevel = self.config.getint('commands', 'topxp')
        except:
            self.topxplevel = 2
            self.debug('Using default value (%i) for commands::topxp', self.topxplevel)
 
        try:
            self.startPoints = self.config.getint('settings', 'startPoints')
        except:
            self.startPoints = 100
            self.debug('Using default value (%i) for settings::startPoints', self.startPoints)
 
        try:
            self.resetscore = self.config.getboolean('settings', 'resetscore')
        except:
            self.resetscore = False
            self.debug('Using default value (%s) for settings::resetscore', self.resetscore)
 
        try:
            self.resetxp = self.config.getboolean('settings', 'resetxp')
        except:
            self.resetxp = False
            self.debug('Using default value (%s) for settings::resetxp', self.resetxp)
 
        try:
            self.show_awards = self.config.getboolean('settings', 'show_awards')
        except:
            self.show_awards = False
            self.debug('Using default value (%s) for settings::show_awards', self.show_awards)
 
        try:
            self.show_awards_xp = self.config.getboolean('settings', 'show_awards_xp')
        except:
            self.show_awards_xp = False
            self.debug('Using default value (%s) for settings::show_awards_xp', self.show_awards_xp)
 
 
    def onStartup(self):
        self._adminPlugin = self.console.getPlugin('admin')
        if not self._adminPlugin:
            self.critical('Cannot find the admin plugin. Disabling Stats plugin')
            self.disable()
            return False
 
        # register our commands
        if 'commands' in self.config.sections():
            for cmd in self.config.options('commands'):
                level = self.config.get('commands', cmd)
                sp = cmd.split('-')
                alias = None
                if len(sp) == 2:
                    cmd, alias = sp
                func = self.getCmd(cmd)
                if func:
                    self._adminPlugin.registerCommand(self, cmd, level, func, alias)
 
 
        self.registerEvent(b3.events.EVT_CLIENT_DAMAGE_TEAM)
        self.registerEvent(b3.events.EVT_CLIENT_KILL_TEAM)
        self.registerEvent(b3.events.EVT_CLIENT_KILL)
        self.registerEvent(b3.events.EVT_CLIENT_DAMAGE)
        #self.registerEvent(b3.events.EVT_CLIENT_DISCONNECT)
        self.registerEvent(b3.events.EVT_GAME_EXIT) # used to show awards at the end of round
        #self.registerEvent(b3.events.EVT_GAME_ROUND_END) # used to show awards at the end of round
        self.registerEvent(b3.events.EVT_GAME_ROUND_START) # better to reinit stats at round start than round end, so that players can still query their stats at the end
 
    def onEvent(self, event):
        if event.type == b3.events.EVT_GAME_EXIT:
            if self.show_awards: self.cmd_topstats(None)
            if self.show_awards_xp: self.cmd_topxp(None)
        elif event.type == b3.events.EVT_GAME_ROUND_START:
            self.debug('Map Start: clearing stats')
            for cid, c in self.console.clients.items():
                if c.maxLevel >= self.mapstatslevel:
                    try:
                        c.setvar(self, 'shotsTeamHit', 0)
                        c.setvar(self, 'damageTeamHit', 0)
                        c.setvar(self, 'shotsHit', 0)
                        c.setvar(self, 'damageHit', 0)
                        c.setvar(self, 'shotsGot', 0)
                        c.setvar(self, 'damageGot', 0)
                        c.setvar(self, 'teamKills', 0)
                        c.setvar(self, 'kills', 0)
                        c.setvar(self, 'deaths', 0)
 
                        if self.resetscore:
                            # skill points are reset at the beginning of each map
                            c.setvar(self, 'pointsLost', 0)
                            c.setvar(self, 'pointsWon', 0)
                            c.setvar(self, 'points', self.startPoints)
                        if self.resetxp:
                            c.setvar(self, 'experience', 0)
                        else:
                            c.var(self, 'oldexperience', 0).value += c.var(self, 'experience', 0).value
                            c.setvar(self, 'experience', 0)
                    except:
                        pass
        elif event.client:
            if event.type == b3.events.EVT_CLIENT_DAMAGE:
                self.clientDamage(event.client, event.target, int(event.data[0]))
            elif event.type == b3.events.EVT_CLIENT_DAMAGE_TEAM:
                self.clientDamageTeam(event.client, event.target, int(event.data[0]))
            elif event.type == b3.events.EVT_CLIENT_KILL:
                self.clientKill(event.client, event.target, int(event.data[0]))
            elif event.type == b3.events.EVT_CLIENT_KILL_TEAM:
                self.clientKillTeam(event.client, event.target, int(event.data[0]))
 
 
    def getCmd(self, cmd):
        """ return the method for a given command  """
        cmd = 'cmd_%s' % cmd
        if hasattr(self, cmd):
            func = getattr(self, cmd)
            return func
        return None
 
 
    def clientDamage(self, killer, victim, points):
        if points > 100:
            points = 100
 
        killer.var(self, 'shotsHit', 0).value  += 1
        killer.var(self, 'damageHit', 0).value += points
 
        victim.var(self, 'shotsGot', 0).value  += 1
        victim.var(self, 'damageGot', 0).value += points
        return
 
    def clientDamageTeam(self, killer, victim, points):
        if points > 100:
            points = 100
 
        killer.var(self, 'shotsTeamHit', 0).value  += 1
        killer.var(self, 'damageTeamHit', 0).value += points
        return
 
    def clientKill(self, killer, victim, points):
        if points > 100:
            points = 100
 
        killer.var(self, 'shotsHit', 0).value  += 1
        killer.var(self, 'damageHit', 0).value += points
 
        victim.var(self, 'shotsGot', 0).value  += 1
        victim.var(self, 'damageGot', 0).value += points
 
        killer.var(self, 'kills', 0).value  += 1
        victim.var(self, 'deaths', 0).value += 1
 
        score = self.score(killer, victim)
        killer.var(self, 'points', self.startPoints).value += score
        killer.var(self, 'pointsWon', 0).value += score
 
        victim.var(self, 'points', self.startPoints).value -= score
        victim.var(self, 'pointsLost', 0).value += score
 
        self.updateXP(killer)
        self.updateXP(victim)
 
        return
 
    def clientKillTeam(self, killer, victim, points):
        if points > 100:
            points = 100
 
        killer.var(self, 'shotsTeamHit', 0).value  += 1
        killer.var(self, 'damageTeamHit', 0).value += points
 
        killer.var(self, 'teamKills', 0).value += 1
 
        score = self.score(killer, victim)
        killer.var(self, 'points', self.startPoints).value -= score
        killer.var(self, 'pointsLost', 0).value += score
 
        self.updateXP(killer)
        self.updateXP(victim)
 
        return
 
    def updateXP(self, sclient):
        realpoints = sclient.var(self, 'pointsWon', 0).value - sclient.var(self, 'pointsLost', 0).value
        if sclient.var(self, 'deaths', 0).value != 0:
            experience = (sclient.var(self, 'kills', 0).value * realpoints) / sclient.var(self, 'deaths', 0).value
        else:
            experience = sclient.var(self, 'kills', 0).value * realpoints
        sclient.var(self, 'experience', 0).value = experience
 
    def cmd_mapstats(self, data, client, cmd=None):
        """\
        [<name>] - list a players stats for the map
        """
        if data:
            sclient = self._adminPlugin.findClientPrompt(data, client)
            if not sclient: return            
        else:
            sclient = client
 
        message = '^3Stats ^7[ %s ^7] K ^2%s ^7D ^3%s ^7TK ^1%s ^7Dmg ^5%s ^7Skill ^3%1.02f ^7XP ^6%s' % (sclient.exactName, sclient.var(self, 'kills', 0).value, sclient.var(self, 'deaths', 0).value, sclient.var(self, 'teamKills', 0).value, sclient.var(self, 'damageHit', 0).value, round(sclient.var(self, 'points', self.startPoints).value, 2), round(sclient.var(self, 'oldexperience', 0).value + sclient.var(self, 'experience', 0).value, 2))
        cmd.sayLoudOrPM(client, message)
 
    def cmd_testscore(self, data, client, cmd=None):
        """\
        <name> - how much skill points you will get if you kill the player
        """
 
        if not data:
            client.message('^7You must supply a player name to test')
            return
 
        sclient = self._adminPlugin.findClientPrompt(data, client)
        if not sclient: return    
        elif sclient == client:
            client.message('^7You don\'t get points for killing yourself')
            return
 
        cmd.sayLoudOrPM(client, '^3Stats: ^7%s^7 will get ^3%s ^7skill points for killing %s^7' % (client.exactName, self.score(client, sclient), sclient.exactName))
 
    def cmd_topstats(self, data, client=None, cmd=None):
        """\
        List the top 5 map-stats players
        """
        self.debug('Haha')
        scores = []
        for c in self.console.clients.getList():
            if c.isvar(self, 'points'):
                scores.append((c.exactName, round(c.var(self, 'points', self.startPoints).value, 2)))
 
        if len(scores):
            tmplist = [(x[1], x) for x in scores]
            tmplist.sort()
            scores = [x for (key, x) in tmplist]
            scores.reverse()
 
            i = 0
            results = []
            for name, score in scores:
                i += 1
 
                if i >= 6:
                    break
 
                results.append('^3#%s^7 %s ^7[^3%s^7]' % (i, name, score))
 
            if client:        
                client.message('^3Top Stats:^7 %s' % string.join(results,', '))
            else:
                self.console.say('^3Top Stats:^7 %s' % string.join(results,', '))
        else:
            client.message('^3Stats: ^7No top players')
 
    def cmd_topxp(self, data, client=None, cmd=None):
        """\
        List the top 5 map-stats most experienced players
        """
 
        scores = []
        for c in self.console.clients.getList():
            if c.isvar(self, 'experience'):
                scores.append((c.exactName, round(c.var(self, 'experience', self.startPoints).value, 2)))
 
        if len(scores):
            tmplist = [(x[1], x) for x in scores]
            tmplist.sort()
            scores = [x for (key, x) in tmplist]
            scores.reverse()
 
            i = 0
            results = []
            for name, score in scores:
                i += 1
 
                if i >= 6:
                    break
 
                results.append('^3#%s^7 %s ^7[^3%s^7]' % (i, name, score))
 
            if client:
                client.message('^3Top Experienced Players:^7 %s' % string.join(results, ', '))
            else:
                self.console.say('^3Top Experienced Players:^7 %s' % string.join(results, ', '))
        else:
            client.message('^3Stats: ^7No top experienced players')
 
    def score(self, killer, victim):
        k = int(killer.var(self, 'points', self.startPoints).value)
        v = int(victim.var(self, 'points', self.startPoints).value)
 
        if k < 1:
            k = 1.00
        if v < 1:
            v = 1.00
 
        """
        if k > v:
            high = k
            low  = v
        else:
            high = v
            low  = k
 
        vshift = float(high) / float(low)
        self.console.verbose('stats vshift %s' % vshift)
 
        #per = (vshift * 100) / 10
        per = (vshift * 10.0) / 100.0
 
        self.console.verbose('stats per %s' % per)
 
        if per > 100:
            per = 100.0
        elif per < 1:
            per = 1.0
        """
 
        vshift = (float(v) / float(k)) / 2
        self.console.verbose('stats vshift %s' % vshift)
 
        points = (15.00 * vshift) + 5
 
        if points < 1:
            points = 1.00
        elif points > 100:
            points = 100.00
 
        return round(points, 2)
 
"""
#--------------------------------------------------------------------------------------------------
class ClientStats(DelayedSQLObject):
    _table = 'stats'
    timeAdd = IntCol(default=0)
    kills = IntCol(default=0)
    teamKills = IntCol(default=0)
    deaths = IntCol(default=0)
    score  = IntCol(default=0)
    shotsGot  = IntCol(default=0)
    shotsHit  = IntCol(default=0)
    damageGot  = IntCol(default=0)
    damageHit  = IntCol(default=0)
    captures = IntCol(default=0)
    pickups = IntCol(default=0)
    rank = IntCol(default=0)
    gameName = StringCol(default='',length=3)
    gameType = StringCol(default='',length=3)
    pointsWon = IntCol(default=0)
    pointsLost = IntCol(default=0)
    playTime = IntCol(default=0)
 
    lastEventTime = 0
 
#    client = ForeignKey('Clients.Client')
 
    def __init__(self):
        ClientStats.__init__(self)
        self.createTable(ifNotExists=True)
 
    def experiance(self):
        return ( self.kills + self.deaths ) / ( (self.pointsWon + self.pointsLost) / self.playTime )
 
    def save(self):
        self.playTime += ( (time.time() - time.timezone) - self.lastEventTime ) / 60
        #DelayedSQLObject.save(self)
"""
 
 
if __name__ == '__main__':
    """
    Automated tests below
    """
    from b3.fake import fakeConsole
    from b3.fake import superadmin, joe 
    import time
 
    from b3.config import XmlConfigParser
 
    conf = XmlConfigParser()
    conf.setXml("""
<configuration plugin="stats">
  <settings name="commands">
    <set name="mapstats-mystatalias">0</set>
    <set name="testscore-tscr">0</set>
    <set name="topstats-tops">2</set>
    <set name="topxp-txp">2</set>
  </settings>
  <settings name="settings">
    <set name="startPoints">100</set>
    <set name="resetscore">no</set>
    <set name="resetxp">no</set>
  </settings>
</configuration>
    """)
 
 
    p = StatsPlugin(fakeConsole, conf)
    p.onStartup()
    p.onLoadConfig()
 
    time.sleep(1)
    joe.connects(cid=3)
    joe.says("!mapstats")
    joe.says("!mystatalias")
    joe.says("!testscore")
    joe.says("!tscr")
    joe.says("!topstats")
    joe.says("!tops")
    joe.says("!topxp")
    joe.says("!txp")
 
 
    superadmin.connects(cid=2)
    joe.kills(superadmin)
    joe.kills(superadmin)
    joe.kills(superadmin)
    superadmin.kills(joe)
 
    superadmin.says("!mapstats")
    superadmin.says("!mystatalias")
    superadmin.says("!testscore")
    superadmin.says("!tscr")
    superadmin.says("!topstats")
    superadmin.says("!tops")
    superadmin.says("!topxp")
    superadmin.says("!txp")