import BigWorld
import AccountCommands
import ArenaType
import codecs
import datetime
import json
import math
import os
import re
import ResMgr
import threading
import urllib2
from xml.dom.minidom import parseString
from Account import Account
from account_helpers import BattleResultsCache
from items import vehicles as vehiclesWG
from gui.shared.utils.requesters import StatsRequester
from gui import SystemMessages
from helpers import i18n
from notification.NotificationListView import NotificationListView
from notification.NotificationPopUpViewer import NotificationPopUpViewer
from messenger import MessengerEntry, g_settings
from messenger.formatters.service_channel import BattleResultsFormatter
from Queue import Queue
from debug_utils import *
from helpers.html import translation as html_translation, templates
from types import DictType
from messenger.doc_loaders.html_templates import _MessageTemplate
import time
GENERAL = 0
BY_TANK = 1
VERSION = '9.16'
print 'Session Statistics by tratatank modified by _Eddy_, _Jeso_, soloviyko, YasenKrasen [Kodak ' + VERSION + ']'

def hexToRgb(hex):
    return [ int(hex[i:i + 2], 16) for i in range(1, 6, 2) ]


def gradColor(startColor, endColor, val):
    start = hexToRgb(startColor)
    end = hexToRgb(endColor)
    grad = []
    for i in [0, 1, 2]:
        grad.append(start[i] * (1.0 - val) + end[i] * val)

    return '#%02x%02x%02x' % (grad[0], grad[1], grad[2])


class SessionStatistic(object):

    def __init__(self):
        self.page = GENERAL
        self.cacheVersion = 8
        self.queue = Queue()
        self.loaded = False
        self.configIsValid = True
        self.battleStats = {}
        self.cache = {}
        self.gradient = {}
        self.palette = {}
        self.config = {}
        self.expectedValues = {}
        self.recalcWN8 = True
        self.values = {}
        self.battles = []
        self.battleStatPatterns = []
        self.messageGeneral = ''
        self.messageByTank = ''
        self.playerName = ''
        self.bgIcon = ''
        self.startDate = None
        self.battleResultsAvailable = threading.Event()
        self.battleResultsAvailable.clear()
        self.battleResultsBusy = threading.Lock()
        self.thread = threading.Thread(target=self.mainLoop)
        self.thread.setDaemon(True)
        self.thread.start()
        return

    def load(self):
        if self.loaded and self.playerName == BigWorld.player().name:
            return
        self.loaded = True
        self.battles = []
        self.playerName = BigWorld.player().name
        res = ResMgr.openSection('../paths.xml')
        sb = res['Paths']
        vals = sb.values()
        for vl in vals:
            path = vl.asString + '/scripts/client/gui/mods/'
            if os.path.isdir(path):
                self.configFilePath = path + 'stat/config.json'
                self.colorsFilePath = path + 'stat/colors.json'
                self.statCacheFilePath = path + 'stat/cache.json'
                expectedValuesPath = path + 'stat/expected_tank_values.json'
                if os.path.isfile(self.configFilePath):
                    break

        self.readConfig()
        with open(expectedValuesPath) as origExpectedValuesJson:
            origExpectedValues = json.load(origExpectedValuesJson)
            for tankValues in origExpectedValues['data']:
                idNum = int(tankValues.pop('IDNum'))
                self.expectedValues[idNum] = {}
                for key in ['expDamage',
                 'expFrag',
                 'expSpot',
                 'expDef',
                 'expWinRate']:
                    self.expectedValues[idNum][key] = float(tankValues[key])

        invalidCache = True
        if os.path.isfile(self.statCacheFilePath):
            with open(self.statCacheFilePath) as jsonCache:
                self.cache = json.load(jsonCache)
                self.startDate = self.cache.get('date', self.getWorkDate())
                if self.cache.get('version', 0) == self.cacheVersion and (self.startDate == self.getWorkDate() or not self.config.get('dailyAutoReset', True)) and not self.config.get('clientReloadReset', False):
                    if self.cache.get('players', {}).has_key(self.playerName):
                        self.battles = self.cache['players'][self.playerName]['battles']
                    invalidCache = False
        if invalidCache:
            self.cache = {}
        if self.config.get('checkForUpdate', True):
            try:
                file = urllib2.urlopen('http://pastebin.com/raw.php?i=0BL7sjCv')
                data = file.read()
                file.close()
                dom = parseString(data)
                latestVersion = dom.getElementsByTagName('version')[0].firstChild.data
                if latestVersion != VERSION:
                    SystemMessages.pushMessage("<font color='#BFE9FF'>Yasen</font><font color='#FF3333'>Krasen</font> Update!\n\n<font color='#C5AB5D'>" + latestVersion + " is available at </font><font color='#78C55E'>new.soloviyko.com</font>\n", type=SystemMessages.SM_TYPE.GameGreeting)
            except:
                print '[stat] Unable to access remote update file.'

        self.updateMessage()

    def readConfig(self):
        with codecs.open(self.configFilePath, 'r', 'utf-8-sig') as configFileJson:
            try:
                self.config = json.load(configFileJson)
                self.battleStatPatterns = []
                for pattern in self.config.get('battleStatPatterns', []):
                    try:
                        condition = pattern.get('if', 'True')
                        condition = re.sub('{{(\\w+)}}', "values['\\1']", condition)
                    except:
                        print '[stat] Invalid condition ' + pattern.get('if', '')
                        SystemMessages.pushMessage('Invalid condition:\n' + pattern.get('if', ''), type=SystemMessages.SM_TYPE.Warning)
                        continue

                    try:
                        compiled = re.compile(pattern.get('pattern', ''))
                        self.battleStatPatterns.append({'condition': condition,
                         'pattern': compiled,
                         'repl': pattern.get('repl', '')})
                    except:
                        print '[stat] Invalid pattern ' + pattern.get('pattern', '')
                        SystemMessages.pushMessage('Invalid pattern:\n' + pattern.get('pattern', ''), type=SystemMessages.SM_TYPE.Warning)
                        continue
                
                for key, value in self.config.get('messenger.xml-overwrites', {}).get('htmlTemplatesSimple', {}).iteritems():
                    g_settings.htmlTemplates[key].source["text"] = html_translation(value.encode("utf-8"))
                for key, value in self.config.get('messenger.xml-overwrites', {}).get('htmlTemplates', {}).iteritems():
                    g_settings.msgTemplates[key].source["message"] = html_translation(value.encode("utf-8"))

                self.configIsValid = True
            except ValueError as e:
                print '[stat] Load config.json has failed: ' + str(e)
                SystemMessages.pushMessage("Load <font color='#05FFDA'>config.json</font> has failed:\n" + str(e), type=SystemMessages.SM_TYPE.Error)
                self.config = {}
                self.configIsValid = False

        with codecs.open(self.colorsFilePath, 'r', 'utf-8-sig') as colorsFileJson:
            try:
                self.colors = json.load(colorsFileJson)
                self.configIsValid = True
            except ValueError as e:
                print '[stat] Load colors.json has failed: ' + str(e)
                SystemMessages.pushMessage("Load <font color='#05A8FF'>colors.json</font> has failed:\n" + str(e), type=SystemMessages.SM_TYPE.Error)
                self.colors = {}
                self.configIsValid = False

    def getWorkDate(self):
        if datetime.datetime.now().hour >= self.config.get('dailyAutoResetHour', 4):
            return datetime.date.today().strftime('%Y-%m-%d')
        return (datetime.date.today() - datetime.timedelta(days=1)).strftime('%Y-%m-%d')

    def save(self):
        statCache = open(self.statCacheFilePath, 'w')
        self.cache['version'] = self.cacheVersion
        self.cache['date'] = self.startDate
        if not self.cache.has_key('players'):
            self.cache['players'] = {}
        if not self.cache['players'].has_key(self.playerName):
            self.cache['players'][self.playerName] = {}
        self.cache['players'][self.playerName]['battles'] = self.battles
        statCache.write(json.dumps(self.cache, sort_keys=True, indent=4, separators=(',', ': ')))
        statCache.close()

    def createMessage(self):
        messages = {GENERAL: self.messageGeneral,
         BY_TANK: self.messageByTank}
        msg = messages[self.page]
        message = {'typeID': 1,
         'message': {'bgIcon': self.bgIcon,
                     'defaultIcon': '',
                     'savedData': 0,
                     'timestamp': -1,
                     'filters': [],
                     'buttonsLayout': [],
                     'message': msg,
                     'type': 'black',
                     'icon': self.config.get('messageIcon', '')},
         'entityID': 99999,
         'auxData': ['GameGreeting']}
        if len(self.battles) and self.config.get('showStatByTank', True):
            buttonNames = {GENERAL: self.config.get('textGeneralPageButton', 'By tank'),
             BY_TANK: self.config.get('textByTankPageButton', 'General')}
            message['message']['buttonsLayout'].append({'action': 'wotstatSwitchPage',
             'type': 'submit',
             'label': buttonNames[self.page]})
        if self.config.get('showResetButton', False):
            message['message']['buttonsLayout'].append({'action': 'wotstatReset',
             'type': 'submit',
             'label': self.config.get('textResetButton', 'Reset')})
        return message

    def battleResultsCallback(self, arenaUniqueID, responseCode, value = None, revision = 0):
        LOG_NOTE("battleResultsCallback", arenaUniqueID, responseCode)
        if responseCode == AccountCommands.RES_NON_PLAYER or responseCode == AccountCommands.RES_COOLDOWN:
            BigWorld.callback(1.0, lambda : self.queue.put(arenaUniqueID))
            self.battleResultsBusy.release()
            return
        elif responseCode < 0:
            self.battleResultsBusy.release()
            return
        else:
            finishReason = value["common"]["finishReason"]
            LOG_NOTE(finishReason)
            if finishReason!=1:
                LOG_NOTE(i18n.makeString('#ingame_gui:statistics/final/reasons/reason{0}'.format(finishReason)))
            arenaTypeID = value['common']['arenaTypeID']
            arenaType = ArenaType.g_cache[arenaTypeID]
            personal = value['personal'].itervalues().next()
            vehicleCompDesc = personal['typeCompDescr']
            arenaName = i18n.makeString(arenaType.name)
            vt = vehiclesWG.getVehicleType(vehicleCompDesc)
            result = 1 if int(personal['team']) == int(value['common']['winnerTeam']) else (0 if not int(value['common']['winnerTeam']) else -1)
            death = 1 if int(personal['deathReason']) > -1 else 0
            if personal['fortResource'] is None:
                fortResource = 0
            else:
                fortResource = int(personal['fortResource'])
            place = 1
            arenaUniqueID = value['arenaUniqueID']
            squadsTier = {}
            vehicles = value['vehicles']
            for vehicle in vehicles.values():
                pTypeCompDescr = vehicle[0]['typeCompDescr']
                if pTypeCompDescr is not None:
                    pvt = vehiclesWG.getVehicleType(pTypeCompDescr)
                    tier = pvt.level
                    if set(vehiclesWG.VEHICLE_CLASS_TAGS.intersection(pvt.tags)).pop() == 'lightTank' and tier > 5:
                        tier += 1
                    squadId = value['players'][vehicle[0]['accountDBID']]['prebattleID']
                    squadsTier[squadId] = max(squadsTier.get(squadId, 0), tier)
                if personal['team'] == vehicle[0]['team'] and personal['originalXP'] < vehicle[0]['xp']:
                    place += 1

            battleTier = 11 if max(squadsTier.values()) == 10 and min(squadsTier.values()) == 9 else max(squadsTier.values())
            proceeds = personal['credits'] - personal['autoRepairCost'] - personal['autoEquipCost'][0] - personal['autoLoadCost'][0]
            tmenXP = personal['tmenXP']
            if 'premium' in vt.tags:
                tmenXP = int(1.5 * tmenXP)
            battle = {'idNum': vehicleCompDesc,
             'map': arenaType.geometryName,
             'vehicle': vt.name.replace(':', '-'),
             'tier': vt.level,
             'result': result,
             'dailyXPFactor': personal['dailyXPFactor10'] / 10,
             'gametype': value['common']['guiType'],
             'damage': personal['damageDealt'],
             'damageRec': personal['damageReceived'],
             'potDamageRec': personal['potentialDamageReceived'],
             'deathsCount': death,
             'frag': personal['kills'],
             'fortResource': fortResource,
             'mileage': personal['mileage'],
             'spot': personal['spotted'],
             'def': personal['droppedCapturePoints'],
             'cap': personal['capturePoints'],
             'shots': personal['shots'],
             'hits': personal['directHits'],
             'pierced': personal['piercings'],
             'xp': personal['xp'],
             'originalXP': personal['originalXP'],
             'place': place,
             'originalPremXP': round(personal['originalXP'] * personal['premiumXPFactor10'] / 10),
             'freeXP': personal['freeXP'],
             'originalFreeXP': personal['originalFreeXP'],
             'tmenXP': tmenXP,
             'eventTmenXP': personal['eventTMenXP'],
             'autoRepairCost': personal['autoRepairCost'],
             'autoLoadCost': personal['autoLoadCost'][0],
             'autoEquipCost': personal['autoEquipCost'][0],
             'service': personal['autoEquipCost'][0] + personal['autoLoadCost'][0] + personal['autoRepairCost'],
             'grossCredits': personal['credits'],
             'netCredits': proceeds,
             'grossGold': personal['gold'],
             'netGold': personal['gold'] - personal['autoEquipCost'][1] - personal['autoLoadCost'][1],
             'battleTier': battleTier,
             'damageAssistedRadio': personal['damageAssistedRadio'],
             'damageAssistedTrack': personal['damageAssistedTrack'],
             'assist': personal['damageAssistedRadio'] + personal['damageAssistedTrack']}
            extended = {'vehicle-raw': battle['vehicle'],
             'vehicle-short': vt.shortUserString,
             'vehicle-long': vt.userString,
             'map-raw': battle['map'],
             'map': arenaName,
             'result': result,
             'autoRepair': personal['autoRepairCost'],
             'autoEquip': personal['autoEquipCost'][0],
             'autoLoad': personal['autoLoadCost'][0]}
            if self.config.get('dailyAutoReset', True) and self.startDate != stat.getWorkDate():
                self.reset()
            if value['common']['guiType'] in self.config.get('battleType', [1]):
                deltas = {'dXEFF': '', 'dXWN8': ''}
                if self.values['battlesCount'] > 0:
                    deltas['dXEFF'] = self.values['XEFF']
                    deltas['dXWN8'] = self.values['XWN8']
                self.battles.append(battle)
                self.save()
                self.recalcWN8 = True
                self.updateMessage()
                if self.values['battlesCount'] > 1:
                    deltas['dXEFF'] = self.values['XEFF'] - deltas['dXEFF']
                    deltas['dXWN8'] = self.values['XWN8'] - deltas['dXWN8']
                else:
                    deltas['dXEFF'] = 0
                    deltas['dXWN8'] = 0
                if deltas['dXEFF'] == 0:
                    deltas['dXEFF'] = '='
                else:
                    deltas['dXEFF'] = ('' if deltas['dXEFF'] < 0 else '+') + str(deltas['dXEFF'])
                if deltas['dXWN8'] == 0:
                    deltas['dXWN8'] = '='
                else:
                    deltas['dXWN8'] = ('' if deltas['dXWN8'] < 0 else '+') + str(deltas['dXWN8'])
                extended.update(deltas)
            battleStat, gradient, palette = self.calcWN8([battle])
            extGradient, extPalette = self.refreshColorMacros(extended)
            gradient.update(extGradient)
            palette.update(extPalette)
            self.battleStats[arenaUniqueID] = {}
            self.battleStats[arenaUniqueID]['values'] = battleStat
            self.battleStats[arenaUniqueID]['extendedValues'] = extended
            self.battleStats[arenaUniqueID]['gradient'] = gradient
            self.battleStats[arenaUniqueID]['palette'] = palette
            self.battleResultsBusy.release()

    def reset(self):
        self.page = GENERAL
        self.startDate = self.getWorkDate()
        self.recalcWN8 = True
        self.battles = []
        self.save()
        self.updateMessage()

    def mainLoop(self):
        while True:
            arenaUniqueID = self.queue.get()
            self.battleResultsAvailable.wait()
            self.battleResultsBusy.acquire()
            BigWorld.player().battleResultsCache.get(arenaUniqueID, lambda resID, value: self.battleResultsCallback(arenaUniqueID, resID, value, None))

    def refreshColorMacros(self, values):
        gradient = {}
        palette = {}
        if values.get('battlesCount', 1) == 0:
            for key in values.keys():
                gradient[key] = '#FFFFFF'
                palette[key] = '#FFFFFF'

            return (gradient, palette)
        for key in values.keys():
            if self.colors.get('gradient', {}).has_key(key):
                color = self.colors.get('gradient', {})[key]
                if values[key] <= color[0]['value']:
                    gradient[key] = color[0]['color']
                elif values[key] >= color[-1]['value']:
                    gradient[key] = color[-1]['color']
                else:
                    sVal = color[0]['value']
                    eVal = color[1]['value']
                    i = 1
                    while eVal < values[key]:
                        sVal = color[i]['value']
                        i += 1
                        eVal = color[i]['value']

                    val = float(values[key] - sVal) / (eVal - sVal)
                    gradient[key] = gradColor(color[i - 1]['color'], color[i]['color'], val)
            else:
                gradient[key] = '#FFFFFF'
            if self.colors.get('palette', {}).has_key(key):
                color = self.colors.get('palette', {})[key]
                palette[key] = color[-1]['color']
                for item in reversed(color):
                    if values[key] <= item['value']:
                        palette[key] = item['color']
                    else:
                        break

            else:
                palette[key] = '#FFFFFF'

        return (gradient, palette)

    def calcExpected(self, newIdNum):
        v = vehiclesWG.getVehicleType(newIdNum)
        newTier = v.level
        newType = set(vehiclesWG.VEHICLE_CLASS_TAGS.intersection(v.tags)).pop()
        if newTier < 1 or newTier > 10:
            newTier = 10
        tierExpected = {}
        tierExpectedCount = 0.0
        typeExpected = {}
        typeExpectedCount = 0.0
        for idNum in self.expectedValues:
            try:
                vt = vehiclesWG.getVehicleType(idNum)
            except:
                continue

            if vt.level == newTier:
                tierExpectedCount += 1
                vType = set(vehiclesWG.VEHICLE_CLASS_TAGS.intersection(vt.tags)).pop()
                if vType == newType:
                    typeExpectedCount += 1
                for key in self.expectedValues[idNum]:
                    tierExpected[key] = tierExpected.get(key, 0) + self.expectedValues[idNum].get(key, 0.0)
                    if vType == newType:
                        typeExpected[key] = typeExpected.get(key, 0) + self.expectedValues[idNum].get(key, 0.0)

        if typeExpectedCount > 0:
            for key in typeExpected:
                typeExpected[key] /= typeExpectedCount

            self.expectedValues[newIdNum] = typeExpected.copy()
            return
        for key in tierExpected:
            tierExpected[key] /= tierExpectedCount

        self.expectedValues[newIdNum] = tierExpected.copy()

    def calcWN8(self, battles):
        values = {}
        values['battlesCount'] = len(battles)
        totalTier = 0
        totalPlace = 0
        places = []
        totalBattleTier = 0
        valuesKeys = ['winsCount',
         'defeatsCount',
         'drawsCount',
         'dailyXPFactor',
         'totalDmg',
         'totalDmgRec',
         'totalMileage',
         'totalMileagekm',
         'totalPotDmgRec',
         'totalDeathsCount',
         'totalFrag',
         'totalShots',
         'totalHits',
         'totalPierced',
         'totalSpot',
         'totalDef',
         'totalCap',
         'totalAssist',
         'totalDmgAssistTrack',
         'totalDmgAssistRadio',
         'totalXP',
         'allXP',
         'totalOriginXP',
         'totalFreeXP',
         'totalOriginalFreeXP',
         'totalOriginPremXP',
         'totalTmenXP',
         'totalEventTmenXP',
         'totalResources',
         'autoRepairCost',
         'autoLoadCost',
         'autoEquipCost',
         'service',
         'grossCredits',
         'netCredits',
         'grossGold',
         'netGold',
         'autoRepairGBMCost',
         'autoLoadGBMCost',
         'autoEquipGBMCost',
         'place',
         'battlesCountRandom',
         'battlesCountTraining',
         'battlesCountCompany',
         'battlesCountTournament',
         'battlesCountClan',
         'battlesCountTutorial',
         'battlesCountTeam',
         'battlesCountHistorical',
         'battlesCountEvents',
         'battlesCountStronghold',
         'battlesCountStrongholdSkirmish',
         'battlesCountRatedTeam']
        for key in valuesKeys:
            values[key] = 0

        expKeys = ['expDamage',
         'expFrag',
         'expSpot',
         'expDef',
         'expWinRate']
        expValues = {}
        for key in expKeys:
            expValues['total_' + key] = 0.0

        resCounters = {-1: 'defeatsCount',
         0: 'drawsCount',
         1: 'winsCount'}
        battleCounters = {1: 'battlesCountRandom',
         2: 'battlesCountTraining',
         3: 'battlesCountCompany',
         4: 'battlesCountTournament',
         5: 'battlesCountClan',
         6: 'battlesCountTutorial',
         7: 'battlesCountTeam',
         8: 'battlesCountHistorical',
         9: 'battlesCountEvents',
         10: 'battlesCountStrongholdSkirmish',
         11: 'battlesCountStronghold',
         12: 'battlesCountRatedTeam',
         13: 'battlesCountClan'}
        for battle in battles:
            values[resCounters[battle['result']]] += 1
            values[battleCounters[battle['gametype']]] += 1
            values['totalDmg'] += battle['damage']
            values['totalDmgRec'] += battle['damageRec']
            values['totalPotDmgRec'] += battle['potDamageRec']
            values['totalDeathsCount'] += battle['deathsCount']
            values['totalFrag'] += battle['frag']
            values['totalSpot'] += battle['spot']
            values['totalDef'] += battle['def']
            values['totalCap'] += battle['cap']
            values['totalShots'] += battle['shots']
            values['totalHits'] += battle['hits']
            values['totalPierced'] += battle['pierced']
            values['totalAssist'] += battle['assist']
            values['totalDmgAssistTrack'] += battle['damageAssistedTrack']
            values['totalDmgAssistRadio'] += battle['damageAssistedRadio']
            values['totalXP'] += battle['xp']
            values['allXP'] += battle['xp'] + battle['freeXP']
            values['totalOriginXP'] += battle['originalXP']
            values['totalOriginPremXP'] += int(battle['originalPremXP'])
            values['totalFreeXP'] += battle['freeXP']
            values['totalOriginalFreeXP'] += battle['originalFreeXP']
            values['totalTmenXP'] += battle['tmenXP']
            values['totalEventTmenXP'] += battle['eventTmenXP']
            values['totalResources'] += battle['fortResource']
            values['totalMileage'] += battle['mileage']
            values['totalMileagekm'] += battle['mileage'] / float(1000)
            values['autoRepairCost'] = battle['autoRepairCost']
            values['autoLoadCost'] = battle['autoLoadCost']
            values['autoEquipCost'] = battle['autoEquipCost']
            values['autoRepairGBMCost'] = battle['autoRepairCost']
            values['autoLoadGBMCost'] = battle['autoLoadCost']
            values['autoEquipGBMCost'] = battle['autoEquipCost']
            values['service'] += battle['service']
            values['netCredits'] += battle['netCredits']
            values['grossCredits'] += battle['grossCredits']
            values['grossGold'] += battle['grossGold']
            values['netGold'] += battle['netGold']
            values['place'] = battle['place']
            values['dailyXPFactor'] = battle['dailyXPFactor']
            totalTier += battle['tier']
            totalBattleTier += battle['battleTier']
            totalPlace += battle['place']
            places.append(battle['place'])
            idNum = battle['idNum']
            if not self.expectedValues.has_key(idNum):
                self.calcExpected(idNum)
            expValues['total_expDamage'] += self.expectedValues[idNum]['expDamage']
            expValues['total_expFrag'] += self.expectedValues[idNum]['expFrag']
            expValues['total_expSpot'] += self.expectedValues[idNum]['expSpot']
            expValues['total_expDef'] += self.expectedValues[idNum]['expDef']
            expValues['total_expWinRate'] += self.expectedValues[idNum]['expWinRate']

        if values['battlesCount'] > 0:
            values['avgWinRate'] = float(values['winsCount']) / values['battlesCount'] * 100
            values['avgDamage'] = float(values['totalDmg']) / values['battlesCount']
            values['avgDamageRec'] = int(values['totalDmgRec'] / values['battlesCount'])
            values['avgPotDmgRec'] = int(values['totalPotDmgRec'] / values['battlesCount'])
            values['avgDeathsCount'] = 0 if values['totalDeathsCount'] < 1 else float(values['totalDeathsCount']) / values['battlesCount']
            values['avgFrag'] = float(values['totalFrag']) / values['battlesCount']
            values['avgShots'] = float(values['totalShots']) / values['battlesCount']
            values['hitsRate'] = 0 if values['totalShots'] < 1 else float(values['totalHits']) / values['totalShots'] * 100
            values['deathsRate'] = 0 if values['totalDeathsCount'] < 1 else float(values['totalDeathsCount']) / values['battlesCount'] * 100
            values['survivalRate'] = 100 if values['totalDeathsCount'] < 1 else abs(float(values['totalDeathsCount']) / values['battlesCount'] * 100 - 100)
            values['effHitsRate'] = 0 if values['totalHits'] < 1 else float(values['totalPierced']) / values['totalHits'] * 100
            values['avgMileage'] = float(values['totalMileage']) / values['battlesCount']
            values['avgMileagekm'] = float(values['totalMileage'] / values['battlesCount']) / 1000
            values['avgSpot'] = float(values['totalSpot']) / values['battlesCount']
            values['avgDef'] = float(values['totalDef']) / values['battlesCount']
            values['avgCap'] = float(values['totalCap']) / values['battlesCount']
            values['avgAssist'] = int(values['totalAssist']) / values['battlesCount']
            values['avgDmgAssistTrack'] = int(values['totalDmgAssistTrack']) / values['battlesCount']
            values['avgDmgAssistRadio'] = int(values['totalDmgAssistRadio']) / values['battlesCount']
            values['avgXP'] = int(values['totalXP'] / values['battlesCount'])
            values['avgOriginalXP'] = int(values['totalOriginXP'] / values['battlesCount'])
            values['avgOriginalPremXP'] = int(values['totalOriginPremXP'] / values['battlesCount'])
            values['avgFreeXP'] = int(values['totalFreeXP'] / values['battlesCount'])
            values['avgOriginalFreeXP'] = int(values['totalOriginalFreeXP'] / values['battlesCount'])
            values['avgTmenXP'] = int(values['totalTmenXP'] / values['battlesCount'])
            values['avgEventTmenXP'] = int(values['totalEventTmenXP'] / values['battlesCount'])
            values['avgNetCredits'] = int(values['netCredits'] / values['battlesCount'])
            values['avgGrossCredits'] = int(values['grossCredits'] / values['battlesCount'])
            values['avgTier'] = float(totalTier) / values['battlesCount']
            values['avgBattleTier'] = float(totalBattleTier) / values['battlesCount']
            values['avgPlace'] = round(float(totalPlace) / values['battlesCount'], 1)
            if values['battlesCountStrongholdSkirmish'] > 0:
                values['avgResources'] = int(values['totalResources'] / values['battlesCountStrongholdSkirmish'])
            else:
                values['avgResources'] = 0
            places = sorted(places)
            length = len(places)
            values['medPlace'] = (places[length / 2] + places[length / 2 - 1]) / 2.0 if not length % 2 else float(places[length / 2])
            for key in expKeys:
                values[key] = expValues['total_' + key] / values['battlesCount']

            values['WN6'] = max(0, int((1240 - 1040 / min(values['avgTier'], 6) ** 0.164) * values['avgFrag'] + values['avgDamage'] * 530 / (184 * math.exp(0.24 * values['avgTier']) + 130) + values['avgSpot'] * 125 + min(values['avgDef'], 2.2) * 100 + (185 / (0.17 + math.exp((values['avgWinRate'] - 35) * -0.134)) - 500) * 0.45 + (6 - min(values['avgTier'], 6)) * -60))
            values['XWN6'] = 100 if values['WN6'] > 2300 else int(max(min(values['WN6'] * (values['WN6'] * (values['WN6'] * (values['WN6'] * (values['WN6'] * (4.66e-18 * values['WN6'] - 3.2413e-14) + 7.524e-11) - 6.516e-08) + 1.307e-05) + 0.05153) - 3.9, 100), 0))
            values['WN7'] = max(0, int((1240 - 1040 / min(values['avgTier'], 6) ** 0.164) * values['avgFrag'] + values['avgDamage'] * 530 / (184 * math.exp(0.24 * values['avgTier']) + 130) + values['avgSpot'] * 125 * min(values['avgTier'], 3) / 3 + min(values['avgDef'], 2.2) * 100 + (185 / (0.17 + math.exp((values['avgWinRate'] - 35) * -0.134)) - 500) * 0.45 - (5 - min(values['avgTier'], 5)) * 125 / (1 + math.exp((values['avgTier'] - math.pow(float(values['battlesCount']) / 220, 3 / values['avgTier'])) * 1.5))))
            values['XWN7'] = 100 if values['WN7'] > 2315 else int(max(min(values['WN7'] * (values['WN7'] * (values['WN7'] * (values['WN7'] * (values['WN7'] * (4.359e-18 * values['WN7'] - 3.262e-14) + 8.6287e-11) - 1.0299e-07) + 6.373e-05) + 0.02439) - 0.58, 100), 0))
            values['EFF'] = max(0, int(values['avgDamage'] * (10 / (values['avgTier'] + 2)) * (0.23 + 2 * values['avgTier'] / 100) + values['avgFrag'] * 250 + values['avgSpot'] * 150 + math.log(values['avgCap'] + 1, 1.732) * 150 + values['avgDef'] * 150))
            values['XEFF'] = 0 if values['EFF'] < 350 else int(max(min(values['EFF'] * (values['EFF'] * (values['EFF'] * (values['EFF'] * (values['EFF'] * (3.388e-17 * values['EFF'] - 2.469e-13) + 6.9335e-10) - 9.5342e-07) + 0.0006656) - 0.1485) - 0.85, 100), 0))
            values['BR'] = max(0, int(values['avgDamage'] * (0.2 + 1.5 / values['avgTier']) + values['avgFrag'] * (350 - values['avgTier'] * 20) + values['avgDmgAssistRadio'] / 2 * (0.2 + 1.5 / values['avgTier']) + values['avgDmgAssistTrack'] / 2 * (0.2 + 1.5 / values['avgTier']) + values['avgSpot'] * 200 + values['avgCap'] * 15 + values['avgDef'] * 15))
        else:
            for key in ['avgResources',
             'avgWinRate',
             'avgDamage',
             'avgDamageRec',
             'avgMileage',
             'avgMileagekm',
             'avgPotDmgRec',
             'survivalRate',
             'deathsRate',
             'avgDeathsCount',
             'avgFrag',
             'avgShots',
             'hitsRate',
             'effHitsRate',
             'avgSpot',
             'avgDef',
             'avgCap',
             'avgAssist',
             'avgDmgAssistTrack',
             'avgDmgAssistRadio',
             'avgXP',
             'avgOriginalXP',
             'avgOriginalPremXP',
             'avgFreeXP',
             'avgOriginalFreeXP',
             'avgTmenXP',
             'avgEventTmenXP',
             'avgNetCredits',
             'avgGrossCredits',
             'avgTier',
             'avgBattleTier',
             'medPlace',
             'avgPlace',
             'WN6',
             'XWN6',
             'WN7',
             'XWN7',
             'EFF',
             'XEFF',
             'BR']:
                values[key] = 0

            for key in expKeys:
                values[key] = 1

        values['avgBattleTierDiff'] = values['avgBattleTier'] - values['avgTier']
        values['rDAMAGE'] = values['avgDamage'] / values['expDamage']
        values['rSPOT'] = values['avgSpot'] / values['expSpot']
        values['rFRAG'] = values['avgFrag'] / values['expFrag']
        values['rDEF'] = values['avgDef'] / values['expDef']
        if len(self.battles) and self.config.get('useWN8WRx', True):
            values['rWIN'] = 1
        else:
            values['rWIN'] = values['avgWinRate'] / values['expWinRate']
        values['rWINc'] = max(0, (values['rWIN'] - 0.71) / 0.29000000000000004)
        values['rDAMAGEc'] = max(0, (values['rDAMAGE'] - 0.22) / 0.78)
        values['rFRAGc'] = max(0, min(values['rDAMAGEc'] + 0.2, (values['rFRAG'] - 0.12) / 0.88))
        values['rSPOTc'] = max(0, min(values['rDAMAGEc'] + 0.1, (values['rSPOT'] - 0.38) / 0.62))
        values['rDEFc'] = max(0, min(values['rDAMAGEc'] + 0.1, (values['rDEF'] - 0.1) / 0.9))
        values['WN8'] = 980 * values['rDAMAGEc'] + 210 * values['rDAMAGEc'] * values['rFRAGc'] + 155 * values['rFRAGc'] * values['rSPOTc'] + 75 * values['rDEFc'] * values['rFRAGc'] + 145 * min(1.8, values['rWINc'])
        values['XWN8'] = 100 if values['WN8'] > 3550 else int(max(min(values['WN8'] * (values['WN8'] * (values['WN8'] * (values['WN8'] * (values['WN8'] * (-4.164e-20 * values['WN8'] + 1.176e-15) - 9.033e-12) + 2.7466e-08) - 3.804e-05) + 0.05819) - 0.965, 100), 0))
        if len(self.battles) and self.config.get('useWN8WRx', True):
            values['WN8'] = int(values['WN8'])
        else:
            values['WN8'] = int(round(values['WN8']))
        values['avgDamage'] = int(values['avgDamage'])
        values['avgMileage'] = int(values['avgMileage'])
        gradient, palette = self.refreshColorMacros(values)
        return (values, gradient, palette)

    def applyMacros(self, val, prec = 2):
        if type(val) == str:
            return val
        if prec <= 0:
            return format(int(round(val)), ',d')
        sVal = format(val, ',.%sf' % prec) if type(val) is float else format(val, ',d')
        separator = self.config.get('thousandSeparator', ' ')
        sVal = sVal.replace(',', separator)
        return sVal

    def applyMacros2(self, val, width, prec = 0):
        if type(val) == str:
            return val
        if prec <= 0:
            sVal = format(val, '>%s,' % width)
        sVal = format(val, '>%s,.%sf' % (width, prec))
        separator = self.config.get('thousandSeparator', ' ')
        sVal = sVal.replace(',', separator)
        return sVal

    def formatString(self, text, values, gradient, palette):
        for key in values.keys():
            text = text.replace('{{%s}}' % key, self.applyMacros(values[key]))
            text = text.replace('{{%s:d}}' % key, self.applyMacros(values[key], 0))
            text = text.replace('{{%s:1f}}' % key, self.applyMacros(values[key], 1))
            for xx in xrange(1, 20):
                text = text.replace('{{%s:%d}}' % (key, xx), self.applyMacros2(values[key], xx))
                text = text.replace('{{%s:%d.1f}}' % (key, xx), self.applyMacros2(values[key], xx, 1))
                text = text.replace('{{%s:%d.2f}}' % (key, xx), self.applyMacros2(values[key], xx, 2))

            text = text.replace('{{g:%s}}' % key, gradient[key])
            text = text.replace('{{c:%s}}' % key, palette[key])

        return text

    def updateMessage(self):
        if not self.configIsValid:
            self.message = 'config.json is not valid'
            return
        if self.recalcWN8:
            self.values, self.gradient, self.palette = self.calcWN8(self.battles)
            self.recalcWN8 = False
        bg = self.config.get('statBackground', '')
        self.bgIcon = self.formatString(bg, self.values, self.gradient, self.palette)
        msg = '\n'.join(self.config.get('template', ''))
        userMacros = self.config.get('userMacros-summary', {})
        for key in userMacros.keys():
            msg = msg.replace('{{%s}}' % key, userMacros[key])

        msg = self.formatString(msg, self.values, self.gradient, self.palette)
        self.messageGeneral = msg
        msg = self.config.get('byTankTitle', '')
        tankStat = {}
        for battle in self.battles:
            idNum = battle['idNum']
            if tankStat.has_key(idNum):
                tankStat[idNum].append(battle)
            else:
                tankStat[idNum] = [battle]

        tankValues = {}
        tankGradient = {}
        tankPalette = {}
        row = self.config.get('byTankRow', '')
        sorting = self.config.get('sortReverse', True)
        tankMacro = self.config.get('sortMacro', 'WN8')
        for idNum in tankStat.keys():
            values, gradient, palette = self.calcWN8(tankStat[idNum])
            tankValues[idNum] = values
            tankGradient[idNum] = gradient
            tankPalette[idNum] = palette

        for idNum in sorted(tankValues.keys(), key=lambda value: tankValues[value][tankMacro], reverse=sorting):
            row = self.config.get('byTankRow', '')
            vt = vehiclesWG.getVehicleType(idNum)
            row = row.replace('{{vehicle-long}}', vt.userString)
            row = row.replace('{{vehicle-short}}', vt.shortUserString)
            row = row.replace('{{vehicle-raw}}', vt.name.replace(':', '-'))
            userMacros = self.config.get('userMacros', {})
            for key in userMacros.keys():
                msg = msg.replace('{{%s}}' % key, userMacros[key])

            row = self.formatString(row, tankValues[idNum], tankGradient[idNum], tankPalette[idNum])
            msg += '\n' + row

        self.messageByTank = msg

    def replaceBattleResultMessage(self, message, arenaUniqueID):
        message = unicode(message, 'utf-8')
        if self.config.get('debugBattleResultMessage', False):
            LOG_NOTE(message)
        basicValues = self.battleStats[arenaUniqueID]['values']
        extendedValues = self.battleStats[arenaUniqueID]['extendedValues']
        values = basicValues
        values.update(extendedValues)
        for pattern in self.battleStatPatterns:
            try:
                if not eval(pattern.get('condition')):
                    continue
            except:
                print '[stat] Invalid calculation condition ' + pattern.get('condition')
                SystemMessages.pushMessage('Invalid calculation condition:\n' + pattern.get('condition'), type=SystemMessages.SM_TYPE.Warning)
                continue

            message = re.sub(pattern.get('pattern', ''), pattern.get('repl', ''), message)

        battleStatText = ''.join(self.config.get('battleStatText', ''))
        gradient = self.battleStats[arenaUniqueID]['gradient']
        palette = self.battleStats[arenaUniqueID]['palette']
        userMacros = self.config.get('userMacros-battle', {})
        for key in userMacros.keys():
            if key in ('dailyXP',) and values[key + 'Factor'] == 1:
                battleStatText = battleStatText.replace('{{%s}}' % key, '')
            if key in ('autoRepair', 'autoLoad', 'autoEquip', 'autoRepairGBM', 'autoLoadGBM', 'autoEquipGBM') and values[key + 'Cost'] == 0:
                battleStatText = battleStatText.replace('{{%s}}' % key, '')
            else:
                battleStatText = battleStatText.replace('{{%s}}' % key, userMacros[key])

        message = message + "<font color='#929290'>" + battleStatText + '</font>'
        message = self.formatString(message, values, gradient, palette)
        return message

    def filterNotificationList(self, item):
        message = item['message'].get('message', '')
        msg = unicode(message, 'utf-8') if isinstance(message, str) else (message if isinstance(message, unicode) else None)
        if msg:
            for pattern in self.config.get('hideMessagePatterns', []):
                if re.search(pattern, msg, re.I):
                    return False

        return True


old_onBecomePlayer = Account.onBecomePlayer

def new_onBecomePlayer(self):
    old_onBecomePlayer(self)
    stat.battleResultsAvailable.set()
    stat.load()


Account.onBecomePlayer = new_onBecomePlayer
old_onBecomeNonPlayer = Account.onBecomeNonPlayer

def new_onBecomeNonPlayer(self):
    stat.battleResultsAvailable.clear()
    old_onBecomeNonPlayer(self)


Account.onBecomeNonPlayer = new_onBecomeNonPlayer
old_nlv_getMessagesList = NotificationListView._NotificationListView__getMessagesList

def new_nlv_getMessagesList(self):
    result = old_nlv_getMessagesList(self)
    if stat.config.get('onlineReloadConfig', False):
        stat.readConfig()
        stat.updateMessage()
        stat.config['onlineReloadConfig'] = True
    if self._NotificationListView__currentGroup in 'info':
        result.append(stat.createMessage())
    return result

NotificationListView._NotificationListView__getMessagesList = new_nlv_getMessagesList
old_nlv_onClickAction = NotificationListView.onClickAction

def new_onClickAction(self, typeID, entityID, action):
    if action == 'wotstatReset':
        stat.reset()
    elif action == 'wotstatSwitchPage':
        stat.page = 1 - stat.page
    else:
        old_nlv_onClickAction(self, typeID, entityID, action)


NotificationListView.onClickAction = new_onClickAction

from notification.settings import NOTIFICATION_GROUP

def new_nlv_setNotificationList(self):
    messages = self._NotificationListView__getMessagesList()
    if len(stat.config.get('hideMessagePatterns', [])):
        messages = filter(stat.filterNotificationList, messages)
    #if stat.config.get('showStatForBattle', True):
    #    messages = map(stat.expandStatNotificationList, messages)
    self.as_setMessagesListS({'messages': messages,
         'emptyListText': self._NotificationListView__getEmptyListMsg(len(messages) > 0),
         'btnBarSelectedIdx': NOTIFICATION_GROUP.ALL.index(self._NotificationListView__currentGroup)})
    self._model.resetNotifiedMessagesCount(self._NotificationListView__currentGroup)


NotificationListView._NotificationListView__setNotificationList = new_nlv_setNotificationList
old_npuv_sendMessageForDisplay = NotificationPopUpViewer._NotificationPopUpViewer__sendMessageForDisplay

def new_npuv_sendMessageForDisplay(self, notification):
    if stat.config.get('showPopUp', True):
        old_npuv_sendMessageForDisplay(self, notification)


NotificationPopUpViewer._NotificationPopUpViewer__sendMessageForDisplay = new_npuv_sendMessageForDisplay
old_brf_format = BattleResultsFormatter.format

def new_brf_format(self, message, *args):
    result = old_brf_format(self, message, *args)
    arenaUniqueID = message.data.get('arenaUniqueID', 0)
    LOG_NOTE("new_brf_format", arenaUniqueID)
    stat.queue.put(arenaUniqueID)
    if stat.config.get('enableBattleEndedMessage', True) and hasattr(BigWorld.player(), 'arena'):
        if BigWorld.player().arena.arenaUniqueID != arenaUniqueID:
            isWinner = message.data.get('isWinner', 0)
            battleEndedMessage = ''
            if isWinner < 0:
                battleEndedMessage = stat.config.get('battleEndedMessageDefeat', '')
            elif isWinner > 0:
                battleEndedMessage = stat.config.get('battleEndedMessageWin', '')
            else:
                battleEndedMessage = stat.config.get('battleEndedMessageDraw', '')
            battleEndedMessage = battleEndedMessage.encode('utf-8')
            playerVehicles = message.data['playerVehicles'].itervalues().next()
            vehicleCompDesc = playerVehicles['vehTypeCompDescr']
            vt = vehiclesWG.getVehicleType(vehicleCompDesc)
            battleEndedMessage = battleEndedMessage.replace('{{vehicle-long}}', vt.userString)
            name = vt.name.replace(':', '-')
            battleEndedMessage = battleEndedMessage.replace('{{vehicle-raw}}', name)
            arenaTypeID = message.data.get('arenaTypeID', 0)
            arenaType = ArenaType.g_cache[arenaTypeID]
            arenaName = i18n.makeString(arenaType.name)
            xp = message.data.get('xp', 0)
            credits = message.data.get('credits', 0)
            battleEndedMessage = battleEndedMessage.replace('{{map}}', arenaName)
            battleEndedMessage = battleEndedMessage.replace('{{map-raw}}', arenaType.geometryName)
            battleEndedMessage = battleEndedMessage.replace('{{xp}}', str(xp))
            battleEndedMessage = battleEndedMessage.replace('{{credits}}', str(credits))
            MessengerEntry.g_instance.gui.addClientMessage(battleEndedMessage)
    return result


BattleResultsFormatter.format = new_brf_format
stat = SessionStatistic()

def new_format(self, ctx = None, data = None):
    item = old_format(self, ctx, data)
    bl = item['buttonsLayout']
    if bl and bl[0]['action'] == 'showBattleResults' and stat.config.get('showStatForBattle', True):
        print "trying to format battle result..."
        savedData = item.get('savedData', -1)
        arenaUniqueID = -1
        if isinstance(savedData, long):
            arenaUniqueID = int(savedData)
        elif isinstance(savedData, tuple):
            arenaUniqueID = int(savedData[0])
        message = item.get('message', '')
        if arenaUniqueID > 0 and type(message) == str:
            retries = 5
            while not (stat.battleStats.has_key(arenaUniqueID) or retries==0):
                # waiting for results for some time, formatter is async so it is safe
                time.sleep(1)
                retries = retries - 1
            if stat.battleStats.has_key(arenaUniqueID):
              message = stat.replaceBattleResultMessage(message, arenaUniqueID)
              item['message'] = message
              if stat.config.get('overwriteBattleResultBgIcon', False):
                  result = stat.battleStats[arenaUniqueID]['extendedValues']['result']
                  bgIconKeys = {-1: 'bgIconDefeat',
                   0: 'bgIconDraw',
                   1: 'bgIconWin'}
                  bgIconKey = bgIconKeys[result]
                  bgIcon = stat.config.get(bgIconKey, item['message']['bgIcon'])
                  item['bgIcon'] = bgIcon
    return item
    
old_format = _MessageTemplate.format
_MessageTemplate.format = new_format
